[Asset Inventory] Added filter functionality onClick Bar Graph (#224896)

## Summary
This PR adds functionality for User to filter data using `entity.type`
and `entity.sub_type` and add it to the Filter Manager


https://github.com/user-attachments/assets/460aa3a4-4052-46d0-824d-6d09e86e645e
This commit is contained in:
Rickyanto Ang 2025-06-24 09:31:00 -07:00 committed by GitHub
parent da41b47f1d
commit b3aad140ae
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 118 additions and 1 deletions

View file

@ -0,0 +1,54 @@
/*
* 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 { ASSET_FIELDS } from '../constants';
import { handleElementClick } from './asset_inventory_bar_chart';
describe('handleElementClick', () => {
it('should call setQuery with the correct filters', () => {
const setQuery = jest.fn();
const mockDatum = {
[ASSET_FIELDS.ENTITY_TYPE]: 'host_type',
[ASSET_FIELDS.ENTITY_SUB_TYPE]: 'host_sub_type',
count: 1,
};
const mockGeometryValue = {
value: 'geometry-value',
datum: mockDatum,
};
const mockSeriesIdentifier = {
key: 'series-identifier',
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const elements: Array<[any, any]> = [[mockGeometryValue, mockSeriesIdentifier]];
const mockIndexId = 'mock-index-id';
handleElementClick(elements, setQuery, mockIndexId);
expect(setQuery).toHaveBeenCalledWith({
filters: [
expect.objectContaining({
meta: expect.objectContaining({
key: 'entity.type',
params: { query: 'host_type' },
}),
}),
expect.objectContaining({
meta: expect.objectContaining({
key: 'entity.sub_type',
params: { query: 'host_sub_type' },
}),
}),
],
});
});
});

View file

@ -15,12 +15,16 @@ import {
type EuiThemeFontSize,
} from '@elastic/eui';
import { getAbbreviatedNumber } from '@kbn/cloud-security-posture-common';
import type { GeometryValue, SeriesIdentifier } from '@elastic/charts';
import { Axis, BarSeries, Chart, Position, Settings, ScaleType } from '@elastic/charts';
import { useElasticChartsTheme } from '@kbn/charts-theme';
import { i18n } from '@kbn/i18n';
import { css } from '@emotion/react';
import { FilterStateStore } from '@kbn/es-query';
import type { AssetInventoryChartData } from '../hooks/use_fetch_chart_data/types';
import { ASSET_FIELDS } from '../constants';
import type { AssetsURLQuery } from '../hooks/use_asset_inventory_url_state/use_asset_inventory_url_state';
import { useDataViewContext } from '../hooks/data_view_context';
const chartTitle = i18n.translate(
'xpack.securitySolution.assetInventory.topAssetsBarChart.chartTitle',
@ -61,19 +65,70 @@ export interface AssetInventoryBarChartProps {
isLoading: boolean;
isFetching: boolean;
assetInventoryChartData: AssetInventoryChartData[];
setQuery(v: Partial<AssetsURLQuery>): void;
}
const createAssetFilter = (key: string, value: string, index: string) => {
return {
$state: { store: FilterStateStore.APP_STATE },
meta: {
alias: null,
disabled: false,
index,
key,
negate: false,
params: { query: value },
type: 'phrase',
},
query: {
match_phrase: {
[key]: value,
},
},
};
};
export const handleElementClick = (
elements: Array<[GeometryValue, SeriesIdentifier]>,
setQuery: (v: Partial<AssetsURLQuery>) => void,
index: string
): void => {
if (!elements.length) return;
const [[geometryValue]] = elements;
const datum = geometryValue.datum as AssetInventoryChartData;
const subtype = datum[ASSET_FIELDS.ENTITY_SUB_TYPE];
const type = datum[ASSET_FIELDS.ENTITY_TYPE];
const filters = [
createAssetFilter(ASSET_FIELDS.ENTITY_TYPE, type, index),
createAssetFilter(ASSET_FIELDS.ENTITY_SUB_TYPE, subtype, index),
];
setQuery({ filters });
};
export const AssetInventoryBarChart = ({
isLoading,
isFetching,
assetInventoryChartData,
setQuery,
}: AssetInventoryBarChartProps) => {
const { euiTheme } = useEuiTheme();
const xsFontSize = useEuiFontSize('xs');
const baseTheme = useElasticChartsTheme();
const { dataView } = useDataViewContext();
if (!dataView.id) {
return null;
}
const dataViewId = dataView.id;
return (
<div css={getChartStyles(euiTheme, xsFontSize)}>
<EuiProgress size="xs" color="accent" style={getProgressStyle(isFetching)} />
<EuiProgress size="xs" color="accent" css={getProgressStyle(isFetching)} />
{isLoading ? (
<EuiFlexGroup
justifyContent="center"
@ -116,6 +171,13 @@ export const AssetInventoryBarChart = ({
const count = !seriesData ? 0 : getAbbreviatedNumber(seriesData.count);
return <span>{count}</span>;
}}
onElementClick={(elements) =>
handleElementClick(
elements as Array<[GeometryValue, SeriesIdentifier]>,
setQuery,
dataViewId
)
}
/>
<Axis
id="X-axis"

View file

@ -101,6 +101,7 @@ const AllAssetsComponent = () => {
isLoading={isLoadingChartData}
isFetching={isFetchingChartData}
assetInventoryChartData={!!chartData && chartData.length > 0 ? chartData : []}
setQuery={setUrlQuery}
/>
<EuiSpacer size="xl" />
<AssetInventoryTableSection state={state} />