Skip to content

Commit

Permalink
[Security Solution] expanded flyout - right section - json tab implem…
Browse files Browse the repository at this point in the history
…entation (#152935)
  • Loading branch information
PhilippeOberti authored Mar 20, 2023
1 parent 54c4d8c commit b23e57b
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
DOCUMENT_DETAILS_FLYOUT_JSON_TAB,
DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB,
DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT,
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB,
DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT,
} from '../../../screens/document_expandable_flyout';
Expand All @@ -23,6 +22,7 @@ import {
openJsonTab,
openOverviewTab,
openTableTab,
scrollWithinDocumentDetailsExpandableFlyoutRightSection,
} from '../../../tasks/document_expandable_flyout';
import { cleanKibana } from '../../../tasks/common';
import { login, visit } from '../../../tasks/login';
Expand Down Expand Up @@ -67,12 +67,15 @@ describe.skip('Alert details expandable flyout right panel', { testIsolation: fa

it('should display tab content when switching tabs in the right section', () => {
openOverviewTab();
cy.get(DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT).should('be.visible');
// we shouldn't need to test anything here as it's covered with the new overview_tab file

openTableTab();
cy.get(DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT).should('be.visible');

openJsonTab();
// the json component is rendered within a dom element with overflow, so Cypress isn't finding it
// this next line is a hack that vertically scrolls down to ensure Cypress finds it
scrollWithinDocumentDetailsExpandableFlyoutRightSection(0, 6500);
cy.get(DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT).should('be.visible');
});
});
2 changes: 1 addition & 1 deletion x-pack/plugins/security_solution/cypress/helpers/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ export const getDataTestSubjectSelector = (dataTestSubjectValue: string) =>
* Helper function to generate selector by class
* @param className the value passed to class property of the DOM element
*/
export const getClassSelector = (className: string) => `[.${className}]`;
export const getClassSelector = (className: string) => `.${className}`;
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
} from '../../public/flyout/right/test_ids';
import {
JSON_TAB_CONTENT_TEST_ID,
OVERVIEW_TAB_CONTENT_TEST_ID,
TABLE_TAB_CONTENT_TEST_ID,
} from '../../public/flyout/right/tabs/test_ids';
import {
Expand All @@ -42,6 +41,8 @@ import {
} from '../../public/flyout/right/components/test_ids';
import { getDataTestSubjectSelector } from '../helpers/common';

/* Right section */

export const DOCUMENT_DETAILS_FLYOUT_HEADER_TITLE = getDataTestSubjectSelector(
FLYOUT_HEADER_TITLE_TEST_ID
);
Expand All @@ -55,14 +56,13 @@ export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB =
getDataTestSubjectSelector(OVERVIEW_TAB_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB = getDataTestSubjectSelector(TABLE_TAB_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB = getDataTestSubjectSelector(JSON_TAB_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_OVERVIEW_TAB_CONTENT = getDataTestSubjectSelector(
OVERVIEW_TAB_CONTENT_TEST_ID
);
export const DOCUMENT_DETAILS_FLYOUT_TABLE_TAB_CONTENT =
getDataTestSubjectSelector(TABLE_TAB_CONTENT_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_JSON_TAB_CONTENT =
getDataTestSubjectSelector(JSON_TAB_CONTENT_TEST_ID);

/* Left section */

export const DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB =
getDataTestSubjectSelector(VISUALIZE_TAB_TEST_ID);
export const DOCUMENT_DETAILS_FLYOUT_INSIGHTS_TAB =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DOCUMENT_DETAILS_FLYOUT_VISUALIZE_TAB_SESSION_VIEW_BUTTON,
} from '../screens/document_expandable_flyout';
import { EXPAND_ALERT_BTN } from '../screens/alerts';
import { getClassSelector } from '../helpers/common';

/**
* Find the first alert row in the alerts table then click on the expand icon button to open the flyout
Expand All @@ -39,6 +40,13 @@ export const expandDocumentDetailsExpandableFlyoutLeftSection = () =>
export const collapseDocumentDetailsExpandableFlyoutLeftSection = () =>
cy.get(DOCUMENT_DETAILS_FLYOUT_COLLAPSE_DETAILS_BUTTON).should('be.visible').click();

/**
* Scroll to x-y positions within the right section of the document details expandable flyout
* // TODO revisit this as it seems very fragile: the first element found is the timeline flyout, which isn't visible but still exist in the DOM
*/
export const scrollWithinDocumentDetailsExpandableFlyoutRightSection = (x: number, y: number) =>
cy.get(getClassSelector('euiFlyout')).last().scrollTo(x, y);

/**
* Open the Overview tab in the document details expandable flyout right section
*/
Expand Down
15 changes: 13 additions & 2 deletions x-pack/plugins/security_solution/public/flyout/right/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
import { css } from '@emotion/react';
import React, { createContext, useContext, useMemo } from 'react';
import type { SearchHit } from '@kbn/es-types';
Expand All @@ -27,6 +28,14 @@ export interface RightPanelContext {
* Name of the index used in the parent's page
*/
indexName: string;
/**
* An object containing fields by type
*/
browserFields: BrowserFields | null;
/**
* An array of field objects with category and value
*/
dataFormattedForFieldBrowser: TimelineEventsDetailsItem[] | null;
/**
* The actual raw document object
*/
Expand All @@ -51,7 +60,7 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid
? SourcererScopeName.detections
: SourcererScopeName.default;
const sourcererDataView = useSourcererDataView(sourcererScope);
const [loading, _, searchHit] = useTimelineEventsDetails({
const [loading, dataFormattedForFieldBrowser, searchHit] = useTimelineEventsDetails({
indexName: eventIndex,
eventId: id ?? '',
runtimeMappings: sourcererDataView.runtimeMappings,
Expand All @@ -64,10 +73,12 @@ export const RightPanelProvider = ({ id, indexName, children }: RightPanelProvid
? {
eventId: id,
indexName,
browserFields: sourcererDataView.browserFields,
dataFormattedForFieldBrowser,
searchHit: searchHit as SearchHit<object>,
}
: undefined,
[id, indexName, searchHit]
[id, indexName, sourcererDataView.browserFields, dataFormattedForFieldBrowser, searchHit]
);

if (loading) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* 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 React from 'react';
import type { Story } from '@storybook/react';
import { RightPanelContext } from '../context';
import { JsonTab } from './json_tab';

export default {
component: JsonTab,
title: 'Flyout/JsonTab',
};

export const Default: Story<void> = () => {
const contextValue = {
searchHit: {
some_field: 'some_value',
},
} as unknown as RightPanelContext;

return (
<RightPanelContext.Provider value={contextValue}>
<JsonTab />
</RightPanelContext.Provider>
);
};

export const Error: Story<void> = () => {
const contextValue = {
searchHit: null,
} as unknown as RightPanelContext;

return (
<RightPanelContext.Provider value={contextValue}>
<JsonTab />
</RightPanelContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 React from 'react';
import { render } from '@testing-library/react';
import { RightPanelContext } from '../context';
import { JsonTab } from './json_tab';
import { JSON_TAB_ERROR_TEST_ID, JSON_TAB_CONTENT_TEST_ID } from './test_ids';

describe('<JsonTab />', () => {
it('should render code block component', () => {
const contextValue = {
searchHit: {
some_field: 'some_value',
},
} as unknown as RightPanelContext;

const { getByTestId } = render(
<RightPanelContext.Provider value={contextValue}>
<JsonTab />
</RightPanelContext.Provider>
);

expect(getByTestId(JSON_TAB_CONTENT_TEST_ID)).toBeInTheDocument();
});

it('should render error message on invalid searchHit', () => {
const contextValue = {
searchHit: null,
} as unknown as RightPanelContext;

const { getByTestId, getByText } = render(
<RightPanelContext.Provider value={contextValue}>
<JsonTab />
</RightPanelContext.Provider>
);

expect(getByTestId(JSON_TAB_ERROR_TEST_ID)).toBeInTheDocument();
expect(getByText('Unable to display document information')).toBeInTheDocument();
expect(
getByText('There was an error displaying the document fields and values')
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@
* 2.0.
*/

import { EuiText } from '@elastic/eui';
import { EuiEmptyPrompt } from '@elastic/eui';
import type { FC } from 'react';
import React, { memo } from 'react';
import { JSON_TAB_CONTENT_TEST_ID } from './test_ids';
import { JSON_TAB_ERROR_TEST_ID } from './test_ids';
import { ERROR_MESSAGE, ERROR_TITLE } from './translations';
import { JsonView } from '../../../common/components/event_details/json_view';
import { useRightPanelContext } from '../context';

/**
* Json view displayed in the document details expandable flyout right section
*/
export const JsonTab: FC = memo(() => {
return <EuiText data-test-subj={JSON_TAB_CONTENT_TEST_ID}>{'Json tab'}</EuiText>;
const { searchHit } = useRightPanelContext();

if (!searchHit) {
return (
<EuiEmptyPrompt
iconType="error"
color="danger"
title={<h2>{ERROR_TITLE}</h2>}
body={<p>{ERROR_MESSAGE}</p>}
data-test-subj={JSON_TAB_ERROR_TEST_ID}
/>
);
}

return <JsonView rawEventData={searchHit} />;
});

JsonTab.displayName = 'JsonTab';
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
export const OVERVIEW_TAB_CONTENT_TEST_ID =
'securitySolutionDocumentDetailsFlyoutOverviewTabContent';
export const TABLE_TAB_CONTENT_TEST_ID = 'securitySolutionDocumentDetailsFlyoutTableTabContent';
export const JSON_TAB_CONTENT_TEST_ID = 'securitySolutionDocumentDetailsFlyoutJsonTabContent';
export const JSON_TAB_CONTENT_TEST_ID = 'jsonView';
export const JSON_TAB_ERROR_TEST_ID = 'securitySolutionDocumentDetailsFlyoutJsonTabError';
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const ERROR_TITLE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.errorTitle',
{
defaultMessage: 'Unable to display document information',
}
);

export const ERROR_MESSAGE = i18n.translate(
'xpack.securitySolution.flyout.documentDetails.errorMessage',
{ defaultMessage: 'There was an error displaying the document fields and values' }
);

0 comments on commit b23e57b

Please sign in to comment.