mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Security Solution] Use new expandable flyout in alert preview (#167902)
## Summary
This PR updates the alert preview in Create rule -> Rule preview to use
the new expandable alert flyout:
- Switched timeline wrapper to be visible on create rule page. This
allows us to keep all the timeline navigation in the new expandable
alert flyout
- Disabled alert specific components, when flyout is open in create
rule:
- Alert status is not shown
- Rule summary preview is disabled
- Title link to rule details page is removed
- Exclude filter in/filter out hover actions in highlighted fields
- New placeholder text for investigation guide and response: we should
not show link to documentation when user is setting up a rule
With feature flag on:
a45e930e
-f1e8-4899-aef4-1aa0c3dc3330
**How to test**
- Add `xpack.securitySolution.enableExperimental:
['expandableFlyoutInCreateRuleEnabled' ]` to `kibana.yml.dev`
- Go to Rules page -> Detection rules (SIEM) => Create rule
- Pick a rule type and populate the query, click `Continue`
- On the right hand side, click `Refresh`, some alerts should appear in
the table
- Click expand on a row
### Checklist
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [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
---------
Co-authored-by: Nikita Indik <nikita.indik@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
192519d01f
commit
f5648d9585
31 changed files with 325 additions and 94 deletions
|
@ -70,8 +70,14 @@ export const allowedExperimentalValues = Object.freeze({
|
|||
* Enables top charts on Alerts Page
|
||||
*/
|
||||
alertsPageChartsEnabled: true,
|
||||
/**
|
||||
* Enables the alert type column in KPI visualizations on Alerts Page
|
||||
*/
|
||||
alertTypeEnabled: false,
|
||||
|
||||
/**
|
||||
* Enables expandable flyout in create rule page, alert preview
|
||||
*/
|
||||
expandableFlyoutInCreateRuleEnabled: false,
|
||||
/*
|
||||
* Enables new Set of filters on the Alerts page.
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@ import { getMappedNonEcsValue } from '../../../../timelines/components/timeline/
|
|||
import type { TimelineItem, TimelineNonEcsData } from '../../../../../common/search_strategy';
|
||||
import type { ColumnHeaderOptions, OnRowSelected } from '../../../../../common/types/timeline';
|
||||
import { TimelineId } from '../../../../../common/types';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features';
|
||||
|
||||
type Props = EuiDataGridCellValueElementProps & {
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
|
@ -73,6 +74,9 @@ const RowActionComponent = ({
|
|||
|
||||
const dispatch = useDispatch();
|
||||
const [isSecurityFlyoutEnabled] = useUiSetting$<boolean>(ENABLE_EXPANDABLE_FLYOUT_SETTING);
|
||||
const isExpandableFlyoutInCreateRuleEnabled = useIsExperimentalFeatureEnabled(
|
||||
'expandableFlyoutInCreateRuleEnabled'
|
||||
);
|
||||
|
||||
const columnValues = useMemo(
|
||||
() =>
|
||||
|
@ -89,6 +93,13 @@ const RowActionComponent = ({
|
|||
[columnHeaders, timelineNonEcsData]
|
||||
);
|
||||
|
||||
let showExpandableFlyout: boolean;
|
||||
if (tableId === TableId.rulePreview) {
|
||||
showExpandableFlyout = isSecurityFlyoutEnabled && isExpandableFlyoutInCreateRuleEnabled;
|
||||
} else {
|
||||
showExpandableFlyout = isSecurityFlyoutEnabled;
|
||||
}
|
||||
|
||||
const handleOnEventDetailPanelOpened = useCallback(() => {
|
||||
const updatedExpandedDetail: ExpandedDetailType = {
|
||||
panelView: 'eventDetail',
|
||||
|
@ -98,9 +109,7 @@ const RowActionComponent = ({
|
|||
},
|
||||
};
|
||||
|
||||
// TODO remove when https://github.com/elastic/security-team/issues/7760 is merged
|
||||
// excluding rule preview page as some sections in new flyout are not applicable when user is creating a new rule
|
||||
if (isSecurityFlyoutEnabled && tableId !== TableId.rulePreview) {
|
||||
if (showExpandableFlyout) {
|
||||
openFlyout({
|
||||
right: {
|
||||
id: DocumentDetailsRightPanelKey,
|
||||
|
@ -133,7 +142,7 @@ const RowActionComponent = ({
|
|||
})
|
||||
);
|
||||
}
|
||||
}, [dispatch, eventId, indexName, isSecurityFlyoutEnabled, openFlyout, tabType, tableId]);
|
||||
}, [dispatch, eventId, indexName, openFlyout, tabType, tableId, showExpandableFlyout]);
|
||||
|
||||
const Action = controlColumn.rowCellRender;
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ describe('use show timeline', () => {
|
|||
});
|
||||
|
||||
it('hides timeline for blacklist routes', async () => {
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules/create' });
|
||||
mockUseLocation.mockReturnValueOnce({ pathname: '/rules/add_rules' });
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(() => useShowTimeline());
|
||||
await waitForNextUpdate();
|
||||
|
|
|
@ -18,6 +18,7 @@ jest.mock('../../shared/hooks/use_investigation_guide');
|
|||
|
||||
const NO_DATA_TEXT =
|
||||
"There's no investigation guide for this rule. Edit the rule's settingsExternal link(opens in a new tab or window) to add one.";
|
||||
const PREVIEW_MESSAGE = 'Investigation guide is not available in alert preview.';
|
||||
|
||||
const renderInvestigationGuide = (context: LeftPanelContext = mockContextValue) => (
|
||||
<TestProviders>
|
||||
|
@ -76,4 +77,15 @@ describe('<InvestigationGuide />', () => {
|
|||
const { getByTestId } = render(renderInvestigationGuide());
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_TEXT);
|
||||
});
|
||||
|
||||
it('should render preview message when flyout is in preview', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: true,
|
||||
});
|
||||
const { getByTestId } = render(
|
||||
renderInvestigationGuide({ ...mockContextValue, isPreview: true })
|
||||
);
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ import { FlyoutLoading } from '../../../shared/components/flyout_loading';
|
|||
* Renders a message saying the guide hasn't been set up or the full investigation guide.
|
||||
*/
|
||||
export const InvestigationGuide: React.FC = () => {
|
||||
const { dataFormattedForFieldBrowser } = useLeftPanelContext();
|
||||
const { dataFormattedForFieldBrowser, isPreview } = useLeftPanelContext();
|
||||
|
||||
const { loading, error, basicAlertData, ruleNote } = useInvestigationGuide({
|
||||
dataFormattedForFieldBrowser,
|
||||
|
@ -26,7 +26,12 @@ export const InvestigationGuide: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div data-test-subj={INVESTIGATION_GUIDE_TEST_ID}>
|
||||
{loading ? (
|
||||
{isPreview ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.left.investigation.previewMessage"
|
||||
defaultMessage="Investigation guide is not available in alert preview."
|
||||
/>
|
||||
) : loading ? (
|
||||
<FlyoutLoading data-test-subj={INVESTIGATION_GUIDE_LOADING_TEST_ID} />
|
||||
) : !error && basicAlertData.ruleId && ruleNote ? (
|
||||
<InvestigationGuideView
|
||||
|
|
|
@ -61,6 +61,7 @@ jest.mock('../../../../common/lib/kibana', () => {
|
|||
|
||||
const NO_DATA_MESSAGE =
|
||||
"There are no response actions defined for this event. To add some, edit the rule's settings and set up response actionsExternal link(opens in a new tab or window).";
|
||||
const PREVIEW_MESSAGE = 'Response is not available in alert preview.';
|
||||
|
||||
const defaultContextValue = {
|
||||
dataAsNestedObject: {
|
||||
|
@ -139,4 +140,10 @@ describe('<ResponseDetails />', () => {
|
|||
|
||||
expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
|
||||
});
|
||||
|
||||
it('should render preview message if flyout is in preview', () => {
|
||||
const wrapper = renderResponseDetails({ ...defaultContextValue, isPreview: true });
|
||||
expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toBeInTheDocument();
|
||||
expect(wrapper.getByTestId(RESPONSE_DETAILS_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -24,7 +24,7 @@ const ExtendedFlyoutWrapper = styled.div`
|
|||
* Automated response actions results, displayed in the document details expandable flyout left section under the Insights tab, Response tab
|
||||
*/
|
||||
export const ResponseDetails: React.FC = () => {
|
||||
const { searchHit, dataAsNestedObject } = useLeftPanelContext();
|
||||
const { searchHit, dataAsNestedObject, isPreview } = useLeftPanelContext();
|
||||
const endpointResponseActionsEnabled = useIsExperimentalFeatureEnabled(
|
||||
'endpointResponseActionsEnabled'
|
||||
);
|
||||
|
@ -40,19 +40,28 @@ export const ResponseDetails: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div data-test-subj={RESPONSE_DETAILS_TEST_ID}>
|
||||
<EuiTitle size="xxxs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.left.response.responseTitle"
|
||||
defaultMessage="Responses"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{isPreview ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.left.response.previewMessage"
|
||||
defaultMessage="Response is not available in alert preview."
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiTitle size="xxxs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.left.response.responseTitle"
|
||||
defaultMessage="Responses"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
|
||||
<ExtendedFlyoutWrapper>
|
||||
{endpointResponseActionsEnabled ? responseActionsView?.content : osqueryView?.content}
|
||||
</ExtendedFlyoutWrapper>
|
||||
<ExtendedFlyoutWrapper>
|
||||
{endpointResponseActionsEnabled ? responseActionsView?.content : osqueryView?.content}
|
||||
</ExtendedFlyoutWrapper>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import React, { createContext, memo, useContext, useMemo } from 'react';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
import { useEventDetails } from '../shared/hooks/use_event_details';
|
||||
import { FlyoutError } from '../../shared/components/flyout_error';
|
||||
import { FlyoutLoading } from '../../shared/components/flyout_loading';
|
||||
|
@ -54,6 +55,10 @@ export interface LeftPanelContext {
|
|||
* Retrieves searchHit values for the provided field
|
||||
*/
|
||||
getFieldsData: GetFieldsData;
|
||||
/**
|
||||
* Boolean to indicate whether it is a preview flyout
|
||||
*/
|
||||
isPreview: boolean;
|
||||
}
|
||||
|
||||
export const LeftPanelContext = createContext<LeftPanelContext | undefined>(undefined);
|
||||
|
@ -97,6 +102,7 @@ export const LeftPanelProvider = memo(
|
|||
searchHit,
|
||||
investigationFields: maybeRule?.investigation_fields?.field_names ?? [],
|
||||
getFieldsData,
|
||||
isPreview: scopeId === TableId.rulePreview,
|
||||
}
|
||||
: undefined,
|
||||
[
|
||||
|
|
|
@ -25,4 +25,5 @@ export const mockContextValue: LeftPanelContext = {
|
|||
searchHit: mockSearchHit,
|
||||
dataAsNestedObject: mockDataAsNestedObject,
|
||||
investigationFields: [],
|
||||
isPreview: false,
|
||||
};
|
||||
|
|
|
@ -53,10 +53,10 @@ const panelContextValue = {
|
|||
dataFormattedForFieldBrowser: mockDataFormattedForFieldBrowser,
|
||||
};
|
||||
|
||||
const renderAnalyzerPreview = () =>
|
||||
const renderAnalyzerPreview = (context = panelContextValue) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<RightPanelContext.Provider value={context}>
|
||||
<AnalyzerPreviewContainer />
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
|
@ -117,7 +117,7 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
).toHaveTextContent(NO_ANALYZER_MESSAGE);
|
||||
});
|
||||
|
||||
it('should navigate to left section Visualize tab when clicking on title', () => {
|
||||
it('should navigate to analyzer in timeline when clicking on title', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
|
@ -136,4 +136,24 @@ describe('AnalyzerPreviewContainer', () => {
|
|||
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID)).click();
|
||||
expect(investigateInTimelineAlertClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not navigate to analyzer when in preview and clicking on title', () => {
|
||||
(isInvestigateInResolverActionEnabled as jest.Mock).mockReturnValue(true);
|
||||
(useAlertPrevalenceFromProcessTree as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
error: false,
|
||||
alertIds: ['alertid'],
|
||||
statsNodes: mock.mockStatsNodes,
|
||||
});
|
||||
(useInvestigateInTimeline as jest.Mock).mockReturnValue({
|
||||
investigateInTimelineAlertClick: jest.fn(),
|
||||
});
|
||||
|
||||
const { queryByTestId } = renderAnalyzerPreview({ ...panelContextValue, isPreview: true });
|
||||
expect(
|
||||
queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(ANALYZER_PREVIEW_TEST_ID))
|
||||
).not.toBeInTheDocument();
|
||||
const { investigateInTimelineAlertClick } = useInvestigateInTimeline({});
|
||||
expect(investigateInTimelineAlertClick).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ const timelineId = 'timeline-1';
|
|||
* Analyzer preview under Overview, Visualizations. It shows a tree representation of analyzer.
|
||||
*/
|
||||
export const AnalyzerPreviewContainer: React.FC = () => {
|
||||
const { dataAsNestedObject } = useRightPanelContext();
|
||||
const { dataAsNestedObject, isPreview } = useRightPanelContext();
|
||||
|
||||
// decide whether to show the analyzer preview or not
|
||||
const isEnabled = isInvestigateInResolverActionEnabled(dataAsNestedObject);
|
||||
|
@ -64,17 +64,18 @@ export const AnalyzerPreviewContainer: React.FC = () => {
|
|||
/>
|
||||
),
|
||||
iconType: 'timeline',
|
||||
...(isEnabled && {
|
||||
link: {
|
||||
callback: goToAnalyzerTab,
|
||||
tooltip: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.visualizations.analyzerPreview.analyzerPreviewTooltip"
|
||||
defaultMessage="Show analyzer graph"
|
||||
/>
|
||||
),
|
||||
},
|
||||
}),
|
||||
...(isEnabled &&
|
||||
!isPreview && {
|
||||
link: {
|
||||
callback: goToAnalyzerTab,
|
||||
tooltip: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.visualizations.analyzerPreview.analyzerPreviewTooltip"
|
||||
defaultMessage="Show analyzer graph"
|
||||
/>
|
||||
),
|
||||
},
|
||||
}),
|
||||
}}
|
||||
data-test-subj={ANALYZER_PREVIEW_TEST_ID}
|
||||
>
|
||||
|
|
|
@ -111,6 +111,15 @@ describe('<Description />', () => {
|
|||
expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toHaveAttribute('disabled');
|
||||
});
|
||||
|
||||
it('should render rule preview button as disabled if flyout is in preview', () => {
|
||||
const { getByTestId } = renderDescription({
|
||||
...panelContextValue([{ ...ruleUuid, values: [] }, ruleName, ruleDescription]),
|
||||
isPreview: true,
|
||||
});
|
||||
expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RULE_SUMMARY_BUTTON_TEST_ID)).toHaveAttribute('disabled');
|
||||
});
|
||||
|
||||
it('should open preview panel when clicking on button', () => {
|
||||
const panelContext = panelContextValue([ruleUuid, ruleDescription, ruleName]);
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ import {
|
|||
* If the document is an alert we show the rule description. If the document is of another type, we show -.
|
||||
*/
|
||||
export const Description: FC = () => {
|
||||
const { dataFormattedForFieldBrowser, scopeId, eventId, indexName } = useRightPanelContext();
|
||||
const { dataFormattedForFieldBrowser, scopeId, eventId, indexName, isPreview } =
|
||||
useRightPanelContext();
|
||||
const { isAlert, ruleDescription, ruleName, ruleId } = useBasicDataFromDetailsData(
|
||||
dataFormattedForFieldBrowser
|
||||
);
|
||||
|
@ -75,7 +76,7 @@ export const Description: FC = () => {
|
|||
defaultMessage: 'Show rule summary',
|
||||
}
|
||||
)}
|
||||
disabled={isEmpty(ruleName) || isEmpty(ruleId)}
|
||||
disabled={isEmpty(ruleName) || isEmpty(ruleId) || isPreview}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.about.description.ruleSummaryButtonLabel"
|
||||
|
@ -84,7 +85,7 @@ export const Description: FC = () => {
|
|||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
),
|
||||
[ruleName, openRulePreview, ruleId]
|
||||
[ruleName, openRulePreview, ruleId, isPreview]
|
||||
);
|
||||
|
||||
const alertRuleDescription =
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
RISK_SCORE_VALUE_TEST_ID,
|
||||
SEVERITY_VALUE_TEST_ID,
|
||||
FLYOUT_HEADER_TITLE_TEST_ID,
|
||||
STATUS_BUTTON_TEST_ID,
|
||||
} from './test_ids';
|
||||
import { HeaderTitle } from './header_title';
|
||||
import moment from 'moment-timezone';
|
||||
|
@ -56,6 +57,7 @@ describe('<HeaderTitle />', () => {
|
|||
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(RISK_SCORE_VALUE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(SEVERITY_VALUE_TEST_ID)).toBeInTheDocument();
|
||||
expect(getByTestId(STATUS_BUTTON_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render rule name in the title if document is an alert', () => {
|
||||
|
@ -82,4 +84,32 @@ describe('<HeaderTitle />', () => {
|
|||
|
||||
expect(getByTestId(FLYOUT_HEADER_TITLE_TEST_ID)).toHaveTextContent('Event details');
|
||||
});
|
||||
|
||||
it('should not render document status if document is not an alert', () => {
|
||||
const contextValue = {
|
||||
...mockContextValue,
|
||||
dataFormattedForFieldBrowser: [
|
||||
{
|
||||
category: 'kibana',
|
||||
field: 'kibana.alert.rule.name',
|
||||
values: [],
|
||||
originalValue: [],
|
||||
isObjectArray: false,
|
||||
},
|
||||
],
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { queryByTestId } = renderHeader(contextValue);
|
||||
expect(queryByTestId(STATUS_BUTTON_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render document status if flyout is open in preview', () => {
|
||||
const contextValue = {
|
||||
...mockContextValue,
|
||||
isPreview: true,
|
||||
} as unknown as RightPanelContext;
|
||||
|
||||
const { queryByTestId } = renderHeader(contextValue);
|
||||
expect(queryByTestId(STATUS_BUTTON_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -25,33 +25,40 @@ import { FlyoutTitle } from '../../../shared/components/flyout_title';
|
|||
* Document details flyout right section header
|
||||
*/
|
||||
export const HeaderTitle: FC = memo(() => {
|
||||
const { dataFormattedForFieldBrowser, eventId, scopeId } = useRightPanelContext();
|
||||
const { dataFormattedForFieldBrowser, eventId, scopeId, isPreview } = useRightPanelContext();
|
||||
const { isAlert, ruleName, timestamp, ruleId } = useBasicDataFromDetailsData(
|
||||
dataFormattedForFieldBrowser
|
||||
);
|
||||
|
||||
const ruleTitle = useMemo(
|
||||
() => (
|
||||
<RenderRuleName
|
||||
contextId={scopeId}
|
||||
eventId={eventId}
|
||||
fieldName={SIGNAL_RULE_NAME_FIELD_NAME}
|
||||
fieldType={'string'}
|
||||
isAggregatable={false}
|
||||
isDraggable={false}
|
||||
linkValue={ruleId}
|
||||
value={ruleName}
|
||||
openInNewTab
|
||||
>
|
||||
() =>
|
||||
isPreview ? (
|
||||
<FlyoutTitle
|
||||
title={ruleName}
|
||||
iconType={'warning'}
|
||||
isLink
|
||||
data-test-subj={FLYOUT_HEADER_TITLE_TEST_ID}
|
||||
/>
|
||||
</RenderRuleName>
|
||||
),
|
||||
[ruleName, ruleId, eventId, scopeId]
|
||||
) : (
|
||||
<RenderRuleName
|
||||
contextId={scopeId}
|
||||
eventId={eventId}
|
||||
fieldName={SIGNAL_RULE_NAME_FIELD_NAME}
|
||||
fieldType={'string'}
|
||||
isAggregatable={false}
|
||||
isDraggable={false}
|
||||
linkValue={ruleId}
|
||||
value={ruleName}
|
||||
openInNewTab
|
||||
>
|
||||
<FlyoutTitle
|
||||
title={ruleName}
|
||||
iconType={'warning'}
|
||||
isLink
|
||||
data-test-subj={FLYOUT_HEADER_TITLE_TEST_ID}
|
||||
/>
|
||||
</RenderRuleName>
|
||||
),
|
||||
[ruleName, ruleId, eventId, scopeId, isPreview]
|
||||
);
|
||||
|
||||
const eventTitle = (
|
||||
|
@ -76,9 +83,11 @@ export const HeaderTitle: FC = memo(() => {
|
|||
</div>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup direction="row" gutterSize="m" responsive={false}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<DocumentStatus />
|
||||
</EuiFlexItem>
|
||||
{isAlert && !isPreview && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<DocumentStatus />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
<EuiFlexItem grow={false}>
|
||||
<RiskScore />
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -15,6 +15,7 @@ import { convertHighlightedFieldsToTableRow } from '../../shared/utils/highlight
|
|||
import { useRuleWithFallback } from '../../../../detection_engine/rule_management/logic/use_rule_with_fallback';
|
||||
import { useBasicDataFromDetailsData } from '../../../../timelines/components/side_panel/event_details/helpers';
|
||||
import { HighlightedFieldsCell } from './highlighted_fields_cell';
|
||||
import { SecurityCellActionType } from '../../../../actions/constants';
|
||||
import {
|
||||
CellActionsMode,
|
||||
SecurityCellActions,
|
||||
|
@ -42,6 +43,10 @@ export interface HighlightedFieldsTableRow {
|
|||
* Maintain backwards compatibility // TODO remove when possible
|
||||
*/
|
||||
scopeId: string;
|
||||
/**
|
||||
* Boolean to indicate this field is shown in a preview
|
||||
*/
|
||||
isPreview: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -71,6 +76,7 @@ const columns: Array<EuiBasicTableColumn<HighlightedFieldsTableRow>> = [
|
|||
field: string;
|
||||
values: string[] | null | undefined;
|
||||
scopeId: string;
|
||||
isPreview: boolean;
|
||||
}) => (
|
||||
<SecurityCellActions
|
||||
data={{
|
||||
|
@ -82,6 +88,11 @@ const columns: Array<EuiBasicTableColumn<HighlightedFieldsTableRow>> = [
|
|||
visibleCellActions={6}
|
||||
sourcererScopeId={getSourcererScopeId(description.scopeId)}
|
||||
metadata={{ scopeId: description.scopeId }}
|
||||
disabledActionTypes={
|
||||
description.isPreview
|
||||
? [SecurityCellActionType.FILTER, SecurityCellActionType.TOGGLE_COLUMN]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<HighlightedFieldsCell values={description.values} field={description.field} />
|
||||
</SecurityCellActions>
|
||||
|
@ -93,7 +104,7 @@ const columns: Array<EuiBasicTableColumn<HighlightedFieldsTableRow>> = [
|
|||
* Component that displays the highlighted fields in the right panel under the Investigation section.
|
||||
*/
|
||||
export const HighlightedFields: FC = () => {
|
||||
const { dataFormattedForFieldBrowser, scopeId } = useRightPanelContext();
|
||||
const { dataFormattedForFieldBrowser, scopeId, isPreview } = useRightPanelContext();
|
||||
const { ruleId } = useBasicDataFromDetailsData(dataFormattedForFieldBrowser);
|
||||
const { loading, rule: maybeRule } = useRuleWithFallback(ruleId);
|
||||
|
||||
|
@ -102,8 +113,8 @@ export const HighlightedFields: FC = () => {
|
|||
investigationFields: maybeRule?.investigation_fields?.field_names ?? [],
|
||||
});
|
||||
const items = useMemo(
|
||||
() => convertHighlightedFieldsToTableRow(highlightedFields, scopeId),
|
||||
[highlightedFields, scopeId]
|
||||
() => convertHighlightedFieldsToTableRow(highlightedFields, scopeId, isPreview),
|
||||
[highlightedFields, scopeId, isPreview]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -24,6 +24,7 @@ import { LeftPanelInvestigationTab, DocumentDetailsLeftPanelKey } from '../../le
|
|||
jest.mock('../../shared/hooks/use_investigation_guide');
|
||||
|
||||
const NO_DATA_MESSAGE = 'Investigation guideThere’s no investigation guide for this rule.';
|
||||
const PREVIEW_MESSAGE = 'Investigation guide is not available in alert preview.';
|
||||
|
||||
const renderInvestigationGuide = () =>
|
||||
render(
|
||||
|
@ -97,6 +98,21 @@ describe('<InvestigationGuide />', () => {
|
|||
expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(NO_DATA_MESSAGE);
|
||||
});
|
||||
|
||||
it('should render preview message when flyout is in preview', () => {
|
||||
const { queryByTestId, getByTestId } = render(
|
||||
<IntlProvider locale="en">
|
||||
<ExpandableFlyoutContext.Provider value={mockFlyoutContextValue}>
|
||||
<RightPanelContext.Provider value={{ ...mockContextValue, isPreview: true }}>
|
||||
<InvestigationGuide />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
|
||||
expect(queryByTestId(INVESTIGATION_GUIDE_BUTTON_TEST_ID)).not.toBeInTheDocument();
|
||||
expect(getByTestId(INVESTIGATION_GUIDE_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE);
|
||||
});
|
||||
|
||||
it('should navigate to investigation guide when clicking on button', () => {
|
||||
(useInvestigationGuide as jest.Mock).mockReturnValue({
|
||||
loading: false,
|
||||
|
|
|
@ -24,7 +24,8 @@ import {
|
|||
*/
|
||||
export const InvestigationGuide: React.FC = () => {
|
||||
const { openLeftPanel } = useExpandableFlyoutContext();
|
||||
const { eventId, indexName, scopeId, dataFormattedForFieldBrowser } = useRightPanelContext();
|
||||
const { eventId, indexName, scopeId, dataFormattedForFieldBrowser, isPreview } =
|
||||
useRightPanelContext();
|
||||
|
||||
const { loading, error, basicAlertData, ruleNote } = useInvestigationGuide({
|
||||
dataFormattedForFieldBrowser,
|
||||
|
@ -56,7 +57,12 @@ export const InvestigationGuide: React.FC = () => {
|
|||
</h5>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
{loading ? (
|
||||
{isPreview ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.investigation.investigationGuide.previewMessage"
|
||||
defaultMessage="Investigation guide is not available in alert preview."
|
||||
/>
|
||||
) : loading ? (
|
||||
<EuiSkeletonText
|
||||
data-test-subj={INVESTIGATION_GUIDE_LOADING_TEST_ID}
|
||||
contentAriaLabel={i18n.translate(
|
||||
|
|
|
@ -13,6 +13,8 @@ import { RightPanelContext } from '../context';
|
|||
import { ExpandableFlyoutContext } from '@kbn/expandable-flyout/src/context';
|
||||
import { ResponseSection } from './response_section';
|
||||
|
||||
const PREVIEW_MESSAGE = 'Response is not available in alert preview.';
|
||||
|
||||
const flyoutContextValue = {} as unknown as ExpandableFlyoutContext;
|
||||
const panelContextValue = {} as unknown as RightPanelContext;
|
||||
|
||||
|
@ -47,4 +49,18 @@ describe('<ResponseSection />', () => {
|
|||
getByTestId(RESPONSE_SECTION_HEADER_TEST_ID).click();
|
||||
expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render preview message if flyout is in preview', () => {
|
||||
const { getByTestId } = render(
|
||||
<IntlProvider locale="en">
|
||||
<ExpandableFlyoutContext.Provider value={flyoutContextValue}>
|
||||
<RightPanelContext.Provider value={{ ...panelContextValue, isPreview: true }}>
|
||||
<ResponseSection />
|
||||
</RightPanelContext.Provider>
|
||||
</ExpandableFlyoutContext.Provider>
|
||||
</IntlProvider>
|
||||
);
|
||||
getByTestId(RESPONSE_SECTION_HEADER_TEST_ID).click();
|
||||
expect(getByTestId(RESPONSE_SECTION_CONTENT_TEST_ID)).toHaveTextContent(PREVIEW_MESSAGE);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ResponseButton } from './response_button';
|
||||
import { ExpandableSection } from './expandable_section';
|
||||
import { useRightPanelContext } from '../context';
|
||||
import { RESPONSE_SECTION_TEST_ID } from './test_ids';
|
||||
export interface ResponseSectionProps {
|
||||
/**
|
||||
|
@ -22,6 +23,7 @@ export interface ResponseSectionProps {
|
|||
* Most bottom section of the overview tab. It contains a summary of the response tab.
|
||||
*/
|
||||
export const ResponseSection: VFC<ResponseSectionProps> = ({ expanded = false }) => {
|
||||
const { isPreview } = useRightPanelContext();
|
||||
return (
|
||||
<ExpandableSection
|
||||
expanded={expanded}
|
||||
|
@ -33,7 +35,14 @@ export const ResponseSection: VFC<ResponseSectionProps> = ({ expanded = false })
|
|||
}
|
||||
data-test-subj={RESPONSE_SECTION_TEST_ID}
|
||||
>
|
||||
<ResponseButton />
|
||||
{isPreview ? (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.response.previewMessage"
|
||||
defaultMessage="Response is not available in alert preview."
|
||||
/>
|
||||
) : (
|
||||
<ResponseButton />
|
||||
)}
|
||||
</ExpandableSection>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -37,7 +37,7 @@ const ValueContainer: FC<{ text?: ReactElement }> = ({ text, children }) => (
|
|||
* Renders session preview under Visualizations section in the flyout right EuiPanel
|
||||
*/
|
||||
export const SessionPreview: FC = () => {
|
||||
const { eventId, scopeId } = useRightPanelContext();
|
||||
const { eventId, scopeId, isPreview } = useRightPanelContext();
|
||||
|
||||
const { processName, userName, startAt, ruleName, ruleId, workdir, command } = useProcessData();
|
||||
const { euiTheme } = useEuiTheme();
|
||||
|
@ -100,13 +100,13 @@ export const SessionPreview: FC = () => {
|
|||
fieldType={'string'}
|
||||
isAggregatable={false}
|
||||
isDraggable={false}
|
||||
linkValue={ruleId}
|
||||
linkValue={!isPreview ? ruleId : null}
|
||||
value={ruleName}
|
||||
/>
|
||||
</ValueContainer>
|
||||
)
|
||||
);
|
||||
}, [ruleName, ruleId, scopeId, eventId]);
|
||||
}, [ruleName, ruleId, scopeId, eventId, isPreview]);
|
||||
|
||||
const commandFragment = useMemo(() => {
|
||||
return (
|
||||
|
|
|
@ -40,10 +40,10 @@ const sessionViewConfig = {
|
|||
sessionStartTime: 'sessionStartTime',
|
||||
};
|
||||
|
||||
const renderSessionPreview = () =>
|
||||
const renderSessionPreview = (context = panelContextValue) =>
|
||||
render(
|
||||
<TestProviders>
|
||||
<RightPanelContext.Provider value={panelContextValue}>
|
||||
<RightPanelContext.Provider value={context}>
|
||||
<SessionPreviewContainer />
|
||||
</RightPanelContext.Provider>
|
||||
</TestProviders>
|
||||
|
@ -121,4 +121,31 @@ describe('SessionPreviewContainer', () => {
|
|||
).not.toHaveTextContent(NO_DATA_MESSAGE);
|
||||
expect(queryByTestId(SESSION_PREVIEW_TEST_ID)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render link to session viewer if flyout is open in preview', () => {
|
||||
(useSessionPreview as jest.Mock).mockReturnValue(sessionViewConfig);
|
||||
(useLicense as jest.Mock).mockReturnValue({ isEnterprise: () => true });
|
||||
|
||||
const { getByTestId, queryByTestId } = renderSessionPreview({
|
||||
...panelContextValue,
|
||||
isPreview: true,
|
||||
});
|
||||
|
||||
expect(getByTestId(SESSION_PREVIEW_TEST_ID)).toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID))
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(SESSION_PREVIEW_TEST_ID))
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(EXPANDABLE_PANEL_CONTENT_TEST_ID(SESSION_PREVIEW_TEST_ID))
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
getByTestId(EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(SESSION_PREVIEW_TEST_ID))
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(SESSION_PREVIEW_TEST_ID))
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ const timelineId = 'timeline-1';
|
|||
* Checks if the SessionView component is available, if so render it or else render an error message
|
||||
*/
|
||||
export const SessionPreviewContainer: FC = () => {
|
||||
const { dataAsNestedObject, getFieldsData } = useRightPanelContext();
|
||||
const { dataAsNestedObject, getFieldsData, isPreview } = useRightPanelContext();
|
||||
|
||||
// decide whether to show the session view or not
|
||||
const sessionViewConfig = useSessionPreview({ getFieldsData });
|
||||
|
@ -122,17 +122,18 @@ export const SessionPreviewContainer: FC = () => {
|
|||
/>
|
||||
),
|
||||
iconType: 'timeline',
|
||||
...(isEnabled && {
|
||||
link: {
|
||||
callback: goToSessionViewTab,
|
||||
tooltip: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.visualizations.sessionPreview.sessionPreviewTooltip"
|
||||
defaultMessage="Show session viewer"
|
||||
/>
|
||||
),
|
||||
},
|
||||
}),
|
||||
...(isEnabled &&
|
||||
!isPreview && {
|
||||
link: {
|
||||
callback: goToSessionViewTab,
|
||||
tooltip: (
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.flyout.right.visualizations.sessionPreview.sessionPreviewTooltip"
|
||||
defaultMessage="Show session viewer"
|
||||
/>
|
||||
),
|
||||
},
|
||||
}),
|
||||
}}
|
||||
data-test-subj={SESSION_PREVIEW_TEST_ID}
|
||||
>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import type { BrowserFields, TimelineEventsDetailsItem } from '@kbn/timelines-plugin/common';
|
||||
import React, { createContext, memo, useContext, useMemo } from 'react';
|
||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||
import { TableId } from '@kbn/securitysolution-data-table';
|
||||
|
||||
import { useEventDetails } from '../shared/hooks/use_event_details';
|
||||
import { FlyoutError } from '../../shared/components/flyout_error';
|
||||
|
@ -59,6 +60,10 @@ export interface RightPanelContext {
|
|||
* Retrieves searchHit values for the provided field
|
||||
*/
|
||||
getFieldsData: GetFieldsData;
|
||||
/**
|
||||
* Boolean to indicate whether it is a preview flyout
|
||||
*/
|
||||
isPreview: boolean;
|
||||
}
|
||||
|
||||
export const RightPanelContext = createContext<RightPanelContext | undefined>(undefined);
|
||||
|
@ -104,6 +109,7 @@ export const RightPanelProvider = memo(
|
|||
investigationFields: maybeRule?.investigation_fields?.field_names ?? [],
|
||||
refetchFlyoutData,
|
||||
getFieldsData,
|
||||
isPreview: scopeId === TableId.rulePreview,
|
||||
}
|
||||
: undefined,
|
||||
[
|
||||
|
|
|
@ -15,10 +15,17 @@ import { useHostIsolationTools } from '../../../timelines/components/side_panel/
|
|||
import { DEFAULT_DARK_MODE } from '../../../../common/constants';
|
||||
import { useUiSetting } from '../../../common/lib/kibana';
|
||||
|
||||
interface PanelFooterProps {
|
||||
/**
|
||||
* Boolean that indicates whether flyout is in preview and action should be hidden
|
||||
*/
|
||||
isPreview: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const PanelFooter: FC = () => {
|
||||
export const PanelFooter: FC<PanelFooterProps> = ({ isPreview }) => {
|
||||
const { closeFlyout, openRightPanel } = useExpandableFlyoutContext();
|
||||
const {
|
||||
eventId,
|
||||
|
@ -48,7 +55,7 @@ export const PanelFooter: FC = () => {
|
|||
[eventId, indexName, openRightPanel, scopeId, showHostIsolationPanel]
|
||||
);
|
||||
|
||||
return (
|
||||
return !isPreview ? (
|
||||
<EuiPanel
|
||||
hasShadow={false}
|
||||
borderRadius="none"
|
||||
|
@ -68,5 +75,5 @@ export const PanelFooter: FC = () => {
|
|||
refetchFlyoutData={refetchFlyoutData}
|
||||
/>
|
||||
</EuiPanel>
|
||||
);
|
||||
) : null;
|
||||
};
|
||||
|
|
|
@ -37,7 +37,7 @@ export interface RightPanelProps extends FlyoutPanelProps {
|
|||
*/
|
||||
export const RightPanel: FC<Partial<RightPanelProps>> = memo(({ path }) => {
|
||||
const { openRightPanel } = useExpandableFlyoutContext();
|
||||
const { eventId, getFieldsData, indexName, scopeId } = useRightPanelContext();
|
||||
const { eventId, getFieldsData, indexName, scopeId, isPreview } = useRightPanelContext();
|
||||
|
||||
// for 8.10, we only render the flyout in its expandable mode if the document viewed is of type signal
|
||||
const documentIsSignal = getField(getFieldsData('event.kind')) === EventKind.signal;
|
||||
|
@ -72,7 +72,7 @@ export const RightPanel: FC<Partial<RightPanelProps>> = memo(({ path }) => {
|
|||
setSelectedTabId={setSelectedTabId}
|
||||
/>
|
||||
<PanelContent tabs={tabsDisplayed} selectedTabId={selectedTabId} />
|
||||
<PanelFooter />
|
||||
<PanelFooter isPreview={isPreview} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -26,4 +26,5 @@ export const mockContextValue: RightPanelContext = {
|
|||
searchHit: mockSearchHit,
|
||||
investigationFields: [],
|
||||
refetchFlyoutData: jest.fn(),
|
||||
isPreview: false,
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ const FLYOUT_FOOTER_HEIGHT = 72;
|
|||
* Json view displayed in the document details expandable flyout right section
|
||||
*/
|
||||
export const JsonTab: FC = memo(() => {
|
||||
const { searchHit } = useRightPanelContext();
|
||||
const { searchHit, isPreview } = useRightPanelContext();
|
||||
const jsonValue = JSON.stringify(searchHit, null, 2);
|
||||
|
||||
const flexGroupElement = useRef<HTMLDivElement>(null);
|
||||
|
@ -31,19 +31,20 @@ export const JsonTab: FC = memo(() => {
|
|||
|
||||
useEffect(() => {
|
||||
const topPosition = flexGroupElement?.current?.getBoundingClientRect().top || 0;
|
||||
const footerOffset = isPreview ? 0 : FLYOUT_FOOTER_HEIGHT;
|
||||
const height =
|
||||
window.innerHeight -
|
||||
topPosition -
|
||||
COPY_TO_CLIPBOARD_BUTTON_HEIGHT -
|
||||
FLYOUT_BODY_PADDING -
|
||||
FLYOUT_FOOTER_HEIGHT;
|
||||
footerOffset;
|
||||
|
||||
if (height === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setEditorHeight(height);
|
||||
}, [setEditorHeight]);
|
||||
}, [setEditorHeight, isPreview]);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from './highlighted_fields_helpers';
|
||||
|
||||
const scopeId = 'scopeId';
|
||||
const isPreview = false;
|
||||
|
||||
describe('convertHighlightedFieldsToTableRow', () => {
|
||||
it('should convert highlighted fields to a table row', () => {
|
||||
|
@ -19,13 +20,14 @@ describe('convertHighlightedFieldsToTableRow', () => {
|
|||
values: ['host-1'],
|
||||
},
|
||||
};
|
||||
expect(convertHighlightedFieldsToTableRow(highlightedFields, scopeId)).toEqual([
|
||||
expect(convertHighlightedFieldsToTableRow(highlightedFields, scopeId, isPreview)).toEqual([
|
||||
{
|
||||
field: 'host.name',
|
||||
description: {
|
||||
field: 'host.name',
|
||||
values: ['host-1'],
|
||||
scopeId: 'scopeId',
|
||||
isPreview,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
@ -38,13 +40,14 @@ describe('convertHighlightedFieldsToTableRow', () => {
|
|||
values: ['host-1'],
|
||||
},
|
||||
};
|
||||
expect(convertHighlightedFieldsToTableRow(highlightedFields, scopeId)).toEqual([
|
||||
expect(convertHighlightedFieldsToTableRow(highlightedFields, scopeId, isPreview)).toEqual([
|
||||
{
|
||||
field: 'host.name-override',
|
||||
description: {
|
||||
field: 'host.name-override',
|
||||
values: ['host-1'],
|
||||
scopeId: 'scopeId',
|
||||
isPreview,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
|
|
@ -16,7 +16,8 @@ import type { HighlightedFieldsTableRow } from '../../right/components/highlight
|
|||
*/
|
||||
export const convertHighlightedFieldsToTableRow = (
|
||||
highlightedFields: UseHighlightedFieldsResult,
|
||||
scopeId: string
|
||||
scopeId: string,
|
||||
isPreview: boolean
|
||||
): HighlightedFieldsTableRow[] => {
|
||||
const fieldNames = Object.keys(highlightedFields);
|
||||
return fieldNames.map((fieldName) => {
|
||||
|
@ -30,6 +31,7 @@ export const convertHighlightedFieldsToTableRow = (
|
|||
field,
|
||||
values,
|
||||
scopeId,
|
||||
isPreview,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -65,7 +65,7 @@ export const links: LinkItem = {
|
|||
title: CREATE_NEW_RULE,
|
||||
path: RULES_CREATE_PATH,
|
||||
skipUrlState: true,
|
||||
hideTimeline: true,
|
||||
hideTimeline: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue