From eb2c8b8fac7b77ba03ecf2517a25345cf9a7bb94 Mon Sep 17 00:00:00 2001
From: Angela Chuang <6295984+angorayc@users.noreply.github.com>
Date: Tue, 2 May 2023 10:26:32 +0100
Subject: [PATCH] [SecuritySolution] Fix edit dashboard url (#156160)
## Summary
It lands on the wrong page after clicking on `Edit Dashboard` button.
- Steps to reproduce:
1. Create a dashboard, save it and add a Security Solution tag.
2. Back to SecuritySolution > Dashboards, select the dashboard you
added.
3. Click the `Edit` button at the top right corner.
4. Observe that it lands at Kibana dashboard listing page.
Expect:
It should navigate to Kibana dashboard's edit mode.
### Checklist
Delete any items that are not applicable to this PR.
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
---
.../components/edit_dashboard_button.test.tsx | 58 ++++++--
.../components/edit_dashboard_button.tsx | 35 +++--
.../hooks/use_dashboard_app_link.test.tsx | 133 ------------------
.../hooks/use_dashboard_app_link.tsx | 71 ----------
4 files changed, 71 insertions(+), 226 deletions(-)
delete mode 100644 x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.tsx
diff --git a/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.test.tsx b/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.test.tsx
index e4cd6fa206929..43afa552d50fd 100644
--- a/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.test.tsx
+++ b/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.test.tsx
@@ -5,12 +5,16 @@
* 2.0.
*/
-import { render } from '@testing-library/react';
+import type { RenderResult } from '@testing-library/react';
+import { fireEvent, render } from '@testing-library/react';
import React from 'react';
+import type { Query } from '@kbn/es-query';
+
import { useKibana } from '../../common/lib/kibana';
-import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock';
import { TestProviders } from '../../common/mock/test_providers';
+import type { EditDashboardButtonComponentProps } from './edit_dashboard_button';
import { EditDashboardButton } from './edit_dashboard_button';
+import { ViewMode } from '@kbn/embeddable-plugin/public';
jest.mock('../../common/lib/kibana/kibana_react', () => {
return {
@@ -24,18 +28,54 @@ describe('EditDashboardButton', () => {
to: '2023-03-24T23:59:59.999Z',
};
- beforeAll(() => {
+ const props = {
+ filters: [],
+ query: { query: '', language: '' } as Query,
+ savedObjectId: 'mockSavedObjectId',
+ timeRange,
+ };
+ const servicesMock = {
+ dashboard: { locator: { getRedirectUrl: jest.fn() } },
+ application: {
+ navigateToApp: jest.fn(),
+ navigateToUrl: jest.fn(),
+ },
+ };
+
+ const renderButton = (testProps: EditDashboardButtonComponentProps) => {
+ return render(
+
+
+
+ );
+ };
+
+ let renderResult: RenderResult;
+ beforeEach(() => {
(useKibana as jest.Mock).mockReturnValue({
- services: createStartServicesMock(),
+ services: servicesMock,
});
+ renderResult = renderButton(props);
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
});
it('should render', () => {
- const { queryByTestId } = render(
-
-
-
+ expect(renderResult.queryByTestId('dashboardEditButton')).toBeInTheDocument();
+ });
+
+ it('should render dashboard edit url', () => {
+ fireEvent.click(renderResult.getByTestId('dashboardEditButton'));
+ expect(servicesMock.dashboard?.locator?.getRedirectUrl).toHaveBeenCalledWith(
+ expect.objectContaining({
+ query: props.query,
+ filters: props.filters,
+ timeRange: props.timeRange,
+ dashboardId: props.savedObjectId,
+ viewMode: ViewMode.EDIT,
+ })
);
- expect(queryByTestId('dashboardEditButton')).toBeInTheDocument();
});
});
diff --git a/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.tsx b/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.tsx
index c1089e95787e0..bd360229c7e1f 100644
--- a/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.tsx
+++ b/x-pack/plugins/security_solution/public/dashboards/components/edit_dashboard_button.tsx
@@ -5,12 +5,12 @@
* 2.0.
*/
-import React from 'react';
+import React, { useCallback } from 'react';
import type { Query, Filter } from '@kbn/es-query';
import { EuiButton } from '@elastic/eui';
-import { useDashboardAppLink } from '../hooks/use_dashboard_app_link';
+import { ViewMode } from '@kbn/embeddable-plugin/public';
import { EDIT_DASHBOARD_BUTTON_TITLE } from '../pages/details/translations';
-import { useKibana } from '../../common/lib/kibana';
+import { useKibana, useNavigation } from '../../common/lib/kibana';
export interface EditDashboardButtonComponentProps {
filters?: Filter[];
@@ -31,24 +31,33 @@ const EditDashboardButtonComponent: React.FC
timeRange,
}) => {
const {
- services: { uiSettings },
+ services: { dashboard },
} = useKibana();
+ const { navigateTo } = useNavigation();
- const { onClick } = useDashboardAppLink({
- query,
- filters,
- timeRange,
- uiSettings,
- savedObjectId,
- });
-
+ const onClick = useCallback(
+ (e) => {
+ e.preventDefault();
+ const url = dashboard?.locator?.getRedirectUrl({
+ query,
+ filters,
+ timeRange,
+ dashboardId: savedObjectId,
+ viewMode: ViewMode.EDIT,
+ });
+ if (url) {
+ navigateTo({ url });
+ }
+ },
+ [dashboard?.locator, query, filters, timeRange, savedObjectId, navigateTo]
+ );
return (
{EDIT_DASHBOARD_BUTTON_TITLE}
diff --git a/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.test.tsx b/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.test.tsx
deleted file mode 100644
index decd108b6d6af..0000000000000
--- a/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.test.tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { renderHook } from '@testing-library/react-hooks';
-import { useNavigation } from '../../common/lib/kibana';
-import { TestProviders } from '../../common/mock';
-import type { UseDashboardAppLinkProps } from './use_dashboard_app_link';
-import { useDashboardAppLink } from './use_dashboard_app_link';
-
-jest.mock('../../common/lib/kibana', () => ({
- useNavigation: jest.fn(),
-}));
-
-describe('useDashboardAppLink', () => {
- const mockNavigateTo = jest.fn();
- const filters = [
- {
- meta: {
- index: 'security-solution-default',
- type: 'phrase',
- key: 'event.action',
- params: {
- query: 'host',
- },
- disabled: false,
- negate: false,
- alias: null,
- },
- query: {
- match_phrase: {
- 'event.action': 'host',
- },
- },
- $state: {
- store: 'appState',
- },
- },
- ];
- const props = {
- query: {
- language: 'kuery',
- query: '',
- },
- filters: [],
- timeRange: {
- from: '2023-03-24T00:00:00.000Z',
- fromStr: 'now/d',
- to: '2023-03-24T23:59:59.999Z',
- toStr: 'now/d',
- },
- uiSettings: {
- get: jest.fn(),
- },
- savedObjectId: 'e2937420-c8ba-11ed-a7eb-3d08ee4d53cb',
- };
-
- beforeEach(() => {
- jest.clearAllMocks();
- (useNavigation as jest.Mock).mockReturnValue({
- getAppUrl: jest
- .fn()
- .mockReturnValue('/app/dashboards#/view/e2937420-c8ba-11ed-a7eb-3d08ee4d53cb'),
- navigateTo: mockNavigateTo,
- });
- });
- it('create links to Dashboard app - with filters', () => {
- const testProps = { ...props, filters } as unknown as UseDashboardAppLinkProps;
-
- const { result } = renderHook(() => useDashboardAppLink(testProps), {
- wrapper: TestProviders,
- });
- expect(result.current.href).toMatchInlineSnapshot(
- `"/app/dashboards#/view/e2937420-c8ba-11ed-a7eb-3d08ee4d53cb?_g=(filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:security-solution-default,key:event.action,negate:!f,params:(query:host),type:phrase),query:(match_phrase:(event.action:host)))),query:(language:kuery,query:''),time:(from:now%2Fd,to:now%2Fd))"`
- );
- });
-
- it('create links to Dashboard app - with query', () => {
- const testProps = {
- ...props,
- query: {
- language: 'kuery',
- query: '@timestamp : *',
- },
- } as unknown as UseDashboardAppLinkProps;
-
- const { result } = renderHook(() => useDashboardAppLink(testProps), { wrapper: TestProviders });
- expect(result.current.href).toMatchInlineSnapshot(
- `"/app/dashboards#/view/e2937420-c8ba-11ed-a7eb-3d08ee4d53cb?_g=(filters:!(),query:(language:kuery,query:'@timestamp%20:%20*'),time:(from:now%2Fd,to:now%2Fd))"`
- );
- });
-
- it('create links to Dashboard app - with absolute time', () => {
- const testProps = {
- ...props,
- timeRange: {
- from: '2023-03-24T00:00:00.000Z',
- to: '2023-03-24T23:59:59.999Z',
- },
- } as unknown as UseDashboardAppLinkProps;
-
- const { result } = renderHook(() => useDashboardAppLink(testProps), { wrapper: TestProviders });
- expect(result.current.href).toMatchInlineSnapshot(
- `"/app/dashboards#/view/e2937420-c8ba-11ed-a7eb-3d08ee4d53cb?_g=(filters:!(),query:(language:kuery,query:''),time:(from:'2023-03-24T00:00:00.000Z',to:'2023-03-24T23:59:59.999Z'))"`
- );
- });
-
- it('navigate to dashboard app with preserved states', () => {
- const testProps = {
- ...props,
- timeRange: {
- from: '2023-03-24T00:00:00.000Z',
- to: '2023-03-24T23:59:59.999Z',
- },
- } as unknown as UseDashboardAppLinkProps;
-
- const { result } = renderHook(() => useDashboardAppLink(testProps), {
- wrapper: TestProviders,
- });
- result.current.onClick({
- preventDefault: jest.fn(),
- } as unknown as React.MouseEvent);
-
- expect(mockNavigateTo).toHaveBeenCalledWith(
- expect.objectContaining({
- url: "/app/dashboards#/view/e2937420-c8ba-11ed-a7eb-3d08ee4d53cb?_g=(filters:!(),query:(language:kuery,query:''),time:(from:'2023-03-24T00:00:00.000Z',to:'2023-03-24T23:59:59.999Z'))",
- })
- );
- });
-});
diff --git a/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.tsx b/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.tsx
deleted file mode 100644
index 1bf8a16f1ce19..0000000000000
--- a/x-pack/plugins/security_solution/public/dashboards/hooks/use_dashboard_app_link.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { createDashboardEditUrl, DASHBOARD_APP_ID } from '@kbn/dashboard-plugin/public';
-import { setStateToKbnUrl } from '@kbn/kibana-utils-plugin/public';
-import type { IUiSettingsClient } from '@kbn/core/public';
-import { useMemo } from 'react';
-import type { Filter, Query } from '@kbn/es-query';
-import { useNavigation } from '../../common/lib/kibana';
-
-const GLOBAL_STATE_STORAGE_KEY = '_g';
-
-export interface UseDashboardAppLinkProps {
- query?: Query;
- filters?: Filter[];
- timeRange: {
- from: string;
- to: string;
- fromStr?: string | undefined;
- toStr?: string | undefined;
- };
- uiSettings: IUiSettingsClient;
- savedObjectId: string | undefined;
-}
-
-export const useDashboardAppLink = ({
- query,
- filters,
- timeRange: { from, fromStr, to, toStr },
- uiSettings,
- savedObjectId,
-}: UseDashboardAppLinkProps) => {
- const { navigateTo, getAppUrl } = useNavigation();
- const useHash = uiSettings.get('state:storeInSessionStorage');
-
- let editDashboardUrl = useMemo(
- () =>
- getAppUrl({
- appId: DASHBOARD_APP_ID,
- path: `#${createDashboardEditUrl(savedObjectId)}`,
- }),
- [getAppUrl, savedObjectId]
- );
-
- editDashboardUrl = setStateToKbnUrl(
- GLOBAL_STATE_STORAGE_KEY,
- {
- time: { from: fromStr ?? from, to: toStr ?? to },
- filters,
- query,
- },
- { useHash, storeInHashQuery: true },
- editDashboardUrl
- );
-
- const editDashboardLinkProps = useMemo(
- () => ({
- onClick: (ev: React.MouseEvent) => {
- ev.preventDefault();
- navigateTo({ url: editDashboardUrl });
- },
- href: editDashboardUrl,
- }),
- [editDashboardUrl, navigateTo]
- );
- return editDashboardLinkProps;
-};