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

Move widget to tab #8236

Merged
merged 19 commits into from
Jun 12, 2020
Merged

Move widget to tab #8236

merged 19 commits into from
Jun 12, 2020

Conversation

kmerz
Copy link
Member

@kmerz kmerz commented May 29, 2020

Description

Add new menu item to widget menu: Move to Page. When clicking the user will see a modal
where 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 will
be 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?

  • Create a dashboard
  • create a widget on the first page
  • move the widget to a different page

Fixes #8152

Screenshots (if appropriate):

Graylog (3)
Graylog (2)

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Refactoring (non-breaking change)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.

Copy link
Contributor

@linuspahl linuspahl left a 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}
Copy link
Contributor

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>
Copy link
Contributor

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') });
});
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Member Author

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}` }));
Copy link
Contributor

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} />
));
Copy link
Contributor

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.

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 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] => {
Copy link
Contributor

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 => {
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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?

kmerz added 11 commits June 5, 2020 14:59
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.
Copy link
Contributor

@linuspahl linuspahl left a 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);
Copy link
Contributor

@linuspahl linuspahl Jun 8, 2020

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">
Copy link
Contributor

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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Regarding the sort, it does not look correct to me, compared with the dashboard:
image

One solution is using the order / values provided by the QueryIdsStore.

Copy link
Contributor

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.

Copy link
Member Author

@kmerz kmerz Jun 9, 2020

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.

kmerz added 5 commits June 9, 2020 15:48
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.
Copy link
Contributor

@linuspahl linuspahl left a 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

Copy link
Contributor

@linuspahl linuspahl left a 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Copy/move tabs between dashboards
2 participants