Skip to content

Commit

Permalink
Enable right click on visualizations and dashboards listings (#88936)
Browse files Browse the repository at this point in the history
* Enable right-click on visualizations listing page

* Make it simpler

* Enable right click to the dashboard listing

* Add unit tests

* Fix link on dashboard

* Fix visualize link

* Fix PR comments

* Fix functional test failure

* Use kbnUrlStateStorage instead

* Change method to getDashboardListItemLink

* Change method to getVisualizeListItemLink

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
stratoula and kibanamachine authored Jan 28, 2021
1 parent 8534faf commit b4931e6
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui';
import React, { Fragment, useCallback, useEffect, useMemo } from 'react';

import { attemptLoadDashboardByTitle } from '../lib';
import { DashboardAppServices, DashboardRedirect } from '../types';
import { getDashboardBreadcrumb, dashboardListingTable } from '../../dashboard_strings';
import { ApplicationStart, SavedObjectsFindOptionsReference } from '../../../../../core/public';

import { syncQueryStateWithUrl } from '../../services/data';
import { IKbnUrlStateStorage } from '../../services/kibana_utils';
import { TableListView, useKibana } from '../../services/kibana_react';
import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss';
import { getDashboardListItemLink } from './get_dashboard_list_item_link';

export interface DashboardListingProps {
kbnUrlStateStorage: IKbnUrlStateStorage;
Expand Down Expand Up @@ -83,8 +82,13 @@ export const DashboardListing = ({

const tableColumns = useMemo(
() =>
getTableColumns((id) => redirectTo({ destination: 'dashboard', id }), savedObjectsTagging),
[savedObjectsTagging, redirectTo]
getTableColumns(
core.application,
kbnUrlStateStorage,
core.uiSettings.get('state:storeInSessionStorage'),
savedObjectsTagging
),
[core.application, core.uiSettings, kbnUrlStateStorage, savedObjectsTagging]
);

const noItemsFragment = useMemo(
Expand All @@ -99,7 +103,6 @@ export const DashboardListing = ({
(filter: string) => {
let searchTerm = filter;
let references: SavedObjectsFindOptionsReference[] | undefined;

if (savedObjectsTagging) {
const parsed = savedObjectsTagging.ui.parseSearchQuery(filter, {
useName: true,
Expand Down Expand Up @@ -164,17 +167,25 @@ export const DashboardListing = ({
};

const getTableColumns = (
redirectTo: (id?: string) => void,
application: ApplicationStart,
kbnUrlStateStorage: IKbnUrlStateStorage,
useHash: boolean,
savedObjectsTagging?: SavedObjectsTaggingApi
) => {
return [
{
field: 'title',
name: dashboardListingTable.getTitleColumnName(),
sortable: true,
render: (field: string, record: { id: string; title: string }) => (
render: (field: string, record: { id: string; title: string; timeRestore: boolean }) => (
<EuiLink
onClick={() => redirectTo(record.id)}
href={getDashboardListItemLink(
application,
kbnUrlStateStorage,
useHash,
record.id,
record.timeRestore
)}
data-test-subj={`dashboardListingTitleLink-${record.title.split(' ').join('-')}`}
>
{field}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/

import { getDashboardListItemLink } from './get_dashboard_list_item_link';
import { ApplicationStart } from 'kibana/public';
import { esFilters } from '../../../../data/public';
import { createHashHistory } from 'history';
import { createKbnUrlStateStorage } from '../../../../kibana_utils/public';
import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator';

const DASHBOARD_ID = '13823000-99b9-11ea-9eb6-d9e8adceb647';

const application = ({
getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => {
return `/app/${appId}${options?.path}`;
}),
} as unknown) as ApplicationStart;

const history = createHashHistory();
const kbnUrlStateStorage = createKbnUrlStateStorage({
history,
useHash: false,
});
kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: 'now' } });

describe('listing dashboard link', () => {
test('creates a link to a dashboard without the timerange query if time is saved on the dashboard', async () => {
const url = getDashboardListItemLink(
application,
kbnUrlStateStorage,
false,
DASHBOARD_ID,
true
);
expect(url).toMatchInlineSnapshot(`"/app/dashboards#/view/${DASHBOARD_ID}?_g=()"`);
});

test('creates a link to a dashboard with the timerange query if time is not saved on the dashboard', async () => {
const url = getDashboardListItemLink(
application,
kbnUrlStateStorage,
false,
DASHBOARD_ID,
false
);
expect(url).toMatchInlineSnapshot(
`"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:now-7d,to:now))"`
);
});
});

describe('when global time changes', () => {
beforeEach(() => {
kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
time: {
from: '2021-01-05T11:45:53.375Z',
to: '2021-01-21T11:46:00.990Z',
},
});
});

test('propagates the correct time on the query', async () => {
const url = getDashboardListItemLink(
application,
kbnUrlStateStorage,
false,
DASHBOARD_ID,
false
);
expect(url).toMatchInlineSnapshot(
`"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"`
);
});
});

describe('when global refreshInterval changes', () => {
beforeEach(() => {
kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
refreshInterval: { pause: false, value: 300 },
});
});

test('propagates the refreshInterval on the query', async () => {
const url = getDashboardListItemLink(
application,
kbnUrlStateStorage,
false,
DASHBOARD_ID,
false
);
expect(url).toMatchInlineSnapshot(
`"/app/dashboards#/view/${DASHBOARD_ID}?_g=(refreshInterval:(pause:!f,value:300))"`
);
});
});

describe('when global filters change', () => {
beforeEach(() => {
const filters = [
{
meta: {
alias: null,
disabled: false,
negate: false,
},
query: { query: 'q1' },
},
{
meta: {
alias: null,
disabled: false,
negate: false,
},
query: { query: 'q1' },
$state: {
store: esFilters.FilterStateStore.GLOBAL_STATE,
},
},
];
kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, {
filters,
});
});

test('propagates the filters on the query', async () => {
const url = getDashboardListItemLink(
application,
kbnUrlStateStorage,
false,
DASHBOARD_ID,
false
);
expect(url).toMatchInlineSnapshot(
`"/app/dashboards#/view/${DASHBOARD_ID}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"`
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* and the Server Side Public License, v 1; you may not use this file except in
* compliance with, at your election, the Elastic License or the Server Side
* Public License, v 1.
*/
import { ApplicationStart } from 'kibana/public';
import { QueryState } from '../../../../data/public';
import { setStateToKbnUrl } from '../../../../kibana_utils/public';
import { createDashboardEditUrl, DashboardConstants } from '../../dashboard_constants';
import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator';
import { IKbnUrlStateStorage } from '../../services/kibana_utils';

export const getDashboardListItemLink = (
application: ApplicationStart,
kbnUrlStateStorage: IKbnUrlStateStorage,
useHash: boolean,
id: string,
timeRestore: boolean
) => {
let url = application.getUrlForApp(DashboardConstants.DASHBOARDS_ID, {
path: `#${createDashboardEditUrl(id)}`,
});
const globalStateInUrl = kbnUrlStateStorage.get<QueryState>(GLOBAL_STATE_STORAGE_KEY) || {};

if (timeRestore) {
delete globalStateInUrl.time;
delete globalStateInUrl.refreshInterval;
}
url = setStateToKbnUrl<QueryState>(GLOBAL_STATE_STORAGE_KEY, globalStateInUrl, { useHash }, url);
return url;
};
2 changes: 2 additions & 0 deletions src/plugins/visualize/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
*/

export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size';
export const STATE_STORAGE_KEY = '_a';
export const GLOBAL_STATE_STORAGE_KEY = '_g';
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const VisualizeListing = () => {
savedObjectsTagging,
uiSettings,
visualizeCapabilities,
kbnUrlStateStorage,
},
} = useKibana<VisualizeServices>();
const { pathname } = useLocation();
Expand Down Expand Up @@ -94,11 +95,10 @@ export const VisualizeListing = () => {
);

const noItemsFragment = useMemo(() => getNoItemsMessage(createNewVis), [createNewVis]);
const tableColumns = useMemo(() => getTableColumns(application, history, savedObjectsTagging), [
application,
history,
savedObjectsTagging,
]);
const tableColumns = useMemo(
() => getTableColumns(application, kbnUrlStateStorage, savedObjectsTagging),
[application, kbnUrlStateStorage, savedObjectsTagging]
);

const fetchItems = useCallback(
(filter) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
*/

import React from 'react';
import { History } from 'history';
import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiBadge } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';

import { ApplicationStart } from 'kibana/public';
import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public';
import { VisualizationListItem } from 'src/plugins/visualizations/public';
import type { SavedObjectsTaggingApi } from 'src/plugins/saved_objects_tagging_oss/public';
import { RedirectAppLinks } from '../../../../kibana_react/public';
import { getVisualizeListItemLink } from './get_visualize_list_item_link';

const getBadge = (item: VisualizationListItem) => {
if (item.stage === 'beta') {
Expand Down Expand Up @@ -72,7 +73,7 @@ const renderItemTypeIcon = (item: VisualizationListItem) => {

export const getTableColumns = (
application: ApplicationStart,
history: History,
kbnUrlStateStorage: IKbnUrlStateStorage,
taggingApi?: SavedObjectsTaggingApi
) => [
{
Expand All @@ -84,18 +85,14 @@ export const getTableColumns = (
render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) =>
// In case an error occurs i.e. the vis has wrong type, we render the vis but without the link
!error ? (
<EuiLink
onClick={() => {
if (editApp) {
application.navigateToApp(editApp, { path: editUrl });
} else if (editUrl) {
history.push(editUrl);
}
}}
data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`}
>
{field}
</EuiLink>
<RedirectAppLinks application={application}>
<EuiLink
href={getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl)}
data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`}
>
{field}
</EuiLink>
</RedirectAppLinks>
) : (
field
),
Expand Down
Loading

0 comments on commit b4931e6

Please sign in to comment.