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

[Security Solution] expanded flyout - right section - json tab implementation #152935

Merged
merged 1 commit into from
Mar 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏾


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

Choose a reason for hiding this comment

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

Is it worth it to make sure some_value shows up in the component as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same reasoning I was having with the table, as I'm reusing the existing JsonView component which internally is a simple EuiCodeBlock and is also already tested, I was thinking it wouldn't be necessary...

});

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