From 0fe015223ace012757317bef90dc11842f02a688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Bl=C3=A1zquez?= Date: Tue, 25 Feb 2025 17:44:45 +0100 Subject: [PATCH 1/2] Delete unused mock data --- .../public/asset_inventory/sample_data.ts | 109 ------------------ 1 file changed, 109 deletions(-) delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/sample_data.ts diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/sample_data.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/sample_data.ts deleted file mode 100644 index 6037a7ebfb85d..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/sample_data.ts +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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 DataTableRecord } from '@kbn/discover-utils/types'; - -export const mockData = [ - { - id: '1', - raw: {}, - flattened: { - 'asset.risk': 89, - 'asset.name': 'kube-scheduler-cspm-control', - 'asset.criticality': 'high_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '2', - raw: {}, - flattened: { - 'asset.risk': 88, - 'asset.name': 'elastic-agent-LK3r', - 'asset.criticality': 'low_impact', - 'asset.source': 'security-ci', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '3', - raw: {}, - flattened: { - 'asset.risk': 89, - 'asset.name': 'app-server-1', - 'asset.criticality': 'high_impact', - 'asset.source': 'sa-testing', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '4', - raw: {}, - flattened: { - 'asset.risk': 87, - 'asset.name': 'database-backup-control', - 'asset.criticality': 'high_impact', - 'asset.source': 'elastic-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '5', - raw: {}, - flattened: { - 'asset.risk': 69, - 'asset.name': 'elastic-agent-XyZ3', - 'asset.criticality': 'low_impact', - 'asset.source': 'elastic-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '6', - raw: {}, - flattened: { - 'asset.risk': 65, - 'asset.name': 'kube-controller-cspm-monitor', - 'asset.criticality': 'unassigned_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '7', - raw: {}, - flattened: { - 'asset.risk': 89, - 'asset.name': 'storage-service-AWS-EU-1', - 'asset.criticality': 'medium_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '8', - raw: {}, - flattened: { - 'asset.risk': 19, - 'asset.name': 'web-server-LB2', - 'asset.criticality': 'low_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, - { - id: '9', - raw: {}, - flattened: { - 'asset.risk': 85, - 'asset.name': 'DNS-controller-azure-sec', - 'asset.criticality': 'unassigned_impact', - 'asset.source': 'cloud-sec-dev', - '@timestamp': '2025-01-01T00:00:00.000Z', - }, - }, -] as DataTableRecord[]; From ba1521016a28c4ccad3d8af2f27684d2a0f496ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Bl=C3=A1zquez?= Date: Mon, 3 Mar 2025 17:27:34 +0100 Subject: [PATCH 2/2] Refactor Asset Inventory page --- .../components/empty_state.tsx | 16 +- .../components/filters/filters.tsx | 11 +- .../asset_inventory/components/search_bar.tsx | 12 +- .../components/technical_preview_badge.tsx | 34 +++ .../public/asset_inventory/constants.ts | 19 +- .../use_asset_inventory_data_table.ts | 20 +- .../use_base_es_query.ts | 12 +- .../use_page_size.ts | 14 +- .../use_persisted_query.ts | 2 +- .../use_asset_inventory_data_table/utils.ts | 14 -- .../hooks/use_asset_inventory_routes.test.ts | 8 +- .../hooks/use_fetch_chart_data.ts | 4 +- ...e_fetch_data.ts => use_fetch_grid_data.ts} | 10 +- .../asset_inventory/pages/all_assets.tsx | 206 +++++++----------- .../asset_inventory_page.cy.ts | 2 +- 15 files changed, 182 insertions(+), 202 deletions(-) create mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/technical_preview_badge.tsx delete mode 100644 x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/utils.ts rename x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/{use_fetch_data.ts => use_fetch_grid_data.ts} (92%) diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/empty_state.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/empty_state.tsx index 42460408f670a..30202b76c76b2 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/empty_state.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/empty_state.tsx @@ -11,17 +11,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { css } from '@emotion/react'; import illustration from '../../common/images/illustration_product_no_results_magnifying_glass.svg'; +import { DOCS_URL, TEST_SUBJ_EMPTY_STATE } from '../constants'; -const ASSET_INVENTORY_DOCS_URL = 'https://ela.st/asset-inventory'; -const EMPTY_STATE_TEST_SUBJ = 'assetInventory:empty-state'; - -export const EmptyState = ({ - onResetFilters, - docsUrl = ASSET_INVENTORY_DOCS_URL, -}: { - onResetFilters: () => void; - docsUrl?: string; -}) => { +export const EmptyState = ({ onResetFilters }: { onResetFilters: () => void }) => { const { euiTheme } = useEuiTheme(); return ( @@ -35,7 +27,7 @@ export const EmptyState = ({ margin-top: ${euiTheme.size.xxxl}}; } `} - data-test-subj={EMPTY_STATE_TEST_SUBJ} + data-test-subj={TEST_SUBJ_EMPTY_STATE} icon={ , - + void; + setQuery: (v: Partial) => void; } -export const Filters = ({ onFiltersChange }: FiltersProps) => { +export const Filters = ({ setQuery }: FiltersProps) => { const { dataView, dataViewIsLoading } = useDataViewContext(); const spaceId = useSpaceId(); @@ -85,7 +86,9 @@ export const Filters = ({ onFiltersChange }: FiltersProps) => { { + setQuery({ filters }); + }} ruleTypeIds={ASSET_INVENTORY_RULE_TYPE_IDS} Storage={Storage} defaultControls={DEFAULT_ASSET_INVENTORY_FILTERS} diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/search_bar.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/search_bar.tsx index 2de5906cd8113..142a098a219dc 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/search_bar.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/search_bar.tsx @@ -12,15 +12,13 @@ import type { Filter } from '@kbn/es-query'; import { useKibana } from '../../common/lib/kibana'; import { FiltersGlobal } from '../../common/components/filters_global/filters_global'; import { useDataViewContext } from '../hooks/data_view_context'; -import type { AssetsBaseURLQuery } from '../hooks/use_asset_inventory_data_table'; - -type SearchBarQueryProps = Pick; +import type { AssetsURLQuery } from '../hooks/use_asset_inventory_data_table'; interface AssetInventorySearchBarProps { - setQuery(v: Partial): void; + setQuery(v: Partial): void; loading: boolean; placeholder?: string; - query: SearchBarQueryProps; + query: AssetsURLQuery; } export const AssetInventorySearchBar = ({ @@ -54,7 +52,7 @@ export const AssetInventorySearchBar = ({ isLoading={loading} indexPatterns={[dataView]} onQuerySubmit={setQuery} - onFiltersUpdated={(value: Filter[]) => setQuery({ filters: value })} + onFiltersUpdated={(filters: Filter[]) => setQuery({ filters })} placeholder={placeholder} query={{ query: query?.query?.query || '', @@ -69,6 +67,6 @@ export const AssetInventorySearchBar = ({ const getContainerStyle = (theme: EuiThemeComputed) => css` border-bottom: ${theme.border.thin}; - background-color: ${theme.colors.body}; + background-color: ${theme.colors.backgroundBaseSubdued}; padding: ${theme.size.base}; `; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/technical_preview_badge.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/technical_preview_badge.tsx new file mode 100644 index 0000000000000..6fcd97dd878cb --- /dev/null +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/components/technical_preview_badge.tsx @@ -0,0 +1,34 @@ +/* + * 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 { css } from '@emotion/react'; +import { EuiBetaBadge, useEuiTheme } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export const TechnicalPreviewBadge = () => { + const { euiTheme } = useEuiTheme(); + + return ( + + ); +}; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/constants.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/constants.ts index fca88734cc80c..78543cc5cfe14 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/constants.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/constants.ts @@ -5,6 +5,23 @@ * 2.0. */ -export const MAX_ASSETS_TO_LOAD = 500; // equivalent to MAX_FINDINGS_TO_LOAD in @kbn/cloud-security-posture-common +export const MAX_ASSETS_TO_LOAD = 500; export const DEFAULT_VISIBLE_ROWS_PER_PAGE = 25; export const ASSET_INVENTORY_INDEX_PATTERN = 'logs-cloud_asset_inventory.asset_inventory-*'; + +export const QUERY_KEY_GRID_DATA = 'asset_inventory_grid_data'; +export const QUERY_KEY_CHART_DATA = 'asset_inventory_chart_data'; + +export const ASSET_INVENTORY_TABLE_ID = 'asset-inventory-table'; + +const LOCAL_STORAGE_PREFIX = 'assetInventory'; +export const LOCAL_STORAGE_COLUMNS_KEY = `${LOCAL_STORAGE_PREFIX}:columns`; +export const LOCAL_STORAGE_COLUMNS_SETTINGS_KEY = `${LOCAL_STORAGE_COLUMNS_KEY}:settings`; +export const LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY = `${LOCAL_STORAGE_PREFIX}:dataTable:pageSize`; +export const LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY = `${LOCAL_STORAGE_PREFIX}:dataTable:columns`; + +export const TEST_SUBJ_DATA_GRID = 'asset-inventory-test-subj-grid-wrapper'; +export const TEST_SUBJ_PAGE_TITLE = 'asset-inventory-test-subj-page-title'; +export const TEST_SUBJ_EMPTY_STATE = 'asset-inventory-empty-state'; + +export const DOCS_URL = 'https://ela.st/asset-inventory'; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_asset_inventory_data_table.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_asset_inventory_data_table.ts index 741bdaebaae45..ae212a83f006c 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_asset_inventory_data_table.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_asset_inventory_data_table.ts @@ -8,27 +8,23 @@ import { type Dispatch, type SetStateAction, useCallback } from 'react'; import type { BoolQuery, Filter, Query } from '@kbn/es-query'; import type { CriteriaWithPagination } from '@elastic/eui'; import type { DataTableRecord } from '@kbn/discover-utils/types'; +import { LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY } from '../../constants'; import { useUrlQuery } from './use_url_query'; import { usePageSize } from './use_page_size'; -import { getDefaultQuery } from './utils'; import { useBaseEsQuery } from './use_base_es_query'; import { usePersistedQuery } from './use_persisted_query'; -const LOCAL_STORAGE_DATA_TABLE_COLUMNS_KEY = 'assetInventory:dataTable:columns'; - export interface AssetsBaseURLQuery { query: Query; filters: Filter[]; - /** - * Filters that are part of the query but not persisted in the URL or in the Filter Manager - */ - nonPersistedFilters?: Filter[]; /** * Grouping component selection */ groupBy?: string[]; } +export type AssetsURLQuery = Pick; + export type URLQuery = AssetsBaseURLQuery & Record; type SortOrder = [string, string]; @@ -53,6 +49,13 @@ export interface AssetInventoryDataTableResult { getRowsFromPages: (data: Array<{ page: DataTableRecord[] }> | undefined) => DataTableRecord[]; } +const getDefaultQuery = ({ query, filters }: AssetsBaseURLQuery) => ({ + query, + filters, + sort: { field: '@timestamp', direction: 'desc' }, + pageIndex: 0, +}); + /* Hook for managing common table state and methods for the Asset Inventory DataTable */ @@ -60,12 +63,10 @@ export const useAssetInventoryDataTable = ({ defaultQuery = getDefaultQuery, paginationLocalStorageKey, columnsLocalStorageKey, - nonPersistedFilters, }: { defaultQuery?: (params: AssetsBaseURLQuery) => URLQuery; paginationLocalStorageKey: string; columnsLocalStorageKey?: string; - nonPersistedFilters?: Filter[]; }): AssetInventoryDataTableResult => { const getPersistedDefaultQuery = usePersistedQuery(defaultQuery); const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery); @@ -128,7 +129,6 @@ export const useAssetInventoryDataTable = ({ const baseEsQuery = useBaseEsQuery({ filters: urlQuery.filters, query: urlQuery.query, - ...(nonPersistedFilters ? { nonPersistedFilters } : {}), }); const handleUpdateQuery = useCallback( diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_base_es_query.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_base_es_query.ts index d7ea573617e86..2379c9ecf76d9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_base_es_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_base_es_query.ts @@ -9,8 +9,8 @@ import type { DataView } from '@kbn/data-views-plugin/common'; import { buildEsQuery, type EsQueryConfig } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { useEffect, useMemo } from 'react'; -import { useDataViewContext } from '../data_view_context'; import { useKibana } from '../../../common/lib/kibana'; +import { useDataViewContext } from '../data_view_context'; import type { AssetsBaseURLQuery } from './use_asset_inventory_data_table'; interface AssetsBaseESQueryConfig { @@ -38,11 +38,7 @@ const getBaseQuery = ({ } }; -export const useBaseEsQuery = ({ - filters = [], - query, - nonPersistedFilters, -}: AssetsBaseURLQuery) => { +export const useBaseEsQuery = ({ filters = [], query }: AssetsBaseURLQuery) => { const { notifications: { toasts }, data: { @@ -57,11 +53,11 @@ export const useBaseEsQuery = ({ () => getBaseQuery({ dataView, - filters: filters.concat(nonPersistedFilters ?? []).flat(), + filters, query, config, }), - [dataView, filters, nonPersistedFilters, query, config] + [dataView, filters, query, config] ); /** diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_page_size.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_page_size.ts index 396aef75509c5..a9ffb62810561 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_page_size.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_page_size.ts @@ -5,8 +5,7 @@ * 2.0. */ import useLocalStorage from 'react-use/lib/useLocalStorage'; - -const DEFAULT_VISIBLE_ROWS_PER_PAGE = 10; // generic default # of table rows to show (currently we only have a list of policies) +import { DEFAULT_VISIBLE_ROWS_PER_PAGE } from '../../constants'; /** * @description handles persisting the users table row size selection @@ -17,11 +16,8 @@ export const usePageSize = (localStorageKey: string) => { DEFAULT_VISIBLE_ROWS_PER_PAGE ); - let pageSize = DEFAULT_VISIBLE_ROWS_PER_PAGE; - - if (persistedPageSize) { - pageSize = persistedPageSize; - } - - return { pageSize, setPageSize: setPersistedPageSize }; + return { + pageSize: persistedPageSize || DEFAULT_VISIBLE_ROWS_PER_PAGE, + setPageSize: setPersistedPageSize, + }; }; diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_persisted_query.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_persisted_query.ts index 0b2b4eb76d9f8..cbc460caccfe5 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_persisted_query.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/use_persisted_query.ts @@ -7,8 +7,8 @@ import { useCallback } from 'react'; import type { Query } from '@kbn/es-query'; -import type { AssetsBaseURLQuery } from './use_asset_inventory_data_table'; import { useKibana } from '../../../common/lib/kibana'; +import type { AssetsBaseURLQuery } from './use_asset_inventory_data_table'; export const usePersistedQuery = (getter: ({ filters, query }: AssetsBaseURLQuery) => T) => { const { diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/utils.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/utils.ts deleted file mode 100644 index 565ed333edc70..0000000000000 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_data_table/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 { AssetsBaseURLQuery } from './use_asset_inventory_data_table'; - -export const getDefaultQuery = ({ query, filters }: AssetsBaseURLQuery) => ({ - query, - filters, - sort: { field: '@timestamp', direction: 'desc' }, - pageIndex: 0, -}); diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_routes.test.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_routes.test.ts index 13e68fffa450f..ce9b63bf7ff88 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_routes.test.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_asset_inventory_routes.test.ts @@ -9,6 +9,10 @@ import { renderHook } from '@testing-library/react'; import { useAssetInventoryRoutes } from './use_asset_inventory_routes'; import { useKibana } from '../../common/lib/kibana'; import { API_VERSIONS } from '../../../common/constants'; +import { + ASSET_INVENTORY_ENABLE_API_PATH, + ASSET_INVENTORY_STATUS_API_PATH, +} from '../../../common/api/asset_inventory/constants'; jest.mock('../../common/lib/kibana'); @@ -31,7 +35,7 @@ describe('useAssetInventoryRoutes', () => { await result.current.postEnableAssetInventory(); expect(mockFetch).toHaveBeenCalledTimes(1); - expect(mockFetch).toHaveBeenCalledWith('/api/asset_inventory/enable', { + expect(mockFetch).toHaveBeenCalledWith(ASSET_INVENTORY_ENABLE_API_PATH, { method: 'POST', version: API_VERSIONS.public.v1, body: JSON.stringify({}), @@ -45,7 +49,7 @@ describe('useAssetInventoryRoutes', () => { const response = await result.current.getAssetInventoryStatus(); expect(mockFetch).toHaveBeenCalledTimes(1); - expect(mockFetch).toHaveBeenCalledWith('/api/asset_inventory/status', { + expect(mockFetch).toHaveBeenCalledWith(ASSET_INVENTORY_STATUS_API_PATH, { method: 'GET', version: API_VERSIONS.public.v1, query: {}, diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_chart_data.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_chart_data.ts index aac245973bb78..dd225a2e0fa94 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_chart_data.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_chart_data.ts @@ -13,7 +13,7 @@ import { showErrorToast } from '@kbn/cloud-security-posture'; import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; import type { BaseEsQuery } from '@kbn/cloud-security-posture'; import { useKibana } from '../../common/lib/kibana'; -import { ASSET_INVENTORY_INDEX_PATTERN } from '../constants'; +import { ASSET_INVENTORY_INDEX_PATTERN, QUERY_KEY_CHART_DATA } from '../constants'; import { getMultiFieldsSort } from './fetch_utils'; interface UseTopAssetsOptions extends BaseEsQuery { @@ -159,7 +159,7 @@ export function useFetchChartData(options: UseTopAssetsOptions) { notifications: { toasts }, } = useKibana().services; return useQuery( - ['asset_inventory_top_assets_chart', { params: options }], + [QUERY_KEY_CHART_DATA, { params: options }], async () => { const { rawResponse: { aggregations }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_data.ts b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_grid_data.ts similarity index 92% rename from x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_data.ts rename to x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_grid_data.ts index 13f495ebe411d..48cdb86a0faa9 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_data.ts +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/hooks/use_fetch_grid_data.ts @@ -15,7 +15,11 @@ import { showErrorToast } from '@kbn/cloud-security-posture'; import type { IKibanaSearchResponse, IKibanaSearchRequest } from '@kbn/search-types'; import type { BaseEsQuery } from '@kbn/cloud-security-posture'; import { useKibana } from '../../common/lib/kibana'; -import { MAX_ASSETS_TO_LOAD, ASSET_INVENTORY_INDEX_PATTERN } from '../constants'; +import { + MAX_ASSETS_TO_LOAD, + ASSET_INVENTORY_INDEX_PATTERN, + QUERY_KEY_GRID_DATA, +} from '../constants'; import { getRuntimeMappingsFromSort, getMultiFieldsSort } from './fetch_utils'; interface UseAssetsOptions extends BaseEsQuery { @@ -59,13 +63,13 @@ interface Asset { type LatestAssetsRequest = IKibanaSearchRequest; type LatestAssetsResponse = IKibanaSearchResponse>; -export function useFetchData(options: UseAssetsOptions) { +export function useFetchGridData(options: UseAssetsOptions) { const { data, notifications: { toasts }, } = useKibana().services; return useInfiniteQuery( - ['asset_inventory', { params: options }], + [QUERY_KEY_GRID_DATA, { params: options }], async ({ pageParam }) => { const { rawResponse: { hits }, diff --git a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx index 4aaa213961efe..80641a94a3a27 100644 --- a/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx +++ b/x-pack/solutions/security/plugins/security_solution/public/asset_inventory/pages/all_assets.tsx @@ -7,7 +7,6 @@ import React, { useState, useMemo } from 'react'; import _ from 'lodash'; -import { type Filter } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { FormattedMessage, I18nProvider } from '@kbn/i18n-react'; import { @@ -17,42 +16,43 @@ import { useColumns, type UnifiedDataTableSettings, type UnifiedDataTableSettingsColumn, + type CustomCellRenderer, } from '@kbn/unified-data-table'; import { CellActionsProvider } from '@kbn/cell-actions'; -import { type HttpSetup } from '@kbn/core-http-browser'; -import { SHOW_MULTIFIELDS, SORT_DEFAULT_ORDER_SETTING } from '@kbn/discover-utils'; +import { + type RowControlColumn, + SHOW_MULTIFIELDS, + SORT_DEFAULT_ORDER_SETTING, +} from '@kbn/discover-utils'; import { type DataTableRecord } from '@kbn/discover-utils/types'; import { type EuiDataGridCellValueElementProps, - type EuiDataGridControlColumn, type EuiDataGridStyle, EuiProgress, EuiPageTemplate, EuiTitle, EuiButtonIcon, - EuiBetaBadge, - useEuiTheme, } from '@elastic/eui'; import { type AddFieldFilterHandler } from '@kbn/unified-field-list'; import { generateFilters } from '@kbn/data-plugin/public'; import { type DocViewFilterFn } from '@kbn/unified-doc-viewer/types'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { css } from '@emotion/react'; - import type { EntityEcs } from '@kbn/securitysolution-ecs/src/entity'; + import { EmptyComponent } from '../../common/lib/cell_actions/helpers'; -import { useDynamicEntityFlyout } from '../hooks/use_dynamic_entity_flyout'; import { type CriticalityLevelWithUnassigned } from '../../../common/entity_analytics/asset_criticality/types'; import { useKibana } from '../../common/lib/kibana'; - import { AssetCriticalityBadge } from '../../entity_analytics/components/asset_criticality/asset_criticality_badge'; + import { AdditionalControls } from '../components/additional_controls'; import { AssetInventorySearchBar } from '../components/search_bar'; import { RiskBadge } from '../components/risk_badge'; import { Filters } from '../components/filters/filters'; import { EmptyState } from '../components/empty_state'; import { TopAssetsBarChart } from '../components/top_assets_bar_chart'; +import { TechnicalPreviewBadge } from '../components/technical_preview_badge'; +import { useDynamicEntityFlyout } from '../hooks/use_dynamic_entity_flyout'; import { useDataViewContext } from '../hooks/data_view_context'; import { useStyles } from '../hooks/use_styles'; import { @@ -60,9 +60,19 @@ import { type AssetsBaseURLQuery, type URLQuery, } from '../hooks/use_asset_inventory_data_table'; -import { useFetchData } from '../hooks/use_fetch_data'; +import { useFetchGridData } from '../hooks/use_fetch_grid_data'; import { useFetchChartData } from '../hooks/use_fetch_chart_data'; -import { DEFAULT_VISIBLE_ROWS_PER_PAGE, MAX_ASSETS_TO_LOAD } from '../constants'; + +import { + DEFAULT_VISIBLE_ROWS_PER_PAGE, + MAX_ASSETS_TO_LOAD, + ASSET_INVENTORY_TABLE_ID, + TEST_SUBJ_DATA_GRID, + TEST_SUBJ_PAGE_TITLE, + LOCAL_STORAGE_COLUMNS_KEY, + LOCAL_STORAGE_COLUMNS_SETTINGS_KEY, + LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY, +} from '../constants'; const gridStyle: EuiDataGridStyle = { border: 'horizontal', @@ -75,8 +85,12 @@ const title = i18n.translate('xpack.securitySolution.assetInventory.allAssets.ta defaultMessage: 'assets', }); -const columnsLocalStorageKey = 'assetInventoryColumns'; -const LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY = 'assetInventory:dataTable:pageSize'; +const moreActionsLabel = i18n.translate( + 'xpack.securitySolution.assetInventory.flyout.moreActionsButton', + { + defaultMessage: 'More actions', + } +); const columnHeaders: Record = { 'asset.risk': i18n.translate('xpack.securitySolution.assetInventory.allAssets.risk', { @@ -99,7 +113,7 @@ const columnHeaders: Record = { }), } as const; -const customCellRenderer = (rows: DataTableRecord[]) => ({ +const customCellRenderer = (rows: DataTableRecord[]): CustomCellRenderer => ({ 'asset.risk': ({ rowIndex }: EuiDataGridCellValueElementProps) => { const risk = rows[rowIndex].flattened['asset.risk'] as number; return ; @@ -131,17 +145,6 @@ const getDefaultQuery = ({ query, filters }: AssetsBaseURLQuery): URLQuery => ({ sort: [['@timestamp', 'desc']], }); -export interface AllAssetsProps { - height?: number | string; - nonPersistedFilters?: Filter[]; - hasDistributionBar?: boolean; - /** - * This function will be used in the control column to create a rule for a specific finding. - */ - createFn?: (rowIndex: number) => ((http: HttpSetup) => Promise) | undefined; - 'data-test-subj'?: string; -} - // TODO: Asset Inventory - adjust and remove type casting once we have real universal entity data const getEntity = (row: DataTableRecord): EntityEcs => { return { @@ -152,21 +155,22 @@ const getEntity = (row: DataTableRecord): EntityEcs => { }; }; -const ASSET_INVENTORY_TABLE_ID = 'asset-inventory-table'; - -export const AllAssets = ({ - nonPersistedFilters, - height, - hasDistributionBar = true, - createFn, - ...rest -}: AllAssetsProps) => { - const { euiTheme } = useEuiTheme(); - const assetInventoryDataTable = useAssetInventoryDataTable({ +export const AllAssets = () => { + const { + pageSize, + sort, + query, + queryError, + urlQuery, + getRowsFromPages, + onChangeItemsPerPage, + onResetFilters, + onSort, + setUrlQuery, + } = useAssetInventoryDataTable({ paginationLocalStorageKey: LOCAL_STORAGE_DATA_TABLE_PAGE_SIZE_KEY, - columnsLocalStorageKey, + columnsLocalStorageKey: LOCAL_STORAGE_COLUMNS_KEY, defaultQuery: getDefaultQuery, - nonPersistedFilters, }); // Table Flyout Controls ------------------------------------------------------------------- @@ -193,27 +197,14 @@ export const AllAssets = ({ }; // ----------------------------------------------------------------------------------------- - const { - filters, - pageSize, - sort, - query, - queryError, - urlQuery, - getRowsFromPages, - onChangeItemsPerPage, - onResetFilters, - onSort, - setUrlQuery, - } = assetInventoryDataTable; const { data: rowsData, // error: fetchError, - isFetching, fetchNextPage: loadMore, - isLoading, - } = useFetchData({ + isFetching: isFetchingGridData, + isLoading: isLoadingGridData, + } = useFetchGridData({ query, sort, enabled: !queryError, @@ -234,13 +225,13 @@ export const AllAssets = ({ const rows = getRowsFromPages(rowsData?.pages); const totalHits = rowsData?.pages[0].total || 0; - const [columns, setColumns] = useLocalStorage( - columnsLocalStorageKey, + const [localStorageColumns, setLocalStorageColumns] = useLocalStorage( + LOCAL_STORAGE_COLUMNS_KEY, defaultColumns.map((c) => c.id) ); const [persistedSettings, setPersistedSettings] = useLocalStorage( - `${columnsLocalStorageKey}:settings`, + LOCAL_STORAGE_COLUMNS_SETTINGS_KEY, { columns: defaultColumns.reduce((columnSettings, column) => { const columnDefaultSettings = column.width ? { width: column.width } : {}; @@ -273,16 +264,12 @@ export const AllAssets = ({ uiSettings, dataViews, data, - application, + application: { capabilities }, theme, fieldFormats, notifications, storage, } = useKibana().services; - - const styles = useStyles(); - - const { capabilities } = application; const { filterManager } = data.query; const services = { @@ -294,6 +281,8 @@ export const AllAssets = ({ data, }; + const styles = useStyles(); + const { columns: currentColumns, onSetColumns, @@ -304,8 +293,8 @@ export const AllAssets = ({ defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING), dataView, dataViews, - setAppState: (props) => setColumns(props.columns), - columns, + setAppState: (props) => setLocalStorageColumns(props.columns), + columns: localStorageColumns, sort, }); @@ -318,22 +307,18 @@ export const AllAssets = ({ const isVirtualizationEnabled = pageSize >= 100; const getWrapperHeight = () => { - if (height) return height; - // If virtualization is not needed the table will render unconstrained. if (!isVirtualizationEnabled) return 'auto'; const baseHeight = 362; // height of Kibana Header + Findings page header and search bar - const filterBarHeight = filters?.length > 0 ? 40 : 0; - const distributionBarHeight = hasDistributionBar ? 52 : 0; - return `calc(100vh - ${baseHeight}px - ${filterBarHeight}px - ${distributionBarHeight}px)`; + return `calc(100vh - ${baseHeight}px)`; }; return { wrapperHeight: getWrapperHeight(), mode: isVirtualizationEnabled ? 'virtualized' : 'standard', }; - }, [pageSize, height, filters?.length, hasDistributionBar]); + }, [pageSize]); const onAddFilter: AddFieldFilterHandler | undefined = useMemo( () => @@ -365,15 +350,10 @@ export const AllAssets = ({ setPersistedSettings(newGrid); }; - const externalCustomRenderers = useMemo(() => { - if (!customCellRenderer) { - return undefined; - } - return customCellRenderer(rows); - }, [rows]); + const externalCustomRenderers = useMemo(() => customCellRenderer(rows), [rows]); const onResetColumns = () => { - setColumns(defaultColumns.map((c) => c.id)); + setLocalStorageColumns(defaultColumns.map((c) => c.id)); }; const externalAdditionalControls = ( @@ -384,75 +364,47 @@ export const AllAssets = ({ columns={currentColumns} onAddColumn={onAddColumn} onRemoveColumn={onRemoveColumn} - // groupSelectorComponent={groupSelectorComponent} onResetColumns={onResetColumns} /> ); - const externalControlColumns: EuiDataGridControlColumn[] = [ + const externalControlColumns: RowControlColumn[] = [ { - id: 'take-action', - width: 20, + id: 'more-actions', + headerAriaLabel: moreActionsLabel, headerCellRender: () => null, - rowCellRender: ({ rowIndex }) => ( + renderControl: () => ( createFn(rowIndex)} + isLoading={isLoadingGridData} /> ), }, ]; - const loadingState = isLoading || !dataView ? DataLoadingState.loading : DataLoadingState.loaded; + const loadingState = + isLoadingGridData || !dataView ? DataLoadingState.loading : DataLoadingState.loaded; return ( - {!dataView ? null : ( - - )} + - +

- +

- { - setUrlQuery({ filters: newFilters }); - }} - /> + {dataView ? (
- + {!dataView ? null : loadingState === DataLoadingState.loaded && totalHits === 0 ? ( ) : ( @@ -498,14 +450,12 @@ export const AllAssets = ({ showTimeCol={false} settings={settings} onFetchMoreRecords={loadMore} - externalControlColumns={externalControlColumns} + rowAdditionalLeadingControls={externalControlColumns} externalCustomRenderers={externalCustomRenderers} externalAdditionalControls={externalAdditionalControls} gridStyleOverride={gridStyle} rowLineHeightOverride="24px" dataGridDensityState={DataGridDensity.EXPANDED} - showFullScreenButton - // showKeyboardShortcuts /> )}
diff --git a/x-pack/test/security_solution_cypress/cypress/e2e/asset_inventory/asset_inventory_page.cy.ts b/x-pack/test/security_solution_cypress/cypress/e2e/asset_inventory/asset_inventory_page.cy.ts index 4ac47753e2d35..3247eb8cd7fec 100644 --- a/x-pack/test/security_solution_cypress/cypress/e2e/asset_inventory/asset_inventory_page.cy.ts +++ b/x-pack/test/security_solution_cypress/cypress/e2e/asset_inventory/asset_inventory_page.cy.ts @@ -13,7 +13,7 @@ import { setKibanaSetting } from '../../tasks/api_calls/kibana_advanced_settings import { ASSET_INVENTORY_URL } from '../../urls/navigation'; const NO_PRIVILEGES_BOX = getDataTestSubjectSelector('noPrivilegesPage'); -const ALL_ASSETS_TITLE = getDataTestSubjectSelector('all-assets-title'); +const ALL_ASSETS_TITLE = getDataTestSubjectSelector('asset-inventory-test-subj-page-title'); const disableAssetInventory = () => { setKibanaSetting(SECURITY_SOLUTION_ENABLE_ASSET_INVENTORY_SETTING, false);