[SecuritySolution] Fix topN legend actions - filter in / out in timeline (#170127)

## Summary

https://github.com/elastic/kibana/issues/168199
https://github.com/elastic/kibana/issues/169656


ff5cee55-6da5-4636-85f5-a697a302f8b5

---------

Co-authored-by: Michael Olorunnisola <michael.olorunnisola@elastic.co>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Angela Chuang 2023-10-31 17:30:08 +00:00 committed by GitHub
parent 8420d5776b
commit 8103a44585
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 405 additions and 45 deletions

View file

@ -10,6 +10,7 @@ import { layeredXyVisFunction } from '.';
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
import { sampleArgs, sampleExtendedLayer } from '../__mocks__';
import { XY_VIS } from '../constants';
import { shouldShowLegendActionDefault } from '../helpers/visualization';
describe('layeredXyVis', () => {
test('it renders with the specified data and args', async () => {
@ -30,6 +31,7 @@ describe('layeredXyVis', () => {
syncTooltips: false,
syncCursor: true,
canNavigateToLens: false,
shouldShowLegendAction: shouldShowLegendActionDefault,
},
});
});

View file

@ -18,6 +18,7 @@ import {
validateAxes,
} from './validate';
import { appendLayerIds, getDataLayers } from '../helpers';
import { shouldShowLegendActionDefault } from '../helpers/visualization';
export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers) => {
const layers = appendLayerIds(args.layers ?? [], 'layers');
@ -66,6 +67,7 @@ export const layeredXyVisFn: LayeredXyVisFn['fn'] = async (data, args, handlers)
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
syncCursor: handlers?.isSyncCursorEnabled?.() ?? true,
overrides: handlers.variables?.overrides as XYRender['value']['overrides'],
shouldShowLegendAction: handlers?.shouldShowLegendAction ?? shouldShowLegendActionDefault,
},
};
};

View file

@ -10,6 +10,7 @@ import { xyVisFunction } from '.';
import { createMockExecutionContext } from '@kbn/expressions-plugin/common/mocks';
import { sampleArgs, sampleLayer } from '../__mocks__';
import { XY_VIS } from '../constants';
import { shouldShowLegendActionDefault } from '../helpers/visualization';
describe('xyVis', () => {
test('it renders with the specified data and args', async () => {
@ -42,6 +43,7 @@ describe('xyVis', () => {
syncColors: false,
syncTooltips: false,
syncCursor: true,
shouldShowLegendAction: shouldShowLegendActionDefault,
},
});
});
@ -352,6 +354,7 @@ describe('xyVis', () => {
syncColors: false,
syncTooltips: false,
syncCursor: true,
shouldShowLegendAction: shouldShowLegendActionDefault,
},
});
});
@ -401,6 +404,7 @@ describe('xyVis', () => {
syncTooltips: false,
syncCursor: true,
overrides,
shouldShowLegendAction: shouldShowLegendActionDefault,
},
});
});

View file

@ -30,6 +30,7 @@ import {
validateAxes,
} from './validate';
import { logDatatable } from '../utils';
import { shouldShowLegendActionDefault } from '../helpers/visualization';
const createDataLayer = (args: XYArgs, table: Datatable): DataLayerConfigResult => {
const accessors = getAccessors<string | ExpressionValueVisDimension, XYArgs>(args, table);
@ -139,6 +140,7 @@ export const xyVisFn: XyVisFn['fn'] = async (data, args, handlers) => {
syncTooltips: handlers?.isSyncTooltipsEnabled?.() ?? false,
syncCursor: handlers?.isSyncCursorEnabled?.() ?? true,
overrides: handlers.variables?.overrides as XYRender['value']['overrides'],
shouldShowLegendAction: handlers?.shouldShowLegendAction ?? shouldShowLegendActionDefault,
},
};
};

View file

@ -19,3 +19,5 @@ export function isTimeChart(layers: CommonXYDataLayerConfigResult[]) {
(!l.xScaleType || l.xScaleType === XScaleTypes.TIME)
);
}
export const shouldShowLegendActionDefault = () => true;

View file

@ -203,7 +203,8 @@ describe('getLegendAction', function () {
formattedColumns: {},
},
},
{}
{},
() => true
);
let wrapper: ReactWrapper<LegendActionProps>;

View file

@ -28,6 +28,7 @@ export const getLegendAction = (
fieldFormats: LayersFieldFormats,
formattedDatatables: DatatablesWithFormatInfo,
titles: LayersAccessorsTitles,
shouldShowLegendAction?: (actionId: string) => boolean,
singleTable?: boolean
): LegendAction =>
React.memo(({ series: [xySeries] }) => {
@ -109,6 +110,7 @@ export const getLegendAction = (
}
onFilter={filterHandler}
legendCellValueActions={legendCellValueActions}
shouldShowLegendAction={shouldShowLegendAction}
/>
);
});

View file

@ -6,11 +6,18 @@
* Side Public License, v 1.
*/
import React, { useState } from 'react';
import React, { useState, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiContextMenuPanelDescriptor, EuiIcon, EuiPopover, EuiContextMenu } from '@elastic/eui';
import {
EuiContextMenuPanelDescriptor,
EuiIcon,
EuiPopover,
EuiContextMenu,
EuiContextMenuPanelItemDescriptor,
} from '@elastic/eui';
import { useLegendAction } from '@elastic/charts';
import type { CellValueAction } from '../types';
import { shouldShowLegendActionDefault } from '../../common/helpers/visualization';
export type LegendCellValueActions = Array<
Omit<CellValueAction, 'execute'> & { execute: () => void }
@ -29,57 +36,70 @@ export interface LegendActionPopoverProps {
* Compatible actions to be added to the popover actions
*/
legendCellValueActions?: LegendCellValueActions;
shouldShowLegendAction?: (actionId: string) => boolean;
}
export const LegendActionPopover: React.FunctionComponent<LegendActionPopoverProps> = ({
label,
onFilter,
legendCellValueActions = [],
shouldShowLegendAction = shouldShowLegendActionDefault,
}) => {
const [popoverOpen, setPopoverOpen] = useState(false);
const [ref, onClose] = useLegendAction<HTMLDivElement>();
const legendCellValueActionPanelItems = legendCellValueActions.map((action) => ({
name: action.displayName,
'data-test-subj': `legend-${label}-${action.id}`,
icon: <EuiIcon type={action.iconType} size="m" />,
onClick: () => {
action.execute();
setPopoverOpen(false);
},
}));
const panels: EuiContextMenuPanelDescriptor[] = useMemo(() => {
const defaultActions = [
{
id: 'filterIn',
displayName: i18n.translate('expressionXY.legend.filterForValueButtonAriaLabel', {
defaultMessage: 'Filter for',
}),
'data-test-subj': `legend-${label}-filterIn`,
iconType: 'plusInCircle',
execute: () => {
setPopoverOpen(false);
onFilter();
},
},
{
id: 'filterOut',
displayName: i18n.translate('expressionXY.legend.filterOutValueButtonAriaLabel', {
defaultMessage: 'Filter out',
}),
'data-test-subj': `legend-${label}-filterOut`,
iconType: 'minusInCircle',
execute: () => {
setPopoverOpen(false);
onFilter({ negate: true });
},
},
];
const panels: EuiContextMenuPanelDescriptor[] = [
{
id: 'main',
title: label,
items: [
{
name: i18n.translate('expressionXY.legend.filterForValueButtonAriaLabel', {
defaultMessage: 'Filter for',
}),
'data-test-subj': `legend-${label}-filterIn`,
icon: <EuiIcon type="plusInCircle" size="m" />,
const legendCellValueActionPanelItems = [...defaultActions, ...legendCellValueActions].reduce<
EuiContextMenuPanelItemDescriptor[]
>((acc, action) => {
if (shouldShowLegendAction(action.id)) {
acc.push({
name: action.displayName,
'data-test-subj': `legend-${label}-${action.id}`,
icon: <EuiIcon type={action.iconType} size="m" />,
onClick: () => {
action.execute();
setPopoverOpen(false);
onFilter();
},
},
{
name: i18n.translate('expressionXY.legend.filterOutValueButtonAriaLabel', {
defaultMessage: 'Filter out',
}),
'data-test-subj': `legend-${label}-filterOut`,
icon: <EuiIcon type="minusInCircle" size="m" />,
onClick: () => {
setPopoverOpen(false);
onFilter({ negate: true });
},
},
...legendCellValueActionPanelItems,
],
},
];
});
}
return acc;
}, []);
return [
{
id: 'main',
title: label,
items: legendCellValueActionPanelItems,
},
];
}, [label, legendCellValueActions, onFilter, shouldShowLegendAction]);
const Button = (
<div

View file

@ -145,6 +145,7 @@ export type XYChartRenderProps = Omit<XYChartProps, 'canNavigateToLens'> & {
renderComplete: () => void;
uiState?: PersistedState;
timeFormat: string;
shouldShowLegendAction?: (actionId: string) => boolean;
};
function nonNullable<T>(v: T): v is NonNullable<T> {
@ -207,6 +208,7 @@ export function XYChart({
uiState,
timeFormat,
overrides,
shouldShowLegendAction,
}: XYChartRenderProps) {
const {
legend,
@ -839,6 +841,7 @@ export function XYChart({
fieldFormats,
formattedDatatables,
titles,
shouldShowLegendAction,
singleTable
)
: undefined

View file

@ -277,6 +277,7 @@ export const getXyChartRenderer = ({
syncCursor={config.syncCursor}
uiState={handlers.uiState as PersistedState}
renderComplete={renderComplete}
shouldShowLegendAction={handlers.shouldShowLegendAction}
/>
</div>
</I18nProvider>

View file

@ -84,6 +84,8 @@ export interface ExecutionContext<
* Logs datatable.
*/
logDatatable?(name: string, datatable: Datatable): void;
shouldShowLegendAction?: (actionId: string) => boolean;
}
/**

View file

@ -105,4 +105,5 @@ export interface IInterpreterRenderHandlers {
uiState?: unknown;
getExecutionContext(): KibanaExecutionContext | undefined;
shouldShowLegendAction?: (actionId: string) => boolean;
}

View file

@ -63,6 +63,7 @@ export class ExpressionLoader {
hasCompatibleActions: params?.hasCompatibleActions,
getCompatibleCellValueActions: params?.getCompatibleCellValueActions,
executionContext: params?.executionContext,
shouldShowLegendAction: params?.shouldShowLegendAction,
});
this.render$ = this.renderHandler.render$;
this.update$ = this.renderHandler.update$;

View file

@ -24,6 +24,7 @@ export interface ReactExpressionRendererProps
error?: ExpressionRenderError | null
) => React.ReactElement | React.ReactElement[];
padding?: 'xs' | 's' | 'm' | 'l' | 'xl';
shouldShowLegendAction?: (actionId: string) => boolean;
}
export type ReactExpressionRendererType = React.ComponentType<ReactExpressionRendererProps>;

View file

@ -36,6 +36,7 @@ export interface ExpressionRenderHandlerParams {
hasCompatibleActions?: (event: ExpressionRendererEvent) => Promise<boolean>;
getCompatibleCellValueActions?: (data: object[]) => Promise<unknown[]>;
executionContext?: KibanaExecutionContext;
shouldShowLegendAction?: (actionId: string) => boolean;
}
type UpdateValue = IInterpreterRenderUpdateParams<IExpressionLoaderParams>;
@ -66,6 +67,7 @@ export class ExpressionRenderHandler {
hasCompatibleActions = async () => false,
getCompatibleCellValueActions = async () => [],
executionContext,
shouldShowLegendAction,
}: ExpressionRenderHandlerParams = {}
) {
this.element = element;
@ -118,6 +120,9 @@ export class ExpressionRenderHandler {
},
hasCompatibleActions,
getCompatibleCellValueActions,
shouldShowLegendAction: (actionId: string) => {
return shouldShowLegendAction?.(actionId) ?? true;
},
};
}

View file

@ -67,6 +67,7 @@ export interface IExpressionLoaderParams {
* By default, it equals 1000.
*/
throttle?: number;
shouldShowLegendAction?: (actionId: string) => boolean;
}
export interface ExpressionRenderError extends Error {

View file

@ -176,6 +176,7 @@ interface LensBaseEmbeddableInput extends EmbeddableInput {
onTableRowClick?: (
data: Simplify<LensTableRowContextMenuEvent['data'] & PreventableEvent>
) => void;
shouldShowLegendAction?: (actionId: string) => boolean;
}
export type LensByValueInput = {
@ -1103,6 +1104,7 @@ export class Embeddable
}}
noPadding={this.visDisplayOptions.noPadding}
docLinks={this.deps.coreStart.docLinks}
shouldShowLegendAction={input.shouldShowLegendAction}
/>
</KibanaThemeProvider>
<MessagesBadge

View file

@ -47,6 +47,7 @@ export interface ExpressionWrapperProps {
lensInspector: LensInspector;
noPadding?: boolean;
docLinks: CoreStart['docLinks'];
shouldShowLegendAction?: (actionId: string) => boolean;
}
export function ExpressionWrapper({
@ -73,6 +74,7 @@ export function ExpressionWrapper({
lensInspector,
noPadding,
docLinks,
shouldShowLegendAction,
}: ExpressionWrapperProps) {
if (!expression) return null;
return (
@ -104,6 +106,7 @@ export function ExpressionWrapper({
onEvent={handleEvent}
hasCompatibleActions={hasCompatibleActions}
getCompatibleCellValueActions={getCompatibleCellValueActions}
shouldShowLegendAction={shouldShowLegendAction}
/>
</div>
</I18nProvider>

View file

@ -9,3 +9,7 @@ export { createFilterInCellActionFactory } from './cell_action/filter_in';
export { createFilterOutCellActionFactory } from './cell_action/filter_out';
export { createFilterInDiscoverCellActionFactory } from './discover/filter_in';
export { createFilterOutDiscoverCellActionFactory } from './discover/filter_out';
export { createTimelineHistogramFilterInLegendActionFactory } from './lens/filter_in_timeline';
export { createFilterInHistogramLegendActionFactory } from './lens/filter_in';
export { createTimelineHistogramFilterOutLegendActionFactory } from './lens/filter_out_timeline';
export { createFilterOutHistogramLegendActionFactory } from './lens/filter_out';

View file

@ -0,0 +1,29 @@
/*
* 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 type { SecurityAppStore } from '../../../common/store';
import type { StartServices } from '../../../types';
import { createHistogramFilterLegendActionFactory } from './helpers';
export const HISTOGRAM_LEGEND_ACTION_FILTER_IN = 'histogramLegendActionFilterIn';
export const createFilterInHistogramLegendActionFactory = ({
store,
order,
services,
}: {
store: SecurityAppStore;
order: number;
services: StartServices;
}) =>
createHistogramFilterLegendActionFactory({
id: HISTOGRAM_LEGEND_ACTION_FILTER_IN,
order,
store,
services,
});

View file

@ -0,0 +1,29 @@
/*
* 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 type { SecurityAppStore } from '../../../common/store';
import type { StartServices } from '../../../types';
import { createHistogramFilterLegendActionFactory } from './helpers';
export const TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN = 'timelineHistogramLegendActionFilterIn';
export const createTimelineHistogramFilterInLegendActionFactory = ({
store,
order,
services,
}: {
store: SecurityAppStore;
order: number;
services: StartServices;
}) =>
createHistogramFilterLegendActionFactory({
id: TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN,
order,
store,
services,
});

View file

@ -0,0 +1,30 @@
/*
* 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 type { SecurityAppStore } from '../../../common/store';
import type { StartServices } from '../../../types';
import { createHistogramFilterLegendActionFactory } from './helpers';
export const HISTOGRAM_LEGEND_ACTION_FILTER_OUT = 'histogramLegendActionFilterOut';
export const createFilterOutHistogramLegendActionFactory = ({
store,
order,
services,
}: {
store: SecurityAppStore;
order: number;
services: StartServices;
}) =>
createHistogramFilterLegendActionFactory({
id: HISTOGRAM_LEGEND_ACTION_FILTER_OUT,
order,
store,
services,
negate: true,
});

View file

@ -0,0 +1,30 @@
/*
* 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 type { SecurityAppStore } from '../../../common/store';
import type { StartServices } from '../../../types';
import { createHistogramFilterLegendActionFactory } from './helpers';
export const TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT = 'timelineHistogramLegendActionFilterOut';
export const createTimelineHistogramFilterOutLegendActionFactory = ({
store,
order,
services,
}: {
store: SecurityAppStore;
order: number;
services: StartServices;
}) =>
createHistogramFilterLegendActionFactory({
id: TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT,
order,
store,
services,
negate: true,
});

View file

@ -0,0 +1,111 @@
/*
* 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 { addFilterIn, addFilterOut } from '@kbn/cell-actions';
import {
isValueSupportedByDefaultActions,
valueToArray,
filterOutNullableValues,
} from '@kbn/cell-actions/src/actions/utils';
import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import type { CellValueContext } from '@kbn/embeddable-plugin/public';
import { createAction } from '@kbn/ui-actions-plugin/public';
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '@kbn/cell-actions/src/actions/translations';
import { i18n } from '@kbn/i18n';
import { KibanaServices } from '../../../common/lib/kibana';
import { timelineSelectors } from '../../../timelines/store/timeline';
import { fieldHasCellActions, isInSecurityApp, isLensEmbeddable } from '../../utils';
import { TimelineId } from '../../../../common/types';
import { SecurityCellActionType } from '../../constants';
import type { SecurityAppStore } from '../../../common/store';
import type { StartServices } from '../../../types';
import { HISTOGRAM_LEGEND_ACTION_FILTER_IN } from './filter_in';
import { HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from './filter_out';
function isDataColumnsValid(data?: CellValueContext['data']): boolean {
return (
!!data &&
data.length > 0 &&
data.every(({ columnMeta }) => columnMeta && fieldHasCellActions(columnMeta.field))
);
}
export const createHistogramFilterLegendActionFactory = ({
id,
order,
store,
services,
negate,
}: {
id: string;
order: number;
store: SecurityAppStore;
services: StartServices;
negate?: boolean;
}) => {
const { application: applicationService } = KibanaServices.get();
let currentAppId: string | undefined;
applicationService.currentAppId$.subscribe((appId) => {
currentAppId = appId;
});
const getTimelineById = timelineSelectors.getTimelineByIdSelector();
const { notifications } = services;
const { filterManager } = services.data.query;
return createAction<CellValueContext>({
id,
order,
getIconType: () => (negate ? 'minusInCircle' : 'plusInCircle'),
getDisplayName: () =>
negate
? i18n.translate('xpack.securitySolution.actions.filterOutTimeline', {
defaultMessage: `Filter out`,
})
: i18n.translate('xpack.securitySolution.actions.filterForTimeline', {
defaultMessage: `Filter for`,
}),
type: SecurityCellActionType.FILTER,
isCompatible: async ({ embeddable, data }) =>
!isErrorEmbeddable(embeddable) &&
isLensEmbeddable(embeddable) &&
isDataColumnsValid(data) &&
isInSecurityApp(currentAppId),
execute: async ({ data }) => {
const field = data[0]?.columnMeta?.field;
const rawValue = data[0]?.value;
const value = filterOutNullableValues(valueToArray(rawValue));
if (!isValueSupportedByDefaultActions(value)) {
notifications.toasts.addWarning({
title: ACTION_INCOMPATIBLE_VALUE_WARNING,
});
return;
}
if (!field) return;
const timeline = getTimelineById(store.getState(), TimelineId.active);
services.topValuesPopover.closePopover();
if (!negate) {
addFilterIn({
filterManager:
id === HISTOGRAM_LEGEND_ACTION_FILTER_IN ? filterManager : timeline.filterManager,
fieldName: field,
value,
});
} else {
addFilterOut({
filterManager:
id === HISTOGRAM_LEGEND_ACTION_FILTER_OUT ? filterManager : timeline.filterManager,
fieldName: field,
value,
});
}
},
});
};

View file

@ -13,8 +13,12 @@ import type { StartServices } from '../types';
import {
createFilterInCellActionFactory,
createFilterInDiscoverCellActionFactory,
createTimelineHistogramFilterInLegendActionFactory,
createFilterInHistogramLegendActionFactory,
createFilterOutCellActionFactory,
createFilterOutDiscoverCellActionFactory,
createFilterOutHistogramLegendActionFactory,
createTimelineHistogramFilterOutLegendActionFactory,
} from './filter';
import {
createAddToTimelineLensAction,
@ -53,11 +57,39 @@ export const registerUIActions = (
const registerLensEmbeddableActions = (store: SecurityAppStore, services: StartServices) => {
const { uiActions } = services;
const addToTimelineAction = createAddToTimelineLensAction({ store, order: 1 });
const addToTimelineAction = createAddToTimelineLensAction({ store, order: 4 });
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, addToTimelineAction);
const copyToClipboardAction = createCopyToClipboardLensAction({ order: 2 });
const copyToClipboardAction = createCopyToClipboardLensAction({ order: 5 });
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, copyToClipboardAction);
const filterInTimelineLegendActions = createTimelineHistogramFilterInLegendActionFactory({
store,
order: 0,
services,
});
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterInTimelineLegendActions);
const filterOutTimelineLegendActions = createTimelineHistogramFilterOutLegendActionFactory({
store,
order: 1,
services,
});
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterOutTimelineLegendActions);
const filterInLegendActions = createFilterInHistogramLegendActionFactory({
store,
order: 2,
services,
});
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterInLegendActions);
const filterOutLegendActions = createFilterOutHistogramLegendActionFactory({
store,
order: 3,
services,
});
uiActions.addTriggerAction(CELL_VALUE_TRIGGER, filterOutLegendActions);
};
const registerDiscoverCellActions = (store: SecurityAppStore, services: StartServices) => {

View file

@ -24,7 +24,7 @@ import { inputsSelectors } from '../../store';
import { useDeepEqualSelector } from '../../hooks/use_selector';
import { ModalInspectQuery } from '../inspect/modal';
import { InputsModelId } from '../../store/inputs/constants';
import { getRequestsAndResponses } from './utils';
import { getRequestsAndResponses, showLegendActionsByActionId } from './utils';
import { SourcererScopeName } from '../../store/sourcerer/model';
import { VisualizationActions } from './actions';
@ -218,6 +218,11 @@ const LensEmbeddableComponent: React.FC<LensEmbeddableComponentProps> = ({
[attributes?.state?.adHocDataViews]
);
const shouldShowLegendAction = useCallback(
(actionId: string) => showLegendActionsByActionId({ actionId, scopeId }),
[scopeId]
);
if (!searchSessionId) {
return null;
}
@ -281,6 +286,7 @@ const LensEmbeddableComponent: React.FC<LensEmbeddableComponentProps> = ({
showInspector={false}
syncTooltips={false}
syncCursor={false}
shouldShowLegendAction={shouldShowLegendAction}
/>
</LensComponentWrapper>
)}

View file

@ -6,10 +6,17 @@
*/
import type { Filter } from '@kbn/es-query';
import { SecurityPageName } from '../../../../common/constants';
import { HISTOGRAM_LEGEND_ACTION_FILTER_IN } from '../../../actions/filter/lens/filter_in';
import { TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN } from '../../../actions/filter/lens/filter_in_timeline';
import { HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from '../../../actions/filter/lens/filter_out';
import { TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT } from '../../../actions/filter/lens/filter_out_timeline';
import type { Request } from './types';
export const VISUALIZATION_ACTIONS_BUTTON_CLASS = 'histogram-actions-trigger';
export const FILTER_IN_LEGEND_ACTION = `filterIn`;
export const FILTER_OUT_LEGEND_ACTION = `filterOut`;
const pageFilterFieldMap: Record<string, string> = {
[SecurityPageName.hosts]: 'host',
@ -192,3 +199,28 @@ export const parseVisualizationData = <T>(data: string[]): T[] =>
return acc;
}
}, [] as T[]);
export const showLegendActionsByActionId = ({
actionId,
scopeId,
}: {
actionId: string;
scopeId: string;
}) => {
switch (actionId) {
/** We no longer use Lens' default filter in / out actions
* as extra custom actions needed after filters applied.
* For example: hide the topN panel after filters applied */
case FILTER_IN_LEGEND_ACTION:
case FILTER_OUT_LEGEND_ACTION:
return false;
case HISTOGRAM_LEGEND_ACTION_FILTER_IN:
case HISTOGRAM_LEGEND_ACTION_FILTER_OUT:
return scopeId !== 'timeline';
case TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_IN:
case TIMELINE_HISTOGRAM_LEGEND_ACTION_FILTER_OUT:
return scopeId === 'timeline';
default:
return true;
}
};

View file

@ -140,8 +140,10 @@ export const SELECT_HISTOGRAM = '[data-test-subj="chart-select-trend"]';
export const LEGEND_ACTIONS = {
ADD_TO_TIMELINE: (ruleName: string) =>
`[data-test-subj="legend-${ruleName}-embeddable_addToTimeline"]`,
FILTER_FOR: (ruleName: string) => `[data-test-subj="legend-${ruleName}-filterIn"]`,
FILTER_OUT: (ruleName: string) => `[data-test-subj="legend-${ruleName}-filterOut"]`,
FILTER_FOR: (ruleName: string) =>
`[data-test-subj="legend-${ruleName}-histogramLegendActionFilterIn"]`,
FILTER_OUT: (ruleName: string) =>
`[data-test-subj="legend-${ruleName}-histogramLegendActionFilterOut"]`,
COPY: (ruleName: string) => `[data-test-subj="legend-${ruleName}-embeddable_copyToClipboard"]`,
};