Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Widget component to React #4020

Merged
merged 40 commits into from
Sep 20, 2019
Merged

Migrate Widget component to React #4020

merged 40 commits into from
Sep 20, 2019

Conversation

gabrieldutra
Copy link
Member

What type of PR is this? (check all applicable)

  • Refactor

Description

Migration of the Widget Component.

Related Tickets & Documents

--

Mobile & Desktop Screenshots/Recordings (if there are UI changes)

--

@gabrieldutra gabrieldutra added Frontend Frontend: React Frontend codebase migration to React labels Jul 31, 2019
@gabrieldutra gabrieldutra self-assigned this Jul 31, 2019
@gabrieldutra gabrieldutra force-pushed the react-widget branch 3 times, most recently from 4bc0086 to f756e5f Compare August 6, 2019 13:50
<span>{widget.getQuery().name}</span>
</React.Fragment>
)}
width={900}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@arikfr @gabrieldutra wdyt about going 100%?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably makes sense, need to see it to get a feel.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it with 900, it works as a max-width, so it goes 100% when using small resolutions and doesn't go that big on larger ones. In any case, I can update to 100% if you want to get the feel of it.

900
Screenshot from 2019-08-18 13-11-23

100%
Screenshot from 2019-08-18 13-11-51

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe 95% or something that's a bit smaller than the current width of the page?

How does it decide on the height?

client/app/components/dashboards/DashboardGrid.jsx Outdated Show resolved Hide resolved
<TextboxWidget
widget={widget}
showDropdown={!isPublic && canEdit}
showDeleteButton={!isPublic && canEdit}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems we can merge these two into canEdit to remain consistent with the VisualizationWidget component and have the same props?

@@ -0,0 +1,3 @@
import Widget from './Widget';

export default Widget;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's this for?

public={this.props.isPublic}
onDelete={() => onRemoveWidget(widget.id)}
isPublic={this.props.isPublic}
canEdit={dashboard.canEdit()}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking this would be the place widget type would be considered, so DashboardWidget's role would be a superclass only and would never be called directly. Sth like:

{widgets.map(widget => (
  <div
    key={widget.id}
    data-grid={DashboardGrid.normalizeFrom(widget)}
    
  >
    {widget.visualization ? <VisualizationWidget {...this.props} /> : <TextboxWidget {...this.props} />}

(the above assumes restricted is handled by VisualizationWidget and widget.width is checked by TextboxWidget)

Copy link
Member Author

@gabrieldutra gabrieldutra Aug 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ranbena I've started working on a Composition pattern here where <Widget> will hold all basic widget stuff (shared menu options, dropdown & remove button) and then we have specializations: <TextboxWidget> and <VisualizationWidget>. This can also be a createWidget HOC if you want to make sure Widget can't be used alone. I'm finally getting the feeling this is going in a good direction, just need to finish working on <VisualizationWidget> (basically see where header and bottom things will be) and on the Props that will be around components.

@arikfr I'll review your other comments again once done with this part as the answers depend on this. To give you a context, I previously had started by moving the code and minimally separating it, the resulting complexity has been annoying me till now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can also be a createWidget HOC if you want to make sure Widget can't be used alone.

Feels like unnecessary complexity. Also I'm not sure about having a VisualizationWidget/TextboxWidget that use a Widget component rather than Widget component that renders the common things and calls on the Viz/Textbox to render the content. The Textbox and Viz have very little in common and this little can be in their parent.

I think this is what you initially had, but something along the lines of:

<DashboardGrid> creates <Widget> components, the <Widget> component renders either a <VisualizationWidget> or a <TextboxWidget>.

Copy link
Member Author

@gabrieldutra gabrieldutra Aug 19, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HOC is actually not far from what it is now, it's just a matter of using a function to return the Widget class. But I shared the same feeling of it being unnecessary.

There are some key parts that composition has advantages and that I realized once I had that code. Overall the result is not that different, but one thing that's nice is to be able to use props instead of repeating the switch logic. So the Widget "shell" will have: tile, delete button, dropdown button (with remove option), optional title.

Example context where I prefer it:

  • I want to have a shared header part (buttons) and also to be able to customize it depending on type.
class DashboardWidget {
  render() {
    const widgetType;
    return (
      <div>
        <SharedHeader />
        {widgetType === 'visualization' && <VisualizationWidget />}
        {widgetType === 'textbox' && <TextboxWidget />}
        <SharedBottom />
      </div>
    );
  }
}

With composition I can build that using props :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But they actually don't have anything in common in terms of content:

  1. Textbox has a header with only a menu button and we should actually make sure it doesn't push down content (like it does today).
  2. Textbox has no footer (bottom).

With composition I can build that using props :)

If it's not too much effort, can you show an example of what you mean?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, no need: I see it in the last commit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the way it turned out 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change anything, but just want to share that personally I would opt in to a way of composition where you would reuse the same small components (wrapping object, menu button, delete button) between the two instead of reusing some configurable piece of code (Widget).

So there is still a Widget component, but it's very basic just to ensure we use the correct class names and such.

This way you have a single place you need to edit if you want to update how one of them behaves, a single place if you want to read how something is working & less risk of breaking one when updating the other.


The current implementation is great and let's work towards finalizing it and moving on instead of more bikeshedding :-)

Copy link
Member Author

@gabrieldutra gabrieldutra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't postpone opening this to review anymore (was finding small issues at every deep-test I was doing). I'll continue testing, but code shouldn't change much.

if (!disableUrlUpdate) {
updateUrl(parameters);
}
onValuesChange(parametersWithPendingValues);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the Dashboard context it needs to have the url updated before triggering onValuesChange

// ANGULAR_REMOVE_ME This forces Widgets re-rendering
// use state when Dashboard is migrated to React
this.forceDashboardGridReload = () => {
this.dashboard.widgets = [...this.dashboard.widgets];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually had to change to use this to force the components re-rendering. @kravets-levko in case you have a better idea :)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's totally fine 👍

Copy link
Contributor

@ranbena ranbena left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job @gabrieldutra 🎉

Copy link
Collaborator

@kravets-levko kravets-levko left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really awesome @gabrieldutra! So massive piece of code migrated to React and refined 🙇‍♂️

<>
<VisualizationName visualization={widget.visualization} />{' '}
<span>{widget.getQuery().name}</span>
</>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

He-he, GitHub has issues with parsing <>...</> 😁

// ANGULAR_REMOVE_ME This forces Widgets re-rendering
// use state when Dashboard is migrated to React
this.forceDashboardGridReload = () => {
this.dashboard.widgets = [...this.dashboard.widgets];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's totally fine 👍

Co-Authored-By: Ran Byron <ranbena@gmail.com>
@gabrieldutra
Copy link
Member Author

@arikfr if there is something that holds this to be merged speak now or forever hold you peace 🙂.

@arikfr arikfr merged commit cb654b3 into master Sep 20, 2019
@arikfr arikfr deleted the react-widget branch September 20, 2019 19:08
@arikfr
Copy link
Member

arikfr commented Sep 20, 2019

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Frontend: React Frontend codebase migration to React Frontend
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants