-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Move widget to tab #8236
Move widget to tab #8236
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few smaller suggestions for improvements, the only bigger topic I would like to discuss is the handling of the widget positions.
@@ -328,6 +361,12 @@ class Widget extends React.Component<Props, State> { | |||
onCancel={this._onToggleCopyToDashboard} /> | |||
)} | |||
{showCsvExport && <CSVExportModal view={view.view} directExportWidgetId={widget.id} closeModal={this._onToggleCSVExport} />} | |||
{showMoveWidgetToTab && ( | |||
<MoveWidgetToTabForm view={view.view} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call the component MoveWidgetToTabModal
to unify the naming with comparable components.
: <span>No dashboards found</span>; | ||
|
||
return ( | ||
<Modal show> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use the BootstrapModalForm
here.
return queryIds.map((queryId, index) => { | ||
const tabTitle = view.state.get(queryId).titles.get('tab', Map({ title: `Page#${index + 1}` })); | ||
return ({ id: queryId, name: tabTitle.get('title') }); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should either sort the list alphabetically or use the order defined by the dashboard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think about not including the current page in the list?
If "Keep Copy" is not active, nothing happens and otherwise the widget will be copied.
If the user wants to copy a widget on the same dashboard page he can also use the copy action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should either sort the list alphabetically or use the order defined by the dashboard
As you see I need to "make up" the names of the page titles on the fly. That means it is in order defined by the dashboard.
const _tabList = (view: View): Array<TabEntry> => { | ||
const queryIds = Object.keys(view.state.toObject()); | ||
return queryIds.map((queryId, index) => { | ||
const tabTitle = view.state.get(queryId).titles.get('tab', Map({ title: `Page#${index + 1}` })); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about extracting the Page#
part and using the reference here, in QueryTitle.js
and QueryTabs.jsx
. But having a look at QueryTitle.js
you can also use the related queryTitle
function, like we do it in BigDisplayMode
.
onClick={() => setSelectedTab(id)} | ||
active={id === selectedTab} | ||
key={id} /> | ||
)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a side note, it would be great if we could unify the styling of this list, the list which is visible when copying a widget to a dashboard and the saved searches list. We are sometimes defining the title as a header and sometimes as a child, which is supposed to be the description. I will create a separate issue for this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I now used the children
way since it looks much nicer than the header way. Thanks for pointing that out.
|
||
type QueryId = string; | ||
|
||
const FindWidgetAndQueryIdInView = (widgetId: string, view: View): ?[Widget, QueryId] => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use the newly created WidgetId
type from views/types
here.
.build(); | ||
}; | ||
|
||
const MoveWidgetToTab = (widgetId: string, targetQueryId: QueryId, dashboard: View, copy: boolean = false): ?View => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could use the newly created WidgetId
type from views/types
here as well.
|
||
const MoveWidgetToTab = (widgetId: string, targetQueryId: QueryId, dashboard: View, copy: boolean = false): ?View => { | ||
if (dashboard.type !== View.Type.Dashboard) { | ||
return undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to expect that this function returns a View
and to throw an error if the search type does not match or if the the related Query
does not exist.
const _removeWidgetFromTab = (widgetId: string, queryId: string, dashboard: View): View => { | ||
const viewState = dashboard.state.get(queryId); | ||
const widgetIndex = viewState.widgets.findIndex((widget) => widget.id === widgetId); | ||
const widgetPosition = viewState.widgetPositions; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call this variable widgetPositions
return undefined; | ||
}; | ||
|
||
export default MoveWidgetToTab; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One bigger topic I would like to discuss is the handling of the widget positions when removing / adding a widget.
When the widget is being moved to a page, we do not define / calculate a widget position. This can lead to strange behaviours. During my tests the newly created widget is mostly being displayed in the second row of the widget grid. But when I open and close the widget edit modal, it moves to the first row. It would be great if we could reuse some parts of the logic we are using when creating a widget. (CurrentViewStateStore
-> widgets()
)
When a user removes a widget, by not using the "Keep Copy" option, we are just removing the widget position, which is ok, because it behaves like when using the widget action Delete
. But in my opinion we should check if this effects other widgets and recalculate their positions. E.g. when the widget was the only one in its row.
The WidgetGrid
is currently able to handle this "empty space", but it is possible that this will lead to problems in the future. For now I would not change this part, but I will create an issue to adjust this behaviour which includes writing a migration which checks if empty spaces theoretically exists on a dashboard and which recalculates the related widget positions.
What do you think?
Prior to this change, there was no code providing the possibility to move a widget from one tab to another. This change will add a function.
Prior to this change, there was no Modal where a user could select to which tab he would like to move a widget. This change will create a new Modal and connects it to the former MoveWidgetToTab logic.
Prior to these changes, we did not update the search id, which needed to be change, since we changed the search. This change will mock the id generation so we do not have failing snapshots tests with every run.
Prior to this change, you could only move a widget to a different page. Now you can also copy it instead. This change will add a checkbox where the user can decide to copy the widget instead of moving it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the update! I've added a few follow-up comments.
.col(0) | ||
.row(0) | ||
.build(); | ||
const tempDashboard = copy ? dashboard : _removeWidgetFromTab(widgetId, queryId, dashboard); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the reason you've implemented .col(0).row(0)
here?
It would be great if moving a widget to a dashboard tab would add the widget to the tab in the same way, like when adding a new widget to a tab. We could reuse some of the logic to recalculate the widget positions from CurrentViewStateStore
-> widgets()
<BootstrapModalForm show | ||
onCancel={onCancel} | ||
onSubmitForm={() => onSubmit(widgetId, selectedTab, keepCopy)} | ||
title="Choose Target Page"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's reimplement submitButtonDisabled={!selectedTab}
here.
const [keepCopy, setKeepCopy] = useState(false); | ||
const { id: activeQuery } = useStore(CurrentQueryStore); | ||
|
||
const list = _tabList(view).filter(({ id }) => id !== activeQuery); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the last review I suggested to remove the current tab from the list, but after having another look I prefer to just disable the active tab. It has the benefits, that the user always sees the "same" list no matter on which tab he opens the modal and it is easier to find the surrounding tabs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the sort: #8264 there is no order :( Which we need to fix before this PR I think now. :(
Regarding disable: I think you were right before, the user does not expect to find the current tab here. Therefore I would leave it out to reduce the visual noise. Let's discuss it later in or after the meeting.
Prior to this change, we simply add a new WidgetPosition with row 1 and col 1. This will only lead to the problem that now 2 widgets have this coordinates. This change will reuse the calculation method from CurrentViewStateStore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for the update! Two last points:
- The widget title is getting lost when moving a widget
- When I open the modal and click directly on the "keep copy" checkbox, the click has no effect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me!
Description
Add new menu item to widget menu:
Move to Page
. When clicking the user will see a modalwhere he can select the target page. Optionally he can decide to keep a copy of the widget
on the current page. After selecting the target page he clicks on
Select
. The widget willbe moved to the new page and the active page will be set to the target query.
Motivation and Context
A user was able to copy a widget from a search to the first page of a dashboard. But the widget
was stuck now there. There was no possibility to move the widget to a different page of the dashboard.
How Has This Been Tested?
Fixes #8152
Screenshots (if appropriate):
Types of changes
Checklist: