From 568cc9929081bcf20ee7634cb693bc0170b45fbf Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 6 Nov 2024 14:42:56 +0000 Subject: [PATCH 01/22] lots of changes --- .../inventory/common/entities.ts | 25 +-- .../inventory/common/entitites.test.ts | 57 ------- .../inventory/kibana.jsonc | 2 +- .../public/components/app_root/index.tsx | 2 +- .../grouped_entities_grid.tsx | 53 ++++--- .../components/grouped_inventory/index.tsx | 37 +++-- .../inventory_group_accordion.tsx | 26 ++-- .../search_bar/control_groups/index.tsx | 94 +++++++++++ .../control_groups/use_control_panels.ts | 118 ++++++++++++++ .../public/components/search_bar/index.tsx | 81 +++------- .../index.tsx} | 49 +++--- .../index.tsx | 24 ++- .../public/hooks/use_unified_search.ts | 146 ++++++++++++++++++ .../public/pages/inventory_page/index.tsx | 30 +--- .../inventory/public/routes/config.tsx | 11 +- .../services/telemetry/telemetry_client.ts | 7 - .../services/telemetry/telemetry_events.ts | 34 ---- .../telemetry/telemetry_service.test.ts | 22 --- .../public/services/telemetry/types.ts | 8 - .../routes/entities/get_entity_groups.ts | 25 +-- .../routes/entities/get_latest_entities.ts | 15 +- .../inventory/server/routes/entities/route.ts | 17 +- .../public/hooks/use_url_state.ts | 142 +++++++++++++++++ .../observability_shared/public/index.ts | 2 + 24 files changed, 655 insertions(+), 372 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/inventory/common/entitites.test.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts rename x-pack/plugins/observability_solution/inventory/public/components/{grouped_inventory/unified_inventory.tsx => unified_inventory/index.tsx} (78%) create mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts create mode 100644 x-pack/plugins/observability_solution/observability_shared/public/hooks/use_url_state.ts diff --git a/x-pack/plugins/observability_solution/inventory/common/entities.ts b/x-pack/plugins/observability_solution/inventory/common/entities.ts index e5bd12d252767..fea3ed5d8e658 100644 --- a/x-pack/plugins/observability_solution/inventory/common/entities.ts +++ b/x-pack/plugins/observability_solution/inventory/common/entities.ts @@ -78,29 +78,6 @@ export const ENTITIES_LATEST_ALIAS = entitiesAliasPattern({ dataset: ENTITY_LATEST, }); -const entityArrayRt = t.array(t.string); -export const entityTypesRt = new t.Type( - 'entityTypesRt', - entityArrayRt.is, - (input, context) => { - if (typeof input === 'string') { - const arr = input.split(','); - const validation = entityArrayRt.decode(arr); - if (isRight(validation)) { - return t.success(validation.right); - } - } else if (Array.isArray(input)) { - const validation = entityArrayRt.decode(input); - if (isRight(validation)) { - return t.success(validation.right); - } - } - - return t.failure(input, context); - }, - (arr) => arr.join() -); - export interface Entity { [ENTITY_LAST_SEEN]: string; [ENTITY_ID]: string; @@ -115,5 +92,5 @@ export interface Entity { export type EntityGroup = { count: number; } & { - [key: string]: any; + [key: string]: string; }; diff --git a/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts b/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts deleted file mode 100644 index c923bda530746..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/common/entitites.test.ts +++ /dev/null @@ -1,57 +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 { isLeft, isRight } from 'fp-ts/lib/Either'; -import { entityTypesRt } from './entities'; - -const validate = (input: unknown) => entityTypesRt.decode(input); - -describe('entityTypesRt codec', () => { - it('should validate a valid string of entity types', () => { - const input = 'service,host,container'; - const result = validate(input); - expect(isRight(result)).toBe(true); - if (isRight(result)) { - expect(result.right).toEqual(['service', 'host', 'container']); - } - }); - - it('should validate a valid array of entity types', () => { - const input = ['service', 'host', 'container']; - const result = validate(input); - expect(isRight(result)).toBe(true); - if (isRight(result)) { - expect(result.right).toEqual(['service', 'host', 'container']); - } - }); - - it('should fail validation when input is not a string or array', () => { - const input = 123; - const result = validate(input); - expect(isLeft(result)).toBe(true); - }); - - it('should validate an empty array as valid', () => { - const input: unknown[] = []; - const result = validate(input); - expect(isRight(result)).toBe(true); - if (isRight(result)) { - expect(result.right).toEqual([]); - } - }); - - it('should serialize a valid array back to a string', () => { - const input = ['service', 'host']; - const serialized = entityTypesRt.encode(input); - expect(serialized).toBe('service,host'); - }); - - it('should serialize an empty array back to an empty string', () => { - const input: string[] = []; - const serialized = entityTypesRt.encode(input); - expect(serialized).toBe(''); - }); -}); diff --git a/x-pack/plugins/observability_solution/inventory/kibana.jsonc b/x-pack/plugins/observability_solution/inventory/kibana.jsonc index fc77163ae3c5f..99ce027a411fe 100644 --- a/x-pack/plugins/observability_solution/inventory/kibana.jsonc +++ b/x-pack/plugins/observability_solution/inventory/kibana.jsonc @@ -19,7 +19,7 @@ "ruleRegistry", "share" ], - "requiredBundles": ["kibanaReact"], + "requiredBundles": ["kibanaReact","controls"], "optionalPlugins": ["spaces", "cloud"], "extraPublicDirs": [] } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx index 6bec4335c7193..a638cbdf5dd1b 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx @@ -44,7 +44,7 @@ export function AppRoot({ - + diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/grouped_entities_grid.tsx b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/grouped_entities_grid.tsx index d005a001999d5..363bec33dd484 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/grouped_entities_grid.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/grouped_entities_grid.tsx @@ -19,35 +19,35 @@ import { import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useInventoryRouter } from '../../hooks/use_inventory_router'; +import { useUnifiedSearch } from '../../hooks/use_unified_search'; interface Props { - field: string; + groupValue: string; } const paginationDecoder = decodeOrThrow(entityPaginationRt); -export function GroupedEntitiesGrid({ field }: Props) { +export function GroupedEntitiesGrid({ groupValue }: Props) { const { query } = useInventoryParams('/'); - const { sortField, sortDirection, kuery, pagination: paginationQuery } = query; + const { sortField, sortDirection, pagination: paginationQuery } = query; const inventoryRoute = useInventoryRouter(); let pagination: EntityPagination | undefined = {}; + const { stringifiedEsQuery } = useUnifiedSearch(); try { pagination = paginationDecoder(paginationQuery); } catch (error) { inventoryRoute.push('/', { path: {}, query: { - sortField, - sortDirection, - kuery, + ...query, pagination: undefined, }, }); window.location.reload(); } - const pageIndex = pagination?.[field] ?? 0; + const pageIndex = pagination?.[groupValue] ?? 0; - const { refreshSubject$ } = useInventorySearchBarContext(); + const { refreshSubject$, isControlPanelsInitiated } = useInventorySearchBarContext(); const { services: { inventoryAPIClient }, } = useKibana(); @@ -58,19 +58,28 @@ export function GroupedEntitiesGrid({ field }: Props) { refresh, } = useInventoryAbortableAsync( ({ signal }) => { - return inventoryAPIClient.fetch('GET /internal/inventory/entities', { - params: { - query: { - sortDirection, - sortField, - entityTypes: field?.length ? JSON.stringify([field]) : undefined, - kuery, + if (isControlPanelsInitiated) { + return inventoryAPIClient.fetch('GET /internal/inventory/entities', { + params: { + query: { + sortDirection, + sortField, + esQuery: stringifiedEsQuery, + entityTypes: groupValue?.length ? JSON.stringify([groupValue]) : undefined, + }, }, - }, - signal, - }); + signal, + }); + } }, - [field, inventoryAPIClient, kuery, sortDirection, sortField] + [ + groupValue, + inventoryAPIClient, + sortDirection, + sortField, + isControlPanelsInitiated, + stringifiedEsQuery, + ] ); useEffectOnce(() => { @@ -86,7 +95,7 @@ export function GroupedEntitiesGrid({ field }: Props) { ...query, pagination: entityPaginationRt.encode({ ...pagination, - [field]: nextPage, + [groupValue]: nextPage, }), }, }); @@ -105,12 +114,14 @@ export function GroupedEntitiesGrid({ field }: Props) { function handleTypeFilter(type: string) { const { pagination: _, ...rest } = query; + + // TODO: caue check it inventoryRoute.push('/', { path: {}, query: { ...rest, // Override the current entity types - entityTypes: [type], + // entityTypes: [type], }, }); } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx index b376200495e43..52ea2b4838f86 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx @@ -8,20 +8,19 @@ import { EuiSpacer } from '@elastic/eui'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import React from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { InventoryGroupAccordion } from './inventory_group_accordion'; +import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useKibana } from '../../hooks/use_kibana'; +import { useUnifiedSearch } from '../../hooks/use_unified_search'; +import { InventoryGroupAccordion } from './inventory_group_accordion'; import { InventorySummary } from './inventory_summary'; -import { useInventoryParams } from '../../hooks/use_inventory_params'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; export function GroupedInventory() { const { services: { inventoryAPIClient }, } = useKibana(); - const { query } = useInventoryParams('/'); - const { kuery, entityTypes } = query; - const { refreshSubject$ } = useInventorySearchBarContext(); + const { refreshSubject$, isControlPanelsInitiated } = useInventorySearchBarContext(); + const { stringifiedEsQuery } = useUnifiedSearch(); const { value = { groupBy: ENTITY_TYPE, groups: [], entitiesCount: 0 }, @@ -29,20 +28,19 @@ export function GroupedInventory() { loading, } = useInventoryAbortableAsync( ({ signal }) => { - return inventoryAPIClient.fetch('GET /internal/inventory/entities/group_by/{field}', { - params: { - path: { - field: ENTITY_TYPE, - }, - query: { - kuery, - entityTypes: entityTypes?.length ? JSON.stringify(entityTypes) : undefined, + if (isControlPanelsInitiated) { + return inventoryAPIClient.fetch('GET /internal/inventory/entities/group_by/{field}', { + params: { + path: { + field: ENTITY_TYPE, + }, + query: { esQuery: stringifiedEsQuery }, }, - }, - signal, - }); + signal, + }); + } }, - [entityTypes, inventoryAPIClient, kuery] + [inventoryAPIClient, stringifiedEsQuery, isControlPanelsInitiated] ); useEffectOnce(() => { @@ -58,8 +56,9 @@ export function GroupedInventory() { {value.groups.map((group) => ( ))} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.tsx b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.tsx index 4c5d34e5a028f..0b4e9a46d4288 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.tsx @@ -4,12 +4,11 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useCallback, useState } from 'react'; -import { i18n } from '@kbn/i18n'; -import { css } from '@emotion/react'; import { EuiAccordion, EuiPanel, EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/react'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useState } from 'react'; import { GroupedEntitiesGrid } from './grouped_entities_grid'; -import type { EntityGroup } from '../../../common/entities'; import { InventoryPanelBadge } from './inventory_panel_badge'; const ENTITIES_COUNT_BADGE = i18n.translate( @@ -18,18 +17,19 @@ const ENTITIES_COUNT_BADGE = i18n.translate( ); export interface InventoryGroupAccordionProps { - group: EntityGroup; groupBy: string; + groupValue: string; + groupCount: number; isLoading?: boolean; } export function InventoryGroupAccordion({ - group, groupBy, + groupValue, + groupCount, isLoading, }: InventoryGroupAccordionProps) { const { euiTheme } = useEuiTheme(); - const field = group[groupBy]; const [open, setOpen] = useState(false); const onToggle = useCallback(() => { @@ -46,19 +46,19 @@ export function InventoryGroupAccordion({ `} > -

{field}

+

{groupValue}

} buttonElement="div" extraAction={ } buttonProps={{ paddingSize: 'm' }} @@ -78,7 +78,7 @@ export function InventoryGroupAccordion({ hasShadow={false} paddingSize="m" > - + )} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx new file mode 100644 index 0000000000000..1741821107bc2 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx @@ -0,0 +1,94 @@ +/* + * 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 { + ControlGroupRenderer, + ControlGroupRendererApi, + ControlGroupRuntimeState, +} from '@kbn/controls-plugin/public'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { skip, Subscription } from 'rxjs'; +import { css } from '@emotion/react'; +import { useInventorySearchBarContext } from '../../../context/inventory_search_bar_context_provider'; +import { useUnifiedSearch } from '../../../hooks/use_unified_search'; +import { useControlPanels } from './use_control_panels'; + +export function ControlGroups() { + const { isControlPanelsInitiated, setIsControlPanelsInitiated, dataView } = + useInventorySearchBarContext(); + const { controlPanels, setControlPanels } = useControlPanels(dataView); + const { searchState, setSearchState } = useUnifiedSearch(); + const subscriptions = useRef(new Subscription()); + + const getInitialInput = useCallback( + () => async () => { + const initialInput: Partial = { + chainingSystem: 'HIERARCHICAL', + labelPosition: 'oneLine', + initialChildControlState: controlPanels, + }; + + return { initialState: initialInput }; + }, + [controlPanels] + ); + + const loadCompleteHandler = useCallback( + (controlGroup: ControlGroupRendererApi) => { + if (!controlGroup) return; + + subscriptions.current.add( + controlGroup.filters$.pipe(skip(1)).subscribe((newFilters = []) => { + setSearchState((state) => ({ ...state, controlFilters: newFilters })); + }) + ); + + subscriptions.current.add( + controlGroup.getInput$().subscribe(({ initialChildControlState }) => { + if (!isControlPanelsInitiated) { + setIsControlPanelsInitiated(true); + } + setControlPanels(initialChildControlState); + }) + ); + }, + [isControlPanelsInitiated, setControlPanels, setIsControlPanelsInitiated, setSearchState] + ); + + useEffect(() => { + const currentSubscriptions = subscriptions.current; + return () => { + currentSubscriptions.unsubscribe(); + }; + }, []); + + if (!dataView) { + return null; + } + + return ( +
+ +
+ ); +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts new file mode 100644 index 0000000000000..75ee719f5ab34 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts @@ -0,0 +1,118 @@ +/* + * 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 * as t from 'io-ts'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { pick } from 'lodash'; + +const HOST_FILTERS_URL_STATE_KEY = 'controlPanels'; + +const PanelRT = t.intersection([ + t.type({ + order: t.number, + type: t.string, + }), + t.partial({ + width: t.union([t.literal('medium'), t.literal('small'), t.literal('large')]), + grow: t.boolean, + dataViewId: t.string, + fieldName: t.string, + title: t.union([t.string, t.undefined]), + selectedOptions: t.array(t.string), + }), +]); + +const ControlPanelsRT = t.record(t.string, PanelRT); +type ControlPanels = t.TypeOf; + +const cleanControlPanels = (controlPanels: ControlPanels) => { + return Object.entries(controlPanels).reduce((acc, [key, controlPanelConfig]) => { + const { dataViewId, ...rest } = controlPanelConfig; + return { + ...acc, + [key]: rest, + }; + }, {}); +}; +const encodeUrlState = (value: ControlPanels) => { + if (value) { + // Remove the dataView.id on update to make the control panels portable between data views + const cleanPanels = cleanControlPanels(value); + + return ControlPanelsRT.encode(cleanPanels); + } +}; + +const decodeUrlState = (value: unknown) => { + return pipe(ControlPanelsRT.decode(value), fold(constant(undefined), identity)); +}; + +const controlPanelDefinitions: ControlPanels = { + 'entity.type': { + order: 0, + type: 'optionsListControl', + fieldName: 'entity.type', + width: 'small', + grow: false, + title: 'Type', + }, +}; + +const controlPanelDefinitionKeys = Object.keys(controlPanelDefinitions); + +export function useControlPanels(dataView?: DataView) { + const visibleControlPanels = controlPanelDefinitionKeys + .filter((key) => dataView?.fields.getByName(key) !== undefined) + .reduce( + (acc, currKey) => ({ + ...acc, + [currKey]: controlPanelDefinitions[currKey], + }), + {} + ); + + const [controlPanels, setControlPanels] = useUrlState({ + defaultState: visibleControlPanels, + decodeUrlState, + encodeUrlState, + urlStateKey: HOST_FILTERS_URL_STATE_KEY, + writeDefaultState: true, + }); + + const mergedControlPanels = mergeVisibleControlPanelsWithUrl({ + visibleControlPanels, + urlControlPanels: controlPanels, + }); + + const controlPanelsWithDataView = Object.keys(mergedControlPanels).reduce( + (acc, currKey) => ({ + ...acc, + [currKey]: { ...mergedControlPanels[currKey], dataViewId: dataView?.id }, + }), + {} + ); + + return { + controlPanels: controlPanelsWithDataView, + setControlPanels, + }; +} + +function mergeVisibleControlPanelsWithUrl({ + visibleControlPanels, + urlControlPanels, +}: { + visibleControlPanels: ControlPanels; + urlControlPanels: ControlPanels; +}) { + const existingKeys = Object.keys(visibleControlPanels); + const controlPanelsToOverride = pick(urlControlPanels, existingKeys); + return { ...visibleControlPanels, ...controlPanelsToOverride }; +} diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 2fd450aab30dd..576224214f49c 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -4,22 +4,23 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; import deepEqual from 'fast-deep-equal'; import React, { useCallback, useEffect } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Query } from '@kbn/es-query'; import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; -import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view'; -import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useKibana } from '../../hooks/use_kibana'; -import { EntityTypesControls } from './entity_types_controls'; -import { DiscoverButton } from './discover_button'; +import { useUnifiedSearch } from '../../hooks/use_unified_search'; import { getKqlFieldsWithFallback } from '../../utils/get_kql_field_names_with_fallback'; +import { ControlGroups } from './control_groups'; +import { DiscoverButton } from './discover_button'; export function SearchBar() { - const { searchBarContentSubject$, refreshSubject$ } = useInventorySearchBarContext(); + const { refreshSubject$, dataView } = useInventorySearchBarContext(); + const { searchState, setSearchState } = useUnifiedSearch(); + const { services: { unifiedSearch, @@ -30,84 +31,47 @@ export function SearchBar() { }, } = useKibana(); - const { - query: { kuery, entityTypes }, - } = useInventoryParams('/*'); - const { SearchBar: UnifiedSearchBar } = unifiedSearch.ui; - const { dataView } = useAdHocInventoryDataView(); - const syncSearchBarWithUrl = useCallback(() => { - const query = kuery ? { query: kuery, language: 'kuery' } : undefined; + const { query } = searchState; if (query && !deepEqual(queryStringService.getQuery(), query)) { queryStringService.setQuery(query); } - if (!query) { + if (!query || query.query === '') { queryStringService.clearQuery(); } - }, [kuery, queryStringService]); + }, [queryStringService, searchState]); useEffect(() => { syncSearchBarWithUrl(); }, [syncSearchBarWithUrl]); const registerSearchSubmittedEvent = useCallback( - ({ - searchQuery, - searchIsUpdate, - searchEntityTypes, - }: { - searchQuery?: Query; - searchEntityTypes?: string[]; - searchIsUpdate?: boolean; - }) => { + ({ searchQuery, searchIsUpdate }: { searchQuery?: Query; searchIsUpdate?: boolean }) => { telemetry.reportEntityInventorySearchQuerySubmitted({ kuery_fields: getKqlFieldsWithFallback(searchQuery?.query as string), - entity_types: searchEntityTypes || [], action: searchIsUpdate ? 'submit' : 'refresh', }); }, [telemetry] ); - const registerEntityTypeFilteredEvent = useCallback( - ({ filterEntityTypes, filterKuery }: { filterEntityTypes: string[]; filterKuery?: string }) => { - telemetry.reportEntityInventoryEntityTypeFiltered({ - entity_types: filterEntityTypes, - kuery_fields: filterKuery ? getKqlFieldsWithFallback(filterKuery) : [], - }); - }, - [telemetry] - ); - - const handleEntityTypesChange = useCallback( - (nextEntityTypes: string[]) => { - searchBarContentSubject$.next({ kuery, entityTypes: nextEntityTypes }); - registerEntityTypeFilteredEvent({ filterEntityTypes: nextEntityTypes, filterKuery: kuery }); - }, - [kuery, registerEntityTypeFilteredEvent, searchBarContentSubject$] - ); - const handleQuerySubmit = useCallback>( - ({ query }, isUpdate) => { - searchBarContentSubject$.next({ - kuery: query?.query as string, - entityTypes, - }); - - registerSearchSubmittedEvent({ - searchQuery: query, - searchEntityTypes: entityTypes, - searchIsUpdate: isUpdate, - }); - + ({ query = { language: 'kuery', query: '' } }, isUpdate) => { if (!isUpdate) { refreshSubject$.next(); + } else { + setSearchState((state) => ({ ...state, query })); + + registerSearchSubmittedEvent({ + searchQuery: query, + searchIsUpdate: isUpdate, + }); } }, - [entityTypes, registerSearchSubmittedEvent, searchBarContentSubject$, refreshSubject$] + [setSearchState, registerSearchSubmittedEvent, refreshSubject$] ); return ( @@ -117,9 +81,8 @@ export function SearchBar() { appName="Inventory" displayStyle="inPage" showDatePicker={false} - showFilterBar={false} indexPatterns={dataView ? [dataView] : undefined} - renderQueryInputAppend={() => } + renderQueryInputAppend={ControlGroups} onQuerySubmit={handleQuerySubmit} placeholder={i18n.translate('xpack.inventory.searchBar.placeholder', { defaultMessage: diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/unified_inventory.tsx b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx similarity index 78% rename from x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/unified_inventory.tsx rename to x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx index 05f7437a32c4b..6dedfe2668fdd 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/unified_inventory.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx @@ -5,21 +5,22 @@ * 2.0. */ import { EuiDataGridSorting } from '@elastic/eui'; +import { decodeOrThrow } from '@kbn/io-ts-utils'; import React from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { decodeOrThrow } from '@kbn/io-ts-utils'; import { - type EntityColumnIds, entityPaginationRt, + type EntityColumnIds, type EntityPagination, } from '../../../common/entities'; -import { EntitiesGrid } from '../entities_grid'; +import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useInventoryRouter } from '../../hooks/use_inventory_router'; import { useKibana } from '../../hooks/use_kibana'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; -import { InventorySummary } from './inventory_summary'; +import { EntitiesGrid } from '../entities_grid'; +import { useUnifiedSearch } from '../../hooks/use_unified_search'; +import { InventorySummary } from '../grouped_inventory/inventory_summary'; const paginationDecoder = decodeOrThrow(entityPaginationRt); @@ -27,9 +28,11 @@ export function UnifiedInventory() { const { services: { inventoryAPIClient }, } = useKibana(); - const { refreshSubject$ } = useInventorySearchBarContext(); + const { refreshSubject$, isControlPanelsInitiated } = useInventorySearchBarContext(); const { query } = useInventoryParams('/'); - const { sortDirection, sortField, kuery, entityTypes, pagination: paginationQuery } = query; + const { sortDirection, sortField, pagination: paginationQuery } = query; + const { stringifiedEsQuery } = useUnifiedSearch(); + let pagination: EntityPagination | undefined = {}; const inventoryRoute = useInventoryRouter(); try { @@ -38,9 +41,7 @@ export function UnifiedInventory() { inventoryRoute.push('/', { path: {}, query: { - sortField, - sortDirection, - kuery, + ...query, pagination: undefined, }, }); @@ -55,24 +56,24 @@ export function UnifiedInventory() { refresh, } = useInventoryAbortableAsync( ({ signal }) => { - return inventoryAPIClient.fetch('GET /internal/inventory/entities', { - params: { - query: { - sortDirection, - sortField, - entityTypes: entityTypes?.length ? JSON.stringify(entityTypes) : undefined, - kuery, + if (isControlPanelsInitiated) { + return inventoryAPIClient.fetch('GET /internal/inventory/entities', { + params: { + query: { + sortDirection, + sortField, + esQuery: stringifiedEsQuery, + }, }, - }, - signal, - }); + signal, + }); + } }, - [entityTypes, inventoryAPIClient, kuery, sortDirection, sortField] + [inventoryAPIClient, sortDirection, sortField, isControlPanelsInitiated, stringifiedEsQuery] ); useEffectOnce(() => { const refreshSubscription = refreshSubject$.subscribe(refresh); - return () => refreshSubscription.unsubscribe(); }); @@ -102,13 +103,13 @@ export function UnifiedInventory() { function handleTypeFilter(type: string) { const { pagination: _, ...rest } = query; - + // TODO: caue check it inventoryRoute.push('/', { path: {}, query: { ...rest, // Override the current entity types - entityTypes: [type], + // entityTypes: [type], }, }); } diff --git a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx index eb5a2a057e529..fa2bca2228777 100644 --- a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx @@ -4,26 +4,36 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { createContext, useContext, type ReactChild } from 'react'; +import React, { createContext, useContext, useState, type ReactChild } from 'react'; import { Subject } from 'rxjs'; +import { DataView } from '@kbn/data-views-plugin/common'; +import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view'; interface InventorySearchBarContextType { - searchBarContentSubject$: Subject<{ - kuery?: string; - entityTypes?: string[]; - }>; refreshSubject$: Subject; + isControlPanelsInitiated: boolean; + setIsControlPanelsInitiated: React.Dispatch>; + dataView?: DataView; } const InventorySearchBarContext = createContext({ - searchBarContentSubject$: new Subject(), refreshSubject$: new Subject(), + isControlPanelsInitiated: false, + setIsControlPanelsInitiated: () => {}, }); export function InventorySearchBarContextProvider({ children }: { children: ReactChild }) { + const [isControlPanelsInitiated, setIsControlPanelsInitiated] = useState(false); + const { dataView } = useAdHocInventoryDataView(); + return ( {children} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts new file mode 100644 index 0000000000000..a067db2cf21cf --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts @@ -0,0 +1,146 @@ +/* + * 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 { buildEsQuery, FilterStateStore, type Query } from '@kbn/es-query'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; +import { enumeration } from '@kbn/securitysolution-io-ts-types'; +import deepEqual from 'fast-deep-equal'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as t from 'io-ts'; +import { useEffect, useMemo } from 'react'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { map, Subscription, tap } from 'rxjs'; +import { useInventorySearchBarContext } from '../context/inventory_search_bar_context_provider'; +import { useKibana } from './use_kibana'; + +const FilterRT = t.intersection([ + t.type({ + meta: t.partial({ + alias: t.union([t.null, t.string]), + disabled: t.boolean, + negate: t.boolean, + controlledBy: t.string, + group: t.string, + index: t.string, + isMultiIndex: t.boolean, + type: t.string, + key: t.string, + params: t.any, + value: t.any, + }), + }), + t.partial({ + query: t.record(t.string, t.any), + $state: t.type({ + store: enumeration('FilterStateStore', FilterStateStore), + }), + }), +]); +const FiltersRT = t.array(FilterRT); + +const QueryStateRT = t.type({ + language: t.string, + query: t.union([t.string, t.record(t.string, t.any)]), +}); + +const SearchStateRT = t.type({ + controlFilters: FiltersRT, + filters: FiltersRT, + query: QueryStateRT, +}); + +export const encodeUrlState = SearchStateRT.encode; +const decodeUrlState = (value: unknown) => { + return pipe(SearchStateRT.decode(value), fold(constant(undefined), identity)); +}; + +type SearchState = t.TypeOf; + +const INITIAL_VALUE: SearchState = { + query: { language: 'kuery', query: '' }, + controlFilters: [], + filters: [], +}; + +export function useUnifiedSearch() { + const { + services: { + data: { + query: { filterManager: filterManagerService, queryString: queryStringService }, + }, + }, + } = useKibana(); + + const [searchState, setSearchState] = useUrlState({ + defaultState: INITIAL_VALUE, + decodeUrlState, + encodeUrlState, + urlStateKey: '_a', + writeDefaultState: true, + }); + const { dataView } = useInventorySearchBarContext(); + + useEffectOnce(() => { + if (!deepEqual(filterManagerService.getFilters(), searchState.filters)) { + filterManagerService.setFilters( + searchState.filters.map((item) => ({ + ...item, + meta: { ...item.meta, index: dataView?.id }, + })) + ); + } + + if (!deepEqual(queryStringService.getQuery(), searchState.query)) { + queryStringService.setQuery(searchState.query); + } + }); + + useEffect(() => { + const subscription = new Subscription(); + subscription.add( + filterManagerService + .getUpdates$() + .pipe( + map(() => filterManagerService.getFilters()), + tap((filters) => setSearchState((state) => ({ ...state, filters }))) + ) + .subscribe() + ); + + subscription.add( + queryStringService + .getUpdates$() + .pipe( + map(() => queryStringService.getQuery() as Query), + tap((query) => setSearchState((state) => ({ ...state, query }))) + ) + .subscribe() + ); + + return () => { + subscription.unsubscribe(); + }; + }, [filterManagerService, queryStringService, setSearchState]); + + const stringifiedEsQuery = useMemo(() => { + if (dataView) { + return JSON.stringify( + buildEsQuery(dataView, searchState.query, [ + ...searchState.controlFilters, + ...searchState.filters, + ]) + ); + } + }, [dataView, searchState.controlFilters, searchState.filters, searchState.query]); + + return { + searchState, + setSearchState, + stringifiedEsQuery, + }; +} diff --git a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx index 03f8b6475175a..f34df1a3c8b32 100644 --- a/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/pages/inventory_page/index.tsx @@ -4,36 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import React, { useEffect } from 'react'; -import { useInventoryParams } from '../../hooks/use_inventory_params'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; -import { useInventoryRouter } from '../../hooks/use_inventory_router'; -import { UnifiedInventory } from '../../components/grouped_inventory/unified_inventory'; +import React from 'react'; import { GroupedInventory } from '../../components/grouped_inventory'; +import { UnifiedInventory } from '../../components/unified_inventory'; +import { useInventoryParams } from '../../hooks/use_inventory_params'; export function InventoryPage() { - const { searchBarContentSubject$ } = useInventorySearchBarContext(); - const inventoryRoute = useInventoryRouter(); const { query } = useInventoryParams('/'); - - useEffect(() => { - const searchBarContentSubscription = searchBarContentSubject$.subscribe( - ({ ...queryParams }) => { - const { pagination: _, ...rest } = query; - - inventoryRoute.push('/', { - path: {}, - query: { ...rest, ...queryParams }, - }); - } - ); - return () => { - searchBarContentSubscription.unsubscribe(); - }; - // If query has updated, the inventoryRoute state is also updated - // as well, so we only need to track changes on query. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [query, searchBarContentSubject$]); - return query.view === 'unified' ? : ; } diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index 36a15c5ae542c..bf5f8324aab25 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -9,12 +9,7 @@ import * as t from 'io-ts'; import React from 'react'; import { InventoryPageTemplate } from '../components/inventory_page_template'; import { InventoryPage } from '../pages/inventory_page'; -import { - defaultEntitySortField, - entityTypesRt, - entityColumnIdsRt, - entityViewRt, -} from '../../common/entities'; +import { defaultEntitySortField, entityColumnIdsRt, entityViewRt } from '../../common/entities'; /** * The array of route definitions to be used when the application @@ -34,10 +29,10 @@ const inventoryRoutes = { sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), t.partial({ - entityTypes: entityTypesRt, - kuery: t.string, view: entityViewRt, pagination: t.string, + _a: t.string, + controlPanels: t.string, }), ]), }), diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts index 54d20ea324b11..c4c238fba5f8f 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_client.ts @@ -14,7 +14,6 @@ import { type EntityInventoryViewedParams, type EntityInventorySearchQuerySubmittedParams, type EntityViewClickedParams, - type EntityInventoryEntityTypeFilteredParams, } from './types'; export class TelemetryClient implements ITelemetryClient { @@ -34,12 +33,6 @@ export class TelemetryClient implements ITelemetryClient { this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_SEARCH_QUERY_SUBMITTED, params); }; - public reportEntityInventoryEntityTypeFiltered = ( - params: EntityInventoryEntityTypeFilteredParams - ) => { - this.analytics.reportEvent(TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, params); - }; - public reportEntityViewClicked = (params: EntityViewClickedParams) => { this.analytics.reportEvent(TelemetryEventTypes.ENTITY_VIEW_CLICKED, params); }; diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts index d61a90f7d30ab..ec2623fe2a2cc 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_events.ts @@ -49,15 +49,6 @@ const searchQuerySubmittedEventType: TelemetryEvent = { }, }, }, - entity_types: { - type: 'array', - items: { - type: 'keyword', - _meta: { - description: 'Entity types used in the search.', - }, - }, - }, action: { type: 'keyword', _meta: { @@ -67,30 +58,6 @@ const searchQuerySubmittedEventType: TelemetryEvent = { }, }; -const entityInventoryEntityTypeFilteredEventType: TelemetryEvent = { - eventType: TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, - schema: { - entity_types: { - type: 'array', - items: { - type: 'keyword', - _meta: { - description: 'Entity types used in the filter.', - }, - }, - }, - kuery_fields: { - type: 'array', - items: { - type: 'text', - _meta: { - description: 'Kuery fields used in the filter.', - }, - }, - }, - }, -}; - const entityViewClickedEventType: TelemetryEvent = { eventType: TelemetryEventTypes.ENTITY_VIEW_CLICKED, schema: { @@ -113,6 +80,5 @@ export const inventoryTelemetryEventBasedTypes = [ inventoryAddDataEventType, entityInventoryViewedEventType, searchQuerySubmittedEventType, - entityInventoryEntityTypeFilteredEventType, entityViewClickedEventType, ]; diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts index 415cf0e7d4406..639b771788f5b 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/telemetry_service.test.ts @@ -13,7 +13,6 @@ import { type EntityViewClickedParams, type EntityInventorySearchQuerySubmittedParams, TelemetryEventTypes, - type EntityInventoryEntityTypeFilteredParams, } from './types'; describe('TelemetryService', () => { @@ -115,7 +114,6 @@ describe('TelemetryService', () => { const params: EntityInventorySearchQuerySubmittedParams = { kuery_fields: ['_index'], action: 'submit', - entity_types: ['container'], }; telemetry.reportEntityInventorySearchQuerySubmitted(params); @@ -128,26 +126,6 @@ describe('TelemetryService', () => { }); }); - describe('#reportEntityInventoryEntityTypeFiltered', () => { - it('should report entity type filtered with properties', async () => { - const setupParams = getSetupParams(); - service.setup(setupParams); - const telemetry = service.start(); - const params: EntityInventoryEntityTypeFilteredParams = { - kuery_fields: ['_index'], - entity_types: ['container'], - }; - - telemetry.reportEntityInventoryEntityTypeFiltered(params); - - expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1); - expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith( - TelemetryEventTypes.ENTITY_INVENTORY_ENTITY_TYPE_FILTERED, - params - ); - }); - }); - describe('#reportEntityViewClicked', () => { it('should report entity view clicked with properties', async () => { const setupParams = getSetupParams(); diff --git a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts index 0e52d115d4597..0d56f44c2c2f2 100644 --- a/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts +++ b/x-pack/plugins/observability_solution/inventory/public/services/telemetry/types.ts @@ -27,15 +27,9 @@ export interface EntityInventoryViewedParams { export interface EntityInventorySearchQuerySubmittedParams { kuery_fields: string[]; - entity_types: string[]; action: 'submit' | 'refresh'; } -export interface EntityInventoryEntityTypeFilteredParams { - kuery_fields: string[]; - entity_types: string[]; -} - export interface EntityViewClickedParams { entity_type: string; view_type: 'detail' | 'flyout'; @@ -45,7 +39,6 @@ export type TelemetryEventParams = | InventoryAddDataParams | EntityInventoryViewedParams | EntityInventorySearchQuerySubmittedParams - | EntityInventoryEntityTypeFilteredParams | EntityViewClickedParams; export interface ITelemetryClient { @@ -54,7 +47,6 @@ export interface ITelemetryClient { reportEntityInventorySearchQuerySubmitted( params: EntityInventorySearchQuerySubmittedParams ): void; - reportEntityInventoryEntityTypeFiltered(params: EntityInventoryEntityTypeFilteredParams): void; reportEntityViewClicked(params: EntityViewClickedParams): void; } diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts index b61f245f1aaf2..8c72e18bc0740 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_entity_groups.ts @@ -5,11 +5,9 @@ * 2.0. */ +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import type { ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; -import { kqlQuery } from '@kbn/observability-utils/es/queries/kql_query'; import { esqlResultToPlainObjects } from '@kbn/observability-utils/es/utils/esql_result_to_plain_objects'; -import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import { ScalarValue } from '@elastic/elasticsearch/lib/api/types'; import { ENTITIES_LATEST_ALIAS, type EntityGroup, @@ -20,38 +18,23 @@ import { getBuiltinEntityDefinitionIdESQLWhereClause } from './query_helper'; export async function getEntityGroupsBy({ inventoryEsClient, field, - kuery, - entityTypes, + esQuery, }: { inventoryEsClient: ObservabilityElasticsearchClient; field: string; - kuery?: string; - entityTypes?: string[]; + esQuery?: QueryDslQueryContainer; }) { const from = `FROM ${ENTITIES_LATEST_ALIAS}`; const where = [getBuiltinEntityDefinitionIdESQLWhereClause()]; - const params: ScalarValue[] = []; - if (entityTypes) { - where.push(`WHERE ${ENTITY_TYPE} IN (${entityTypes.map(() => '?').join()})`); - params.push(...entityTypes); - } - - // STATS doesn't support parameterisation. const group = `STATS count = COUNT(*) by ${field}`; const sort = `SORT ${field} asc`; - // LIMIT doesn't support parameterisation. const limit = `LIMIT ${MAX_NUMBER_OF_ENTITIES}`; const query = [from, ...where, group, sort, limit].join(' | '); const groups = await inventoryEsClient.esql('get_entities_groups', { query, - filter: { - bool: { - filter: kqlQuery(kuery), - }, - }, - params, + filter: esQuery, }); return esqlResultToPlainObjects(groups); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts index c95a488ad49dd..402d11720a9da 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities.ts @@ -5,11 +5,10 @@ * 2.0. */ +import type { QueryDslQueryContainer, ScalarValue } from '@elastic/elasticsearch/lib/api/types'; +import { ENTITY_LAST_SEEN, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import { type ObservabilityElasticsearchClient } from '@kbn/observability-utils/es/client/create_observability_es_client'; -import { kqlQuery } from '@kbn/observability-utils/es/queries/kql_query'; import { esqlResultToPlainObjects } from '@kbn/observability-utils/es/utils/esql_result_to_plain_objects'; -import { ENTITY_LAST_SEEN, ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; -import type { ScalarValue } from '@elastic/elasticsearch/lib/api/types'; import { ENTITIES_LATEST_ALIAS, MAX_NUMBER_OF_ENTITIES, @@ -22,14 +21,14 @@ export async function getLatestEntities({ inventoryEsClient, sortDirection, sortField, + esQuery, entityTypes, - kuery, }: { inventoryEsClient: ObservabilityElasticsearchClient; sortDirection: 'asc' | 'desc'; sortField: EntityColumnIds; + esQuery?: QueryDslQueryContainer; entityTypes?: string[]; - kuery?: string; }) { // alertsCount doesn't exist in entities index. Ignore it and sort by entity.lastSeenTimestamp by default. const entitiesSortField = sortField === 'alertsCount' ? ENTITY_LAST_SEEN : sortField; @@ -50,11 +49,7 @@ export async function getLatestEntities({ const latestEntitiesEsqlResponse = await inventoryEsClient.esql('get_latest_entities', { query, - filter: { - bool: { - filter: [...kqlQuery(kuery)], - }, - }, + filter: esQuery, params, }); diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts index 88d6cb68ee214..714bbb1f0abf2 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts @@ -47,8 +47,8 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ sortDirection: t.union([t.literal('asc'), t.literal('desc')]), }), t.partial({ + esQuery: jsonRt.pipe(t.UnknownRecord), entityTypes: jsonRt.pipe(t.array(t.string)), - kuery: t.string, }), ]), }), @@ -69,7 +69,7 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ plugin: `@kbn/${INVENTORY_APP_ID}-plugin`, }); - const { sortDirection, sortField, entityTypes, kuery } = params.query; + const { sortDirection, sortField, esQuery, entityTypes } = params.query; const [alertsClient, latestEntities] = await Promise.all([ createAlertsClient({ plugins, request }), @@ -77,8 +77,8 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ inventoryEsClient, sortDirection, sortField, + esQuery, entityTypes, - kuery, }), ]); @@ -87,7 +87,8 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ const alerts = await getLatestEntitiesAlerts({ identityFieldsPerEntityType, alertsClient, - kuery, + // TODO: caue fix this + kuery: '', }); const joined = joinByKey( @@ -114,8 +115,7 @@ export const groupEntitiesByRoute = createInventoryServerRoute({ t.type({ path: t.type({ field: t.literal(ENTITY_TYPE) }) }), t.partial({ query: t.partial({ - kuery: t.string, - entityTypes: jsonRt.pipe(t.array(t.string)), + esQuery: jsonRt.pipe(t.UnknownRecord), }), }), ]), @@ -131,13 +131,12 @@ export const groupEntitiesByRoute = createInventoryServerRoute({ }); const { field } = params.path; - const { kuery, entityTypes } = params.query ?? {}; + const { esQuery } = params.query ?? {}; const groups = await getEntityGroupsBy({ inventoryEsClient, field, - kuery, - entityTypes, + esQuery, }); const entitiesCount = groups.reduce((acc, group) => acc + group.count, 0); diff --git a/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_url_state.ts b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_url_state.ts new file mode 100644 index 0000000000000..ac18fbe413005 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_url_state.ts @@ -0,0 +1,142 @@ +/* + * 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 { parse, stringify } from 'query-string'; +import { Location } from 'history'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { decode, encode, RisonValue } from '@kbn/rison'; +import { useHistory } from 'react-router-dom'; +import { url } from '@kbn/kibana-utils-plugin/common'; + +export const useUrlState = ({ + defaultState, + decodeUrlState, + encodeUrlState, + urlStateKey, + writeDefaultState = false, +}: { + defaultState: State; + decodeUrlState: (value: RisonValue | undefined) => State | undefined; + encodeUrlState: (value: State) => RisonValue | undefined; + urlStateKey: string; + writeDefaultState?: boolean; +}) => { + const history = useHistory(); + + // history.location is mutable so we can't reliably use useMemo + const queryString = history?.location ? getQueryStringFromLocation(history.location) : ''; + + const urlStateString = useMemo(() => { + if (!queryString) { + return; + } + + return getParamFromQueryString(queryString, urlStateKey); + }, [queryString, urlStateKey]); + + const decodedState = useMemo(() => { + return decodeUrlState(decodeRisonUrlState(urlStateString)); + }, [decodeUrlState, urlStateString]); + + const state = useMemo(() => { + return typeof decodedState !== 'undefined' ? decodedState : defaultState; + }, [defaultState, decodedState]); + + const setState = useCallback( + (patch: State | undefined | ((prevState: State) => State)) => { + if (!history || !history.location) { + return; + } + + const currentLocation = history.location; + + const newState = + patch instanceof Function + ? patch( + decodeUrlState( + decodeRisonUrlState( + getParamFromQueryString(getQueryStringFromLocation(currentLocation), urlStateKey) + ) + ) ?? defaultState + ) + : patch; + + const newLocation = replaceQueryStringInLocation( + currentLocation, + replaceStateKeyInQueryString( + urlStateKey, + typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined + )(getQueryStringFromLocation(currentLocation)) + ); + + if (newLocation !== currentLocation) { + history.replace(newLocation); + } + }, + [decodeUrlState, defaultState, encodeUrlState, history, urlStateKey] + ); + + const [shouldInitialize, setShouldInitialize] = useState( + writeDefaultState && typeof decodedState === 'undefined' + ); + + useEffect(() => { + if (shouldInitialize) { + setShouldInitialize(false); + setState(defaultState); + } + }, [shouldInitialize, setState, defaultState]); + + return [state, setState] as [typeof state, typeof setState]; +}; + +export const decodeRisonUrlState = (value: string | undefined | null): RisonValue | undefined => { + try { + return value ? decode(value) : undefined; + } catch (error) { + if (error instanceof Error && error.message.startsWith('rison decoder error')) { + return {}; + } + throw error; + } +}; + +const getQueryStringFromLocation = (location: Location) => location.search.substring(1); + +const getParamFromQueryString = (queryString: string, key: string) => { + const parsedQueryString = parse(queryString, { sort: false }); + const queryParam = parsedQueryString[key]; + + return Array.isArray(queryParam) ? queryParam[0] : queryParam; +}; + +const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { + if (queryString === getQueryStringFromLocation(location)) { + return location; + } else { + return { + ...location, + search: `?${queryString}`, + }; + } +}; + +const encodeRisonUrlState = (state: any) => encode(state); + +const replaceStateKeyInQueryString = + (stateKey: string, urlState: UrlState | undefined) => + (queryString: string) => { + const previousQueryValues = parse(queryString, { sort: false }); + const newValue = + typeof urlState === 'undefined' + ? previousQueryValues + : { + ...previousQueryValues, + [stateKey]: encodeRisonUrlState(urlState), + }; + return stringify(url.encodeQuery(newValue), { sort: false, encode: false }); + }; diff --git a/x-pack/plugins/observability_solution/observability_shared/public/index.ts b/x-pack/plugins/observability_solution/observability_shared/public/index.ts index d732a669e45bd..bf9d2b98c298e 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/index.ts @@ -104,3 +104,5 @@ export { BottomBarActions } from './components/bottom_bar_actions/bottom_bar_act export { FieldValueSelection, FieldValueSuggestions } from './components'; export { AddDataPanel, type AddDataPanelProps } from './components/add_data_panel'; + +export { useUrlState } from './hooks/use_url_state'; From 4a488cf0a9ed61af4e21280228705dca1375ea48 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Wed, 6 Nov 2024 14:48:22 +0000 Subject: [PATCH 02/22] more changes --- .../public/components/search_bar/index.tsx | 14 +++++++------- .../index.tsx | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 576224214f49c..4ffc27f5fcb81 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -62,14 +62,14 @@ export function SearchBar() { ({ query = { language: 'kuery', query: '' } }, isUpdate) => { if (!isUpdate) { refreshSubject$.next(); - } else { - setSearchState((state) => ({ ...state, query })); - - registerSearchSubmittedEvent({ - searchQuery: query, - searchIsUpdate: isUpdate, - }); } + + setSearchState((state) => ({ ...state, query })); + + registerSearchSubmittedEvent({ + searchQuery: query, + searchIsUpdate: isUpdate, + }); }, [setSearchState, registerSearchSubmittedEvent, refreshSubject$] ); diff --git a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx index fa2bca2228777..05c7655474437 100644 --- a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx @@ -25,11 +25,12 @@ const InventorySearchBarContext = createContext({ export function InventorySearchBarContextProvider({ children }: { children: ReactChild }) { const [isControlPanelsInitiated, setIsControlPanelsInitiated] = useState(false); const { dataView } = useAdHocInventoryDataView(); + const [refreshSubject$] = useState>(new Subject()); return ( Date: Thu, 7 Nov 2024 10:56:53 +0000 Subject: [PATCH 03/22] filtering on grid --- .../badge_filter_with_popover/index.tsx | 121 ++++++++++-------- .../public/components/entities_grid/index.tsx | 13 +- .../search_bar/control_groups/index.tsx | 1 + .../public/components/search_bar/index.tsx | 35 +---- .../public/hooks/use_unified_search.ts | 26 ++++ 5 files changed, 105 insertions(+), 91 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx index d1e952e189d6e..6205135fdfd8c 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx @@ -8,28 +8,29 @@ import { EuiBadge, EuiButtonEmpty, - EuiFlexGrid, EuiFlexGroup, EuiFlexItem, EuiPopover, EuiPopoverFooter, + EuiPopoverTitle, copyToClipboard, useEuiTheme, } from '@elastic/eui'; import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; +import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import React, { useState } from 'react'; +import { useUnifiedSearch } from '../../hooks/use_unified_search'; interface Props { field: string; value: string; - label?: string; - onFilter: () => void; } -export function BadgeFilterWithPopover({ field, value, onFilter, label }: Props) { +export function BadgeFilterWithPopover({ field, value }: Props) { const [isOpen, setIsOpen] = useState(false); const theme = useEuiTheme(); + const { addFilter } = useUnifiedSearch(); return ( - {label || value} + {value} } isOpen={isOpen} closePopover={() => setIsOpen(false)} + panelPaddingSize="s" > - - - - - {field}: - - - - {value} - - - + + + + + + {field}: + + + + {value} + + + + + + + { + addFilter({ fieldName: ENTITY_TYPE, operation: '+', value }); + }} + > + {i18n.translate('xpack.inventory.badgeFilterWithPopover.filterForButtonEmptyLabel', { + defaultMessage: 'Filter for', + })} + + + + { + addFilter({ fieldName: ENTITY_TYPE, operation: '-', value }); + }} + > + {i18n.translate('xpack.inventory.badgeFilterWithPopover.filterForButtonEmptyLabel', { + defaultMessage: 'Filter out', + })} + + + - - - - {i18n.translate('xpack.inventory.badgeFilterWithPopover.filterForButtonEmptyLabel', { - defaultMessage: 'Filter for', - })} - - - - copyToClipboard(value)} - > - {i18n.translate('xpack.inventory.badgeFilterWithPopover.copyValueButtonEmptyLabel', { - defaultMessage: 'Copy value', - })} - - - + copyToClipboard(value)} + > + {i18n.translate('xpack.inventory.badgeFilterWithPopover.copyValueButtonEmptyLabel', { + defaultMessage: 'Copy value', + })} + ); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx index e3c0d24837f91..8f88d17852c2c 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/index.tsx @@ -38,7 +38,6 @@ interface Props { pageIndex: number; onChangeSort: (sorting: EuiDataGridSorting['columns'][0]) => void; onChangePage: (nextPage: number) => void; - onFilterByType: (entityType: string) => void; } const PAGE_SIZE = 20; @@ -51,7 +50,6 @@ export function EntitiesGrid({ pageIndex, onChangePage, onChangeSort, - onFilterByType, }: Props) { const onSort: EuiDataGridSorting['onSort'] = useCallback( (newSortingColumns) => { @@ -91,14 +89,7 @@ export function EntitiesGrid({ return entity?.alertsCount ? : null; case ENTITY_TYPE: - return ( - onFilterByType(entityType)} - /> - ); + return ; case ENTITY_LAST_SEEN: return ( (new Subscription()); const getInitialInput = useCallback( diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 4ffc27f5fcb81..5a08dbd53e0bc 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -8,8 +8,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; -import deepEqual from 'fast-deep-equal'; -import React, { useCallback, useEffect } from 'react'; +import React, { useCallback } from 'react'; import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useKibana } from '../../hooks/use_kibana'; import { useUnifiedSearch } from '../../hooks/use_unified_search'; @@ -20,34 +19,14 @@ import { DiscoverButton } from './discover_button'; export function SearchBar() { const { refreshSubject$, dataView } = useInventorySearchBarContext(); const { searchState, setSearchState } = useUnifiedSearch(); + console.log('### caue SearchBar searchState:', searchState); const { - services: { - unifiedSearch, - data: { - query: { queryString: queryStringService }, - }, - telemetry, - }, + services: { unifiedSearch, telemetry }, } = useKibana(); const { SearchBar: UnifiedSearchBar } = unifiedSearch.ui; - const syncSearchBarWithUrl = useCallback(() => { - const { query } = searchState; - if (query && !deepEqual(queryStringService.getQuery(), query)) { - queryStringService.setQuery(query); - } - - if (!query || query.query === '') { - queryStringService.clearQuery(); - } - }, [queryStringService, searchState]); - - useEffect(() => { - syncSearchBarWithUrl(); - }, [syncSearchBarWithUrl]); - const registerSearchSubmittedEvent = useCallback( ({ searchQuery, searchIsUpdate }: { searchQuery?: Query; searchIsUpdate?: boolean }) => { telemetry.reportEntityInventorySearchQuerySubmitted({ @@ -60,12 +39,12 @@ export function SearchBar() { const handleQuerySubmit = useCallback>( ({ query = { language: 'kuery', query: '' } }, isUpdate) => { - if (!isUpdate) { + if (isUpdate) { + setSearchState((state) => ({ ...state, query })); + } else { refreshSubject$.next(); } - setSearchState((state) => ({ ...state, query })); - registerSearchSubmittedEvent({ searchQuery: query, searchIsUpdate: isUpdate, @@ -82,7 +61,7 @@ export function SearchBar() { displayStyle="inPage" showDatePicker={false} indexPatterns={dataView ? [dataView] : undefined} - renderQueryInputAppend={ControlGroups} + renderQueryInputAppend={() => } onQuerySubmit={handleQuerySubmit} placeholder={i18n.translate('xpack.inventory.searchBar.placeholder', { defaultMessage: diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts index a067db2cf21cf..a764efc845f43 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts @@ -15,6 +15,7 @@ import * as t from 'io-ts'; import { useEffect, useMemo } from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import { map, Subscription, tap } from 'rxjs'; +import { generateFilters } from '@kbn/data-plugin/public'; import { useInventorySearchBarContext } from '../context/inventory_search_bar_context_provider'; import { useKibana } from './use_kibana'; @@ -138,9 +139,34 @@ export function useUnifiedSearch() { } }, [dataView, searchState.controlFilters, searchState.filters, searchState.query]); + function addFilter({ + fieldName, + operation, + value, + }: { + fieldName: string; + value: string; + operation: '+' | '-'; + }) { + if (dataView) { + const newFilters = generateFilters( + filterManagerService, + fieldName, + value, + operation, + dataView + ); + setSearchState((state) => ({ + ...state, + filters: [...state.filters, ...newFilters], + })); + } + } + return { searchState, setSearchState, stringifiedEsQuery, + addFilter, }; } From 96bf6457f1b5f5888a7b830ed33fc05f83e64c88 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 12:47:09 +0000 Subject: [PATCH 04/22] updating --- .../hooks/use_unified_search_url_state.ts | 2 + .../public/components/app_root/index.tsx | 12 +- .../badge_filter_with_popover/index.tsx | 4 +- .../grouped_entities_grid.tsx | 30 +-- .../components/grouped_inventory/index.tsx | 7 +- .../search_bar/control_groups/index.tsx | 25 ++- .../public/components/search_bar/index.tsx | 37 +++- .../components/unified_inventory/index.tsx | 7 +- .../index.tsx | 50 ----- .../public/hooks/use_kibana_query_settings.ts | 31 ++++ .../public/hooks/use_unified_search.ts | 172 ------------------ .../hooks/use_unified_search_context.ts | 166 +++++++++++++++++ .../public/hooks/use_unified_search_url.ts | 100 ++++++++++ 13 files changed, 364 insertions(+), 279 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx create mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts delete mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts create mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index 4c96fd93524be..99aaeb8de9af7 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -79,9 +79,11 @@ export const useHostsUrlState = (): [HostsState, Dispatch] => urlStateKey: '_a', writeDefaultState: true, }); + console.log('### caue useHostsUrlState urlState:', urlState); const [search, setSearch] = useReducer(reducer, urlState); if (!deepEqual(search, urlState)) { + console.log('################################:', search); setUrlState(search); if (localStorageHostLimit !== search.limit) { setLocalStorageHostLimit(search.limit); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx index a638cbdf5dd1b..52f46268da2ef 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx @@ -12,12 +12,12 @@ import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; import React from 'react'; import { InventoryContextProvider } from '../../context/inventory_context_provider'; -import { InventorySearchBarContextProvider } from '../../context/inventory_search_bar_context_provider'; +import { KibanaEnvironment } from '../../hooks/use_kibana'; +import { UnifiedSearchProvider } from '../../hooks/use_unified_search_context'; import { inventoryRouter } from '../../routes/config'; import { InventoryServices } from '../../services/types'; import { InventoryStartDependencies } from '../../types'; import { HeaderActionMenuItems } from './header_action_menu'; -import { KibanaEnvironment } from '../../hooks/use_kibana'; export function AppRoot({ coreStart, @@ -43,12 +43,12 @@ export function AppRoot({ return ( - - + + - - + +
); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx index 6205135fdfd8c..83e0bb02e6d8d 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/index.tsx @@ -20,7 +20,7 @@ import { css } from '@emotion/react'; import { i18n } from '@kbn/i18n'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import React, { useState } from 'react'; -import { useUnifiedSearch } from '../../hooks/use_unified_search'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; interface Props { field: string; @@ -30,7 +30,7 @@ interface Props { export function BadgeFilterWithPopover({ field, value }: Props) { const [isOpen, setIsOpen] = useState(false); const theme = useEuiTheme(); - const { addFilter } = useUnifiedSearch(); + const { addFilter } = useUnifiedSearchContext(); return ( ); } diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx index 52ea2b4838f86..b939f0fa5c423 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/index.tsx @@ -8,10 +8,9 @@ import { EuiSpacer } from '@elastic/eui'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import React from 'react'; import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useKibana } from '../../hooks/use_kibana'; -import { useUnifiedSearch } from '../../hooks/use_unified_search'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; import { InventoryGroupAccordion } from './inventory_group_accordion'; import { InventorySummary } from './inventory_summary'; @@ -19,8 +18,8 @@ export function GroupedInventory() { const { services: { inventoryAPIClient }, } = useKibana(); - const { refreshSubject$, isControlPanelsInitiated } = useInventorySearchBarContext(); - const { stringifiedEsQuery } = useUnifiedSearch(); + const { refreshSubject$, isControlPanelsInitiated, stringifiedEsQuery } = + useUnifiedSearchContext(); const { value = { groupBy: ENTITY_TYPE, groups: [], entitiesCount: 0 }, diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx index e21df1a6de5d8..39d75686fbfb4 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx @@ -5,6 +5,7 @@ * 2.0. */ +import { css } from '@emotion/react'; import { ControlGroupRenderer, ControlGroupRendererApi, @@ -12,17 +13,18 @@ import { } from '@kbn/controls-plugin/public'; import React, { useCallback, useEffect, useRef } from 'react'; import { skip, Subscription } from 'rxjs'; -import { css } from '@emotion/react'; -import { useInventorySearchBarContext } from '../../../context/inventory_search_bar_context_provider'; -import { useUnifiedSearch } from '../../../hooks/use_unified_search'; +import { useUnifiedSearchContext } from '../../../hooks/use_unified_search_context'; import { useControlPanels } from './use_control_panels'; export function ControlGroups() { - const { isControlPanelsInitiated, setIsControlPanelsInitiated, dataView } = - useInventorySearchBarContext(); + const { + isControlPanelsInitiated, + setIsControlPanelsInitiated, + dataView, + searchState, + onControlFiltersChange, + } = useUnifiedSearchContext(); const { controlPanels, setControlPanels } = useControlPanels(dataView); - const { searchState, setSearchState } = useUnifiedSearch(); - console.log('### caue ControlGroups searchState:', searchState); const subscriptions = useRef(new Subscription()); const getInitialInput = useCallback( @@ -44,7 +46,7 @@ export function ControlGroups() { subscriptions.current.add( controlGroup.filters$.pipe(skip(1)).subscribe((newFilters = []) => { - setSearchState((state) => ({ ...state, controlFilters: newFilters })); + onControlFiltersChange(newFilters); }) ); @@ -57,7 +59,12 @@ export function ControlGroups() { }) ); }, - [isControlPanelsInitiated, setControlPanels, setIsControlPanelsInitiated, setSearchState] + [ + isControlPanelsInitiated, + onControlFiltersChange, + setControlPanels, + setIsControlPanelsInitiated, + ] ); useEffect(() => { diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 5a08dbd53e0bc..5a98e60ace7a8 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -8,25 +8,44 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import { SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; -import React, { useCallback } from 'react'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; +import React, { useCallback, useEffect } from 'react'; +import deepEqual from 'fast-deep-equal'; import { useKibana } from '../../hooks/use_kibana'; -import { useUnifiedSearch } from '../../hooks/use_unified_search'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; import { getKqlFieldsWithFallback } from '../../utils/get_kql_field_names_with_fallback'; import { ControlGroups } from './control_groups'; import { DiscoverButton } from './discover_button'; export function SearchBar() { - const { refreshSubject$, dataView } = useInventorySearchBarContext(); - const { searchState, setSearchState } = useUnifiedSearch(); - console.log('### caue SearchBar searchState:', searchState); + const { refreshSubject$, dataView, searchState, onQueryChange } = useUnifiedSearchContext(); const { - services: { unifiedSearch, telemetry }, + services: { + unifiedSearch, + telemetry, + data: { + query: { queryString: queryStringService }, + }, + }, } = useKibana(); const { SearchBar: UnifiedSearchBar } = unifiedSearch.ui; + const syncSearchBarWithUrl = useCallback(() => { + const query = searchState.query; + if (query && !deepEqual(queryStringService.getQuery(), query)) { + queryStringService.setQuery(query); + } + + if (!query) { + queryStringService.clearQuery(); + } + }, [searchState.query, queryStringService]); + + useEffect(() => { + syncSearchBarWithUrl(); + }, [syncSearchBarWithUrl]); + const registerSearchSubmittedEvent = useCallback( ({ searchQuery, searchIsUpdate }: { searchQuery?: Query; searchIsUpdate?: boolean }) => { telemetry.reportEntityInventorySearchQuerySubmitted({ @@ -40,7 +59,7 @@ export function SearchBar() { const handleQuerySubmit = useCallback>( ({ query = { language: 'kuery', query: '' } }, isUpdate) => { if (isUpdate) { - setSearchState((state) => ({ ...state, query })); + onQueryChange(query); } else { refreshSubject$.next(); } @@ -50,7 +69,7 @@ export function SearchBar() { searchIsUpdate: isUpdate, }); }, - [setSearchState, registerSearchSubmittedEvent, refreshSubject$] + [registerSearchSubmittedEvent, onQueryChange, refreshSubject$] ); return ( diff --git a/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx index 6dedfe2668fdd..ba0325cfc3c0f 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx @@ -13,13 +13,12 @@ import { type EntityColumnIds, type EntityPagination, } from '../../../common/entities'; -import { useInventorySearchBarContext } from '../../context/inventory_search_bar_context_provider'; import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useInventoryRouter } from '../../hooks/use_inventory_router'; import { useKibana } from '../../hooks/use_kibana'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; import { EntitiesGrid } from '../entities_grid'; -import { useUnifiedSearch } from '../../hooks/use_unified_search'; import { InventorySummary } from '../grouped_inventory/inventory_summary'; const paginationDecoder = decodeOrThrow(entityPaginationRt); @@ -28,10 +27,10 @@ export function UnifiedInventory() { const { services: { inventoryAPIClient }, } = useKibana(); - const { refreshSubject$, isControlPanelsInitiated } = useInventorySearchBarContext(); + const { refreshSubject$, isControlPanelsInitiated, stringifiedEsQuery } = + useUnifiedSearchContext(); const { query } = useInventoryParams('/'); const { sortDirection, sortField, pagination: paginationQuery } = query; - const { stringifiedEsQuery } = useUnifiedSearch(); let pagination: EntityPagination | undefined = {}; const inventoryRoute = useInventoryRouter(); diff --git a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx b/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx deleted file mode 100644 index 05c7655474437..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/context/inventory_search_bar_context_provider/index.tsx +++ /dev/null @@ -1,50 +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 React, { createContext, useContext, useState, type ReactChild } from 'react'; -import { Subject } from 'rxjs'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { useAdHocInventoryDataView } from '../../hooks/use_adhoc_inventory_data_view'; - -interface InventorySearchBarContextType { - refreshSubject$: Subject; - isControlPanelsInitiated: boolean; - setIsControlPanelsInitiated: React.Dispatch>; - dataView?: DataView; -} - -const InventorySearchBarContext = createContext({ - refreshSubject$: new Subject(), - isControlPanelsInitiated: false, - setIsControlPanelsInitiated: () => {}, -}); - -export function InventorySearchBarContextProvider({ children }: { children: ReactChild }) { - const [isControlPanelsInitiated, setIsControlPanelsInitiated] = useState(false); - const { dataView } = useAdHocInventoryDataView(); - const [refreshSubject$] = useState>(new Subject()); - - return ( - - {children} - - ); -} - -export function useInventorySearchBarContext() { - const context = useContext(InventorySearchBarContext); - if (!context) { - throw new Error('Context was not found'); - } - return context; -} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts new file mode 100644 index 0000000000000..521cd0142303b --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts @@ -0,0 +1,31 @@ +/* + * 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 { EsQueryConfig } from '@kbn/es-query'; +import { SerializableRecord } from '@kbn/utility-types'; +import { useMemo } from 'react'; +import { UI_SETTINGS } from '@kbn/data-plugin/public'; +import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; + +export const useKibanaQuerySettings = (): EsQueryConfig => { + const [allowLeadingWildcards] = useUiSetting$(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); + const [queryStringOptions] = useUiSetting$(UI_SETTINGS.QUERY_STRING_OPTIONS); + const [dateFormatTZ] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); + const [ignoreFilterIfFieldNotInIndex] = useUiSetting$( + UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX + ); + + return useMemo( + () => ({ + allowLeadingWildcards, + queryStringOptions, + dateFormatTZ, + ignoreFilterIfFieldNotInIndex, + }), + [allowLeadingWildcards, dateFormatTZ, ignoreFilterIfFieldNotInIndex, queryStringOptions] + ); +}; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts deleted file mode 100644 index a764efc845f43..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search.ts +++ /dev/null @@ -1,172 +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 { buildEsQuery, FilterStateStore, type Query } from '@kbn/es-query'; -import { useUrlState } from '@kbn/observability-shared-plugin/public'; -import { enumeration } from '@kbn/securitysolution-io-ts-types'; -import deepEqual from 'fast-deep-equal'; -import { fold } from 'fp-ts/lib/Either'; -import { constant, identity } from 'fp-ts/lib/function'; -import { pipe } from 'fp-ts/lib/pipeable'; -import * as t from 'io-ts'; -import { useEffect, useMemo } from 'react'; -import useEffectOnce from 'react-use/lib/useEffectOnce'; -import { map, Subscription, tap } from 'rxjs'; -import { generateFilters } from '@kbn/data-plugin/public'; -import { useInventorySearchBarContext } from '../context/inventory_search_bar_context_provider'; -import { useKibana } from './use_kibana'; - -const FilterRT = t.intersection([ - t.type({ - meta: t.partial({ - alias: t.union([t.null, t.string]), - disabled: t.boolean, - negate: t.boolean, - controlledBy: t.string, - group: t.string, - index: t.string, - isMultiIndex: t.boolean, - type: t.string, - key: t.string, - params: t.any, - value: t.any, - }), - }), - t.partial({ - query: t.record(t.string, t.any), - $state: t.type({ - store: enumeration('FilterStateStore', FilterStateStore), - }), - }), -]); -const FiltersRT = t.array(FilterRT); - -const QueryStateRT = t.type({ - language: t.string, - query: t.union([t.string, t.record(t.string, t.any)]), -}); - -const SearchStateRT = t.type({ - controlFilters: FiltersRT, - filters: FiltersRT, - query: QueryStateRT, -}); - -export const encodeUrlState = SearchStateRT.encode; -const decodeUrlState = (value: unknown) => { - return pipe(SearchStateRT.decode(value), fold(constant(undefined), identity)); -}; - -type SearchState = t.TypeOf; - -const INITIAL_VALUE: SearchState = { - query: { language: 'kuery', query: '' }, - controlFilters: [], - filters: [], -}; - -export function useUnifiedSearch() { - const { - services: { - data: { - query: { filterManager: filterManagerService, queryString: queryStringService }, - }, - }, - } = useKibana(); - - const [searchState, setSearchState] = useUrlState({ - defaultState: INITIAL_VALUE, - decodeUrlState, - encodeUrlState, - urlStateKey: '_a', - writeDefaultState: true, - }); - const { dataView } = useInventorySearchBarContext(); - - useEffectOnce(() => { - if (!deepEqual(filterManagerService.getFilters(), searchState.filters)) { - filterManagerService.setFilters( - searchState.filters.map((item) => ({ - ...item, - meta: { ...item.meta, index: dataView?.id }, - })) - ); - } - - if (!deepEqual(queryStringService.getQuery(), searchState.query)) { - queryStringService.setQuery(searchState.query); - } - }); - - useEffect(() => { - const subscription = new Subscription(); - subscription.add( - filterManagerService - .getUpdates$() - .pipe( - map(() => filterManagerService.getFilters()), - tap((filters) => setSearchState((state) => ({ ...state, filters }))) - ) - .subscribe() - ); - - subscription.add( - queryStringService - .getUpdates$() - .pipe( - map(() => queryStringService.getQuery() as Query), - tap((query) => setSearchState((state) => ({ ...state, query }))) - ) - .subscribe() - ); - - return () => { - subscription.unsubscribe(); - }; - }, [filterManagerService, queryStringService, setSearchState]); - - const stringifiedEsQuery = useMemo(() => { - if (dataView) { - return JSON.stringify( - buildEsQuery(dataView, searchState.query, [ - ...searchState.controlFilters, - ...searchState.filters, - ]) - ); - } - }, [dataView, searchState.controlFilters, searchState.filters, searchState.query]); - - function addFilter({ - fieldName, - operation, - value, - }: { - fieldName: string; - value: string; - operation: '+' | '-'; - }) { - if (dataView) { - const newFilters = generateFilters( - filterManagerService, - fieldName, - value, - operation, - dataView - ); - setSearchState((state) => ({ - ...state, - filters: [...state.filters, ...newFilters], - })); - } - } - - return { - searchState, - setSearchState, - stringifiedEsQuery, - addFilter, - }; -} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts new file mode 100644 index 0000000000000..d098b3d18710d --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts @@ -0,0 +1,166 @@ +/* + * 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 { buildEsQuery, type Filter, fromKueryExpression, type Query } from '@kbn/es-query'; +import createContainer from 'constate'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { map, Subject, Subscription, tap } from 'rxjs'; +import { generateFilters } from '@kbn/data-plugin/public'; +import useEffectOnce from 'react-use/lib/useEffectOnce'; +import deepEqual from 'fast-deep-equal'; +import { i18n } from '@kbn/i18n'; +import { useAdHocInventoryDataView } from './use_adhoc_inventory_data_view'; +import { useKibanaQuerySettings } from './use_kibana_query_settings'; +import { useUnifiedSearchUrl } from './use_unified_search_url'; +import { useKibana } from './use_kibana'; + +function useUnifiedSearch() { + const [isControlPanelsInitiated, setIsControlPanelsInitiated] = useState(false); + const { dataView } = useAdHocInventoryDataView(); + const [refreshSubject$] = useState>(new Subject()); + const { searchState, setSearchState } = useUnifiedSearchUrl(); + const kibanaQuerySettings = useKibanaQuerySettings(); + const { + services: { + data: { + query: { filterManager: filterManagerService, queryString: queryStringService }, + }, + notifications, + }, + } = useKibana(); + + useEffectOnce(() => { + if (!deepEqual(filterManagerService.getFilters(), searchState.filters)) { + filterManagerService.setFilters( + searchState.filters.map((item) => ({ + ...item, + meta: { ...item.meta, index: dataView?.id }, + })) + ); + } + + if (!deepEqual(queryStringService.getQuery(), searchState.query)) { + queryStringService.setQuery(searchState.query); + } + }); + + useEffect(() => { + const subscription = new Subscription(); + subscription.add( + filterManagerService + .getUpdates$() + .pipe( + map(() => filterManagerService.getFilters()), + tap((filters) => setSearchState({ type: 'SET_FILTERS', filters })) + ) + .subscribe() + ); + + subscription.add( + queryStringService + .getUpdates$() + .pipe( + map(() => queryStringService.getQuery() as Query), + tap((query) => setSearchState({ type: 'SET_QUERY', query })) + ) + .subscribe() + ); + + return () => { + subscription.unsubscribe(); + }; + }, [filterManagerService, queryStringService, setSearchState]); + + const validateQuery = useCallback( + (query: Query) => { + fromKueryExpression(query.query, kibanaQuerySettings); + }, + [kibanaQuerySettings] + ); + + const onQueryChange = useCallback( + (query: Query) => { + try { + validateQuery(query); + setSearchState({ type: 'SET_QUERY', query }); + } catch (e) { + const err = e as Error; + notifications.toasts.addDanger({ + title: i18n.translate('xpack.inventory.unifiedSearchContext.queryError', { + defaultMessage: 'Error while updating the new query', + }), + text: err.message, + }); + } + }, + [validateQuery, setSearchState, notifications.toasts] + ); + + const onControlFiltersChange = useCallback( + (controlFilters: Filter[]) => { + setSearchState({ type: 'SET_PANEL_FILTERS', controlFilters }); + }, + [setSearchState] + ); + + const onFiltersChange = useCallback( + (filters: Filter[]) => { + setSearchState({ type: 'SET_FILTERS', filters }); + }, + [setSearchState] + ); + + const addFilter = useCallback( + ({ + fieldName, + operation, + value, + }: { + fieldName: string; + value: string; + operation: '+' | '-'; + }) => { + if (dataView) { + const newFilters = generateFilters( + filterManagerService, + fieldName, + value, + operation, + dataView + ); + setSearchState({ type: 'SET_FILTERS', filters: [...newFilters, ...searchState.filters] }); + } + }, + [dataView, filterManagerService, searchState.filters, setSearchState] + ); + + const stringifiedEsQuery = useMemo(() => { + if (dataView) { + return JSON.stringify( + buildEsQuery(dataView, searchState.query, [ + ...searchState.controlFilters, + ...searchState.filters, + ]) + ); + } + }, [dataView, searchState.controlFilters, searchState.filters, searchState.query]); + + return { + isControlPanelsInitiated, + setIsControlPanelsInitiated, + dataView, + refreshSubject$, + searchState, + addFilter, + stringifiedEsQuery, + onQueryChange, + onControlFiltersChange, + onFiltersChange, + }; +} + +const UnifiedSearch = createContainer(useUnifiedSearch); +export const [UnifiedSearchProvider, useUnifiedSearchContext] = UnifiedSearch; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts new file mode 100644 index 0000000000000..9d151f79085f2 --- /dev/null +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts @@ -0,0 +1,100 @@ +/* + * 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 { FilterStateStore } from '@kbn/es-query'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; +import { enumeration } from '@kbn/securitysolution-io-ts-types'; +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as t from 'io-ts'; +import { useReducer } from 'react'; +import deepEqual from 'fast-deep-equal'; + +const FilterRT = t.intersection([ + t.type({ + meta: t.partial({ + alias: t.union([t.null, t.string]), + disabled: t.boolean, + negate: t.boolean, + controlledBy: t.string, + group: t.string, + index: t.string, + isMultiIndex: t.boolean, + type: t.string, + key: t.string, + params: t.any, + value: t.any, + }), + }), + t.partial({ + query: t.record(t.string, t.any), + $state: t.type({ + store: enumeration('FilterStateStore', FilterStateStore), + }), + }), +]); +const FiltersRT = t.array(FilterRT); + +const QueryStateRT = t.type({ + language: t.string, + query: t.union([t.string, t.record(t.string, t.any)]), +}); + +const SearchStateRT = t.type({ + controlFilters: FiltersRT, + filters: FiltersRT, + query: QueryStateRT, +}); + +const encodeUrlState = SearchStateRT.encode; +const decodeUrlState = (value: unknown) => { + return pipe(SearchStateRT.decode(value), fold(constant(undefined), identity)); +}; + +type SearchState = t.TypeOf; + +const INITIAL_VALUE: SearchState = { + query: { language: 'kuery', query: '' }, + controlFilters: [], + filters: [], +}; + +export type HostsStateAction = + | { type: 'SET_FILTERS'; filters: SearchState['filters'] } + | { type: 'SET_QUERY'; query: SearchState['query'] } + | { type: 'SET_PANEL_FILTERS'; controlFilters: SearchState['controlFilters'] }; + +const reducer = (state: SearchState, action: HostsStateAction): SearchState => { + switch (action.type) { + case 'SET_FILTERS': + return { ...state, filters: action.filters }; + case 'SET_QUERY': + return { ...state, query: action.query }; + case 'SET_PANEL_FILTERS': + return { ...state, controlFilters: action.controlFilters }; + default: + return state; + } +}; + +export function useUnifiedSearchUrl() { + const [urlState, setUrlState] = useUrlState({ + defaultState: INITIAL_VALUE, + decodeUrlState, + encodeUrlState, + urlStateKey: '_a', + writeDefaultState: true, + }); + + const [searchState, setSearchState] = useReducer(reducer, urlState); + + if (!deepEqual(searchState, urlState)) { + setUrlState(searchState); + } + + return { searchState, setSearchState }; +} From 5a899044dff2babda8d28e6cbda4c031261abf63 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 13:20:32 +0000 Subject: [PATCH 05/22] refactoring --- .../hooks/use_asset_details_url_state.ts | 2 +- .../infra/public/hooks/use_inventory_views.ts | 3 +- .../hooks/use_metrics_explorer_views.ts | 3 +- .../infra/public/hooks/use_url_state.ts | 126 -------------- ...log_entry_categories_results_url_state.tsx | 2 +- .../use_log_entry_rate_results_url_state.tsx | 2 +- .../search_bar/control_panels_config.ts | 41 +++++ .../search_bar/controls_content.tsx | 13 +- .../components/search_bar/controls_title.tsx | 2 +- .../hosts/hooks/use_hosts_table_url_state.ts | 2 +- .../hosts/hooks/use_logs_search_url_state.ts | 2 +- .../pages/metrics/hosts/hooks/use_tab_id.ts | 2 +- .../hooks/use_unified_search_url_state.ts | 4 +- .../use_asset_details_flyout_url_state.ts | 2 +- .../hooks/use_waffle_filters.ts | 2 +- .../hooks/use_waffle_options.ts | 2 +- .../inventory_view/hooks/use_waffle_time.ts | 2 +- .../metric_detail/hooks/use_metrics_time.ts | 2 +- .../index.tsx => control_groups.tsx} | 29 ++-- .../control_groups/use_control_panels.ts | 118 ------------- .../hooks/use_unified_search_context.ts | 12 +- .../public/hooks/use_unified_search_url.ts | 8 +- .../hooks/use_control_panels_url_state.ts | 158 ++++++++---------- .../observability_shared/public/index.ts | 1 + 24 files changed, 160 insertions(+), 380 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/infra/public/hooks/use_url_state.ts create mode 100644 x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/control_panels_config.ts rename x-pack/plugins/observability_solution/inventory/public/components/search_bar/{control_groups/index.tsx => control_groups.tsx} (76%) delete mode 100644 x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts rename x-pack/plugins/observability_solution/{infra/public/pages/metrics/hosts => observability_shared/public}/hooks/use_control_panels_url_state.ts (63%) diff --git a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts index d0694ef7f207f..3199cbf70c0be 100644 --- a/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/components/asset_details/hooks/use_asset_details_url_state.ts @@ -15,8 +15,8 @@ import { ALERT_STATUS_RECOVERED, ALERT_STATUS_UNTRACKED, } from '@kbn/rule-data-utils'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { ContentTabIds } from '../types'; -import { useUrlState } from '../../../hooks/use_url_state'; import { ASSET_DETAILS_URL_STATE_KEY } from '../constants'; import { ALERT_STATUS_ALL } from '../../shared/alerts/constants'; diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_inventory_views.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_inventory_views.ts index 38f3b19604102..d334baa0e5fdf 100644 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_inventory_views.ts +++ b/x-pack/plugins/observability_solution/infra/public/hooks/use_inventory_views.ts @@ -9,7 +9,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useUiTracker } from '@kbn/observability-shared-plugin/public'; +import { useUiTracker, useUrlState } from '@kbn/observability-shared-plugin/public'; import { MutationContext, SavedViewResult, @@ -23,7 +23,6 @@ import { } from '../../common/http_api/latest'; import type { InventoryView } from '../../common/inventory_views'; import { useKibanaContextForPlugin } from './use_kibana'; -import { useUrlState } from './use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_metrics_explorer_views.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_metrics_explorer_views.ts index 6d652af02a136..ddf27da96e1a7 100644 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_metrics_explorer_views.ts +++ b/x-pack/plugins/observability_solution/infra/public/hooks/use_metrics_explorer_views.ts @@ -9,7 +9,7 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useUiTracker } from '@kbn/observability-shared-plugin/public'; +import { useUiTracker, useUrlState } from '@kbn/observability-shared-plugin/public'; import { MutationContext, @@ -23,7 +23,6 @@ import { UpdateMetricsExplorerViewAttributesRequestPayload, } from '../../common/http_api/latest'; import { MetricsExplorerView } from '../../common/metrics_explorer_views'; -import { useUrlState } from './use_url_state'; import { useSavedViewsNotifier } from './use_saved_views_notifier'; import { useSourceContext } from '../containers/metrics_source'; import { useKibanaContextForPlugin } from './use_kibana'; diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_url_state.ts b/x-pack/plugins/observability_solution/infra/public/hooks/use_url_state.ts deleted file mode 100644 index a581e6536e487..0000000000000 --- a/x-pack/plugins/observability_solution/infra/public/hooks/use_url_state.ts +++ /dev/null @@ -1,126 +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 { parse } from 'query-string'; -import { Location } from 'history'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { decode, RisonValue } from '@kbn/rison'; -import { useHistory } from 'react-router-dom'; -import { replaceStateKeyInQueryString } from '../../common/url_state_storage_service'; - -export const useUrlState = ({ - defaultState, - decodeUrlState, - encodeUrlState, - urlStateKey, - writeDefaultState = false, -}: { - defaultState: State; - decodeUrlState: (value: RisonValue | undefined) => State | undefined; - encodeUrlState: (value: State) => RisonValue | undefined; - urlStateKey: string; - writeDefaultState?: boolean; -}) => { - const history = useHistory(); - - // history.location is mutable so we can't reliably use useMemo - const queryString = history?.location ? getQueryStringFromLocation(history.location) : ''; - - const urlStateString = useMemo(() => { - if (!queryString) { - return; - } - - return getParamFromQueryString(queryString, urlStateKey); - }, [queryString, urlStateKey]); - - const decodedState = useMemo(() => { - return decodeUrlState(decodeRisonUrlState(urlStateString)); - }, [decodeUrlState, urlStateString]); - - const state = useMemo(() => { - return typeof decodedState !== 'undefined' ? decodedState : defaultState; - }, [defaultState, decodedState]); - - const setState = useCallback( - (patch: State | undefined | ((prevState: State) => State)) => { - if (!history || !history.location) { - return; - } - - const currentLocation = history.location; - - const newState = - patch instanceof Function - ? patch( - decodeUrlState( - decodeRisonUrlState( - getParamFromQueryString(getQueryStringFromLocation(currentLocation), urlStateKey) - ) - ) ?? defaultState - ) - : patch; - - const newLocation = replaceQueryStringInLocation( - currentLocation, - replaceStateKeyInQueryString( - urlStateKey, - typeof newState !== 'undefined' ? encodeUrlState(newState) : undefined - )(getQueryStringFromLocation(currentLocation)) - ); - - if (newLocation !== currentLocation) { - history.replace(newLocation); - } - }, - [decodeUrlState, defaultState, encodeUrlState, history, urlStateKey] - ); - - const [shouldInitialize, setShouldInitialize] = useState( - writeDefaultState && typeof decodedState === 'undefined' - ); - - useEffect(() => { - if (shouldInitialize) { - setShouldInitialize(false); - setState(defaultState); - } - }, [shouldInitialize, setState, defaultState]); - - return [state, setState] as [typeof state, typeof setState]; -}; - -const decodeRisonUrlState = (value: string | undefined | null): RisonValue | undefined => { - try { - return value ? decode(value) : undefined; - } catch (error) { - if (error instanceof Error && error.message.startsWith('rison decoder error')) { - return {}; - } - throw error; - } -}; - -const getQueryStringFromLocation = (location: Location) => location.search.substring(1); - -const getParamFromQueryString = (queryString: string, key: string) => { - const parsedQueryString = parse(queryString, { sort: false }); - const queryParam = parsedQueryString[key]; - - return Array.isArray(queryParam) ? queryParam[0] : queryParam; -}; - -const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { - if (queryString === getQueryStringFromLocation(location)) { - return location; - } else { - return { - ...location, - search: `?${queryString}`, - }; - } -}; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx index 169000aa3801f..f219757da8514 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx @@ -9,7 +9,7 @@ import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; import * as rt from 'io-ts'; -import { useUrlState } from '../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx index 1130c8dca9be2..f669d82f76f0f 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/log_entry_rate/use_log_entry_rate_results_url_state.tsx @@ -12,8 +12,8 @@ import moment from 'moment'; import * as rt from 'io-ts'; import type { TimeRange as KibanaTimeRange } from '@kbn/es-query'; import { decodeOrThrow } from '@kbn/io-ts-utils'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { TimeRange } from '../../../../common/time/time_range'; -import { useUrlState } from '../../../hooks/use_url_state'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/control_panels_config.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/control_panels_config.ts new file mode 100644 index 0000000000000..75e2469974768 --- /dev/null +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/control_panels_config.ts @@ -0,0 +1,41 @@ +/* + * 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 { ControlPanels } from '@kbn/observability-shared-plugin/public'; + +export const availableControlsPanels = { + HOST_OS_NAME: 'host.os.name', + CLOUD_PROVIDER: 'cloud.provider', + SERVICE_NAME: 'service.name', +}; + +export const controlPanelConfigs: ControlPanels = { + [availableControlsPanels.HOST_OS_NAME]: { + order: 0, + width: 'medium', + grow: false, + type: 'optionsListControl', + fieldName: availableControlsPanels.HOST_OS_NAME, + title: 'Operating System', + }, + [availableControlsPanels.CLOUD_PROVIDER]: { + order: 1, + width: 'medium', + grow: false, + type: 'optionsListControl', + fieldName: availableControlsPanels.CLOUD_PROVIDER, + title: 'Cloud Provider', + }, + [availableControlsPanels.SERVICE_NAME]: { + order: 2, + width: 'medium', + grow: false, + type: 'optionsListControl', + fieldName: availableControlsPanels.SERVICE_NAME, + title: 'Service Name', + }, +}; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx index 2ee6aa762e77c..847d0e05183a7 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_content.tsx @@ -5,18 +5,19 @@ * 2.0. */ -import React, { useCallback, useEffect, useRef } from 'react'; import { ControlGroupRenderer, ControlGroupRendererApi, - DataControlApi, ControlGroupRuntimeState, + DataControlApi, } from '@kbn/controls-plugin/public'; -import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { DataView } from '@kbn/data-views-plugin/public'; -import { Subscription } from 'rxjs'; +import type { Filter, Query, TimeRange } from '@kbn/es-query'; import { euiStyled } from '@kbn/kibana-react-plugin/common'; -import { useControlPanels } from '../../hooks/use_control_panels_url_state'; +import { useControlPanels } from '@kbn/observability-shared-plugin/public'; +import React, { useCallback, useEffect, useRef } from 'react'; +import { Subscription } from 'rxjs'; +import { controlPanelConfigs } from './control_panels_config'; import { ControlTitle } from './controls_title'; interface Props { @@ -34,7 +35,7 @@ export const ControlsContent: React.FC = ({ timeRange, onFiltersChange, }) => { - const [controlPanels, setControlPanels] = useControlPanels(dataView); + const [controlPanels, setControlPanels] = useControlPanels(controlPanelConfigs, dataView); const subscriptions = useRef(new Subscription()); const getInitialInput = useCallback( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_title.tsx b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_title.tsx index 7202985dbb6bb..b5b45e0d3979a 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_title.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/components/search_bar/controls_title.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFormLabel, EuiText, EuiLink, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; -import { availableControlsPanels } from '../../hooks/use_control_panels_url_state'; import { Popover } from '../common/popover'; +import { availableControlsPanels } from './control_panels_config'; const helpMessages = { [availableControlsPanels.SERVICE_NAME]: ( diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts index c3a5117c18efe..3c2569da620c7 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_hosts_table_url_state.ts @@ -12,7 +12,7 @@ import { constant, identity } from 'fp-ts/lib/function'; import useLocalStorage from 'react-use/lib/useLocalStorage'; import deepEqual from 'fast-deep-equal'; import { useReducer } from 'react'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { DEFAULT_PAGE_SIZE, LOCAL_STORAGE_PAGE_SIZE_KEY } from '../constants'; export const GET_DEFAULT_TABLE_PROPERTIES: TableProperties = { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_logs_search_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_logs_search_url_state.ts index d40004e325a60..e2caa050132d3 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_logs_search_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_logs_search_url_state.ts @@ -9,7 +9,7 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; const DEFAULT_QUERY = { language: 'kuery', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_tab_id.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_tab_id.ts index ec5d897751481..4e9215158c696 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_tab_id.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_tab_id.ts @@ -9,7 +9,7 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; const TAB_ID_URL_STATE_KEY = 'tabId'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts index 99aaeb8de9af7..c7bcf09271a3e 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts @@ -14,7 +14,7 @@ import { constant, identity } from 'fp-ts/lib/function'; import { enumeration } from '@kbn/securitysolution-io-ts-types'; import { FilterStateStore } from '@kbn/es-query'; import useLocalStorage from 'react-use/lib/useLocalStorage'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { useKibanaTimefilterTime, useSyncKibanaTimeFilterTime, @@ -79,11 +79,9 @@ export const useHostsUrlState = (): [HostsState, Dispatch] => urlStateKey: '_a', writeDefaultState: true, }); - console.log('### caue useHostsUrlState urlState:', urlState); const [search, setSearch] = useReducer(reducer, urlState); if (!deepEqual(search, urlState)) { - console.log('################################:', search); setUrlState(search); if (localStorageHostLimit !== search.limit) { setLocalStorageHostLimit(search.limit); diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_asset_details_flyout_url_state.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_asset_details_flyout_url_state.ts index bbce5a9f90579..8878dead99d05 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_asset_details_flyout_url_state.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_asset_details_flyout_url_state.ts @@ -9,7 +9,7 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; export const GET_DEFAULT_PROPERTIES: AssetDetailsFlyoutProperties = { detailsItemId: null, diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts index 94ad32c4e3097..7372fbcfdd737 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_filters.ts @@ -12,12 +12,12 @@ import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import createContainter from 'constate'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { type InventoryFiltersState, inventoryFiltersStateRT, } from '../../../../../common/inventory_views'; import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill'; -import { useUrlState } from '../../../../hooks/use_url_state'; import { useMetricsDataViewContext } from '../../../../containers/metrics_source'; import { convertKueryToElasticSearchQuery } from '../../../../utils/kuery'; diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts index 38dd4f0bf3617..41b91bce9c4ee 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_options.ts @@ -11,6 +11,7 @@ import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import createContainer from 'constate'; import type { InventoryItemType } from '@kbn/metrics-data-access-plugin/common'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { InventoryViewOptions } from '../../../../../common/inventory_views/types'; import { type InventoryLegendOptions, @@ -24,7 +25,6 @@ import type { SnapshotGroupBy, SnapshotCustomMetricInput, } from '../../../../../common/http_api/snapshot_api'; -import { useUrlState } from '../../../../hooks/use_url_state'; export const DEFAULT_LEGEND: WaffleLegendOptions = { palette: 'cool', diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_time.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_time.ts index 670f8645fc485..a7aadb731a750 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_time.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/inventory_view/hooks/use_waffle_time.ts @@ -12,7 +12,7 @@ import { fold } from 'fp-ts/lib/Either'; import DateMath from '@kbn/datemath'; import { constant, identity } from 'fp-ts/lib/function'; import createContainer from 'constate'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { useKibanaTimefilterTime } from '../../../../hooks/use_kibana_timefilter_time'; export const DEFAULT_WAFFLE_TIME_STATE: WaffleTimeState = { currentTime: Date.now(), diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts index 7c61778dce74e..b36a1a1cd1c5d 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/metric_detail/hooks/use_metrics_time.ts @@ -13,8 +13,8 @@ import * as rt from 'io-ts'; import { pipe } from 'fp-ts/lib/pipeable'; import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; +import { useUrlState } from '@kbn/observability-shared-plugin/public'; import { replaceStateKeyInQueryString } from '../../../../../common/url_state_storage_service'; -import { useUrlState } from '../../../../hooks/use_url_state'; const parseRange = (range: MetricsTimeInput) => { const parsedFrom = dateMath.parse(range.from.toString()); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx similarity index 76% rename from x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx rename to x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx index 39d75686fbfb4..42381d266aeec 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx @@ -13,8 +13,20 @@ import { } from '@kbn/controls-plugin/public'; import React, { useCallback, useEffect, useRef } from 'react'; import { skip, Subscription } from 'rxjs'; -import { useUnifiedSearchContext } from '../../../hooks/use_unified_search_context'; -import { useControlPanels } from './use_control_panels'; +import { ControlPanels, useControlPanels } from '@kbn/observability-shared-plugin/public'; +import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; + +const controlPanelDefinitions: ControlPanels = { + [ENTITY_TYPE]: { + order: 0, + type: 'optionsListControl', + fieldName: ENTITY_TYPE, + width: 'small', + grow: false, + title: 'Type', + }, +}; export function ControlGroups() { const { @@ -22,9 +34,9 @@ export function ControlGroups() { setIsControlPanelsInitiated, dataView, searchState, - onControlFiltersChange, + onPanelFiltersChange, } = useUnifiedSearchContext(); - const { controlPanels, setControlPanels } = useControlPanels(dataView); + const [controlPanels, setControlPanels] = useControlPanels(controlPanelDefinitions, dataView); const subscriptions = useRef(new Subscription()); const getInitialInput = useCallback( @@ -46,7 +58,7 @@ export function ControlGroups() { subscriptions.current.add( controlGroup.filters$.pipe(skip(1)).subscribe((newFilters = []) => { - onControlFiltersChange(newFilters); + onPanelFiltersChange(newFilters); }) ); @@ -59,12 +71,7 @@ export function ControlGroups() { }) ); }, - [ - isControlPanelsInitiated, - onControlFiltersChange, - setControlPanels, - setIsControlPanelsInitiated, - ] + [isControlPanelsInitiated, onPanelFiltersChange, setControlPanels, setIsControlPanelsInitiated] ); useEffect(() => { diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts deleted file mode 100644 index 75ee719f5ab34..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups/use_control_panels.ts +++ /dev/null @@ -1,118 +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 * as t from 'io-ts'; -import { DataView } from '@kbn/data-views-plugin/common'; -import { useUrlState } from '@kbn/observability-shared-plugin/public'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { constant, identity } from 'fp-ts/lib/function'; -import { pick } from 'lodash'; - -const HOST_FILTERS_URL_STATE_KEY = 'controlPanels'; - -const PanelRT = t.intersection([ - t.type({ - order: t.number, - type: t.string, - }), - t.partial({ - width: t.union([t.literal('medium'), t.literal('small'), t.literal('large')]), - grow: t.boolean, - dataViewId: t.string, - fieldName: t.string, - title: t.union([t.string, t.undefined]), - selectedOptions: t.array(t.string), - }), -]); - -const ControlPanelsRT = t.record(t.string, PanelRT); -type ControlPanels = t.TypeOf; - -const cleanControlPanels = (controlPanels: ControlPanels) => { - return Object.entries(controlPanels).reduce((acc, [key, controlPanelConfig]) => { - const { dataViewId, ...rest } = controlPanelConfig; - return { - ...acc, - [key]: rest, - }; - }, {}); -}; -const encodeUrlState = (value: ControlPanels) => { - if (value) { - // Remove the dataView.id on update to make the control panels portable between data views - const cleanPanels = cleanControlPanels(value); - - return ControlPanelsRT.encode(cleanPanels); - } -}; - -const decodeUrlState = (value: unknown) => { - return pipe(ControlPanelsRT.decode(value), fold(constant(undefined), identity)); -}; - -const controlPanelDefinitions: ControlPanels = { - 'entity.type': { - order: 0, - type: 'optionsListControl', - fieldName: 'entity.type', - width: 'small', - grow: false, - title: 'Type', - }, -}; - -const controlPanelDefinitionKeys = Object.keys(controlPanelDefinitions); - -export function useControlPanels(dataView?: DataView) { - const visibleControlPanels = controlPanelDefinitionKeys - .filter((key) => dataView?.fields.getByName(key) !== undefined) - .reduce( - (acc, currKey) => ({ - ...acc, - [currKey]: controlPanelDefinitions[currKey], - }), - {} - ); - - const [controlPanels, setControlPanels] = useUrlState({ - defaultState: visibleControlPanels, - decodeUrlState, - encodeUrlState, - urlStateKey: HOST_FILTERS_URL_STATE_KEY, - writeDefaultState: true, - }); - - const mergedControlPanels = mergeVisibleControlPanelsWithUrl({ - visibleControlPanels, - urlControlPanels: controlPanels, - }); - - const controlPanelsWithDataView = Object.keys(mergedControlPanels).reduce( - (acc, currKey) => ({ - ...acc, - [currKey]: { ...mergedControlPanels[currKey], dataViewId: dataView?.id }, - }), - {} - ); - - return { - controlPanels: controlPanelsWithDataView, - setControlPanels, - }; -} - -function mergeVisibleControlPanelsWithUrl({ - visibleControlPanels, - urlControlPanels, -}: { - visibleControlPanels: ControlPanels; - urlControlPanels: ControlPanels; -}) { - const existingKeys = Object.keys(visibleControlPanels); - const controlPanelsToOverride = pick(urlControlPanels, existingKeys); - return { ...visibleControlPanels, ...controlPanelsToOverride }; -} diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts index d098b3d18710d..9fb1f644570fa 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts @@ -99,9 +99,9 @@ function useUnifiedSearch() { [validateQuery, setSearchState, notifications.toasts] ); - const onControlFiltersChange = useCallback( - (controlFilters: Filter[]) => { - setSearchState({ type: 'SET_PANEL_FILTERS', controlFilters }); + const onPanelFiltersChange = useCallback( + (panelFilters: Filter[]) => { + setSearchState({ type: 'SET_PANEL_FILTERS', panelFilters }); }, [setSearchState] ); @@ -141,12 +141,12 @@ function useUnifiedSearch() { if (dataView) { return JSON.stringify( buildEsQuery(dataView, searchState.query, [ - ...searchState.controlFilters, + ...searchState.panelFilters, ...searchState.filters, ]) ); } - }, [dataView, searchState.controlFilters, searchState.filters, searchState.query]); + }, [dataView, searchState.panelFilters, searchState.filters, searchState.query]); return { isControlPanelsInitiated, @@ -157,7 +157,7 @@ function useUnifiedSearch() { addFilter, stringifiedEsQuery, onQueryChange, - onControlFiltersChange, + onPanelFiltersChange, onFiltersChange, }; } diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts index 9d151f79085f2..b663061f024a3 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts @@ -45,7 +45,7 @@ const QueryStateRT = t.type({ }); const SearchStateRT = t.type({ - controlFilters: FiltersRT, + panelFilters: FiltersRT, filters: FiltersRT, query: QueryStateRT, }); @@ -59,14 +59,14 @@ type SearchState = t.TypeOf; const INITIAL_VALUE: SearchState = { query: { language: 'kuery', query: '' }, - controlFilters: [], + panelFilters: [], filters: [], }; export type HostsStateAction = | { type: 'SET_FILTERS'; filters: SearchState['filters'] } | { type: 'SET_QUERY'; query: SearchState['query'] } - | { type: 'SET_PANEL_FILTERS'; controlFilters: SearchState['controlFilters'] }; + | { type: 'SET_PANEL_FILTERS'; panelFilters: SearchState['panelFilters'] }; const reducer = (state: SearchState, action: HostsStateAction): SearchState => { switch (action.type) { @@ -75,7 +75,7 @@ const reducer = (state: SearchState, action: HostsStateAction): SearchState => { case 'SET_QUERY': return { ...state, query: action.query }; case 'SET_PANEL_FILTERS': - return { ...state, controlFilters: action.controlFilters }; + return { ...state, panelFilters: action.panelFilters }; default: return state; } diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_control_panels_url_state.ts b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_control_panels_url_state.ts similarity index 63% rename from x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_control_panels_url_state.ts rename to x-pack/plugins/observability_solution/observability_shared/public/hooks/use_control_panels_url_state.ts index 19c0580c73f86..0ade125dcf816 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_control_panels_url_state.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_control_panels_url_state.ts @@ -12,85 +12,49 @@ import { fold } from 'fp-ts/lib/Either'; import { constant, identity } from 'fp-ts/lib/function'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useMemo } from 'react'; -import { useUrlState } from '../../../../hooks/use_url_state'; +import { useUrlState } from './use_url_state'; -const HOST_FILTERS_URL_STATE_KEY = 'controlPanels'; +const CONTROL_PANELS_URL_KEY = 'controlPanels'; -export const availableControlsPanels = { - HOST_OS_NAME: 'host.os.name', - CLOUD_PROVIDER: 'cloud.provider', - SERVICE_NAME: 'service.name', -}; - -const controlPanelConfigs: ControlPanels = { - [availableControlsPanels.HOST_OS_NAME]: { - order: 0, - width: 'medium', - grow: false, - type: 'optionsListControl', - fieldName: availableControlsPanels.HOST_OS_NAME, - title: 'Operating System', - }, - [availableControlsPanels.CLOUD_PROVIDER]: { - order: 1, - width: 'medium', - grow: false, - type: 'optionsListControl', - fieldName: availableControlsPanels.CLOUD_PROVIDER, - title: 'Cloud Provider', - }, - [availableControlsPanels.SERVICE_NAME]: { - order: 2, - width: 'medium', - grow: false, - type: 'optionsListControl', - fieldName: availableControlsPanels.SERVICE_NAME, - title: 'Service Name', - }, -}; +const PanelRT = rt.intersection([ + rt.type({ + order: rt.number, + type: rt.string, + }), + rt.partial({ + width: rt.union([rt.literal('medium'), rt.literal('small'), rt.literal('large')]), + grow: rt.boolean, + dataViewId: rt.string, + fieldName: rt.string, + title: rt.union([rt.string, rt.undefined]), + selectedOptions: rt.array(rt.string), + }), +]); -const availableControlPanelFields = Object.values(availableControlsPanels); +const ControlPanelRT = rt.record(rt.string, PanelRT); +export type ControlPanels = rt.TypeOf; -export const useControlPanels = ( +const getVisibleControlPanels = ( + availableControlPanelFields: string[], dataView: DataView | undefined -): [ControlPanels, (state: ControlPanels) => void] => { - const defaultState = useMemo(() => getVisibleControlPanelsConfig(dataView), [dataView]); - - const [controlPanels, setControlPanels] = useUrlState({ - defaultState, - decodeUrlState, - encodeUrlState, - urlStateKey: HOST_FILTERS_URL_STATE_KEY, - }); - - /** - * Configure the control panels as - * 1. Available fields from the data view - * 2. Existing filters from the URL parameter (not colliding with allowed fields from data view) - * 3. Enhanced with dataView.id - */ - const controlsPanelsWithId = dataView - ? mergeDefaultPanelsWithUrlConfig(dataView, controlPanels) - : ({} as ControlPanels); - - return [controlsPanelsWithId, setControlPanels]; -}; - -/** - * Utils - */ -const getVisibleControlPanels = (dataView: DataView | undefined) => { +) => { return availableControlPanelFields.filter( (panelKey) => dataView?.fields.getByName(panelKey) !== undefined ); }; -const getVisibleControlPanelsConfig = (dataView: DataView | undefined) => { - return getVisibleControlPanels(dataView).reduce((panelsMap, panelKey) => { - const config = controlPanelConfigs[panelKey]; - - return { ...panelsMap, [panelKey]: config }; - }, {} as ControlPanels); +const getVisibleControlPanelsConfig = ( + controlPanelConfigs: ControlPanels, + dataView: DataView | undefined +) => { + return getVisibleControlPanels(Object.keys(controlPanelConfigs), dataView).reduce( + (panelsMap, panelKey) => { + const config = controlPanelConfigs[panelKey]; + + return { ...panelsMap, [panelKey]: config }; + }, + {} as ControlPanels + ); }; const addDataViewIdToControlPanels = (controlPanels: ControlPanels, dataViewId: string = '') => { @@ -115,9 +79,13 @@ const cleanControlPanels = (controlPanels: ControlPanels) => { }, {}); }; -const mergeDefaultPanelsWithUrlConfig = (dataView: DataView, urlPanels: ControlPanels = {}) => { +const mergeDefaultPanelsWithUrlConfig = ( + dataView: DataView, + urlPanels: ControlPanels = {}, + controlPanelConfigs: ControlPanels +) => { // Get default panel configs from existing fields in data view - const visiblePanels = getVisibleControlPanelsConfig(dataView); + const visiblePanels = getVisibleControlPanelsConfig(controlPanelConfigs, dataView); // Get list of panel which can be overridden to avoid merging additional config from url const existingKeys = Object.keys(visiblePanels); @@ -130,25 +98,6 @@ const mergeDefaultPanelsWithUrlConfig = (dataView: DataView, urlPanels: ControlP ); }; -const PanelRT = rt.intersection([ - rt.type({ - order: rt.number, - type: rt.string, - }), - rt.partial({ - width: rt.union([rt.literal('medium'), rt.literal('small'), rt.literal('large')]), - grow: rt.boolean, - dataViewId: rt.string, - fieldName: rt.string, - title: rt.union([rt.string, rt.undefined]), - selectedOptions: rt.array(rt.string), - }), -]); - -const ControlPanelRT = rt.record(rt.string, PanelRT); - -type ControlPanels = rt.TypeOf; - const encodeUrlState = (value: ControlPanels) => { if (value) { // Remove the dataView.id on update to make the control panels portable between data views @@ -163,3 +112,32 @@ const decodeUrlState = (value: unknown) => { return pipe(ControlPanelRT.decode(value), fold(constant({}), identity)); } }; + +export const useControlPanels = ( + controlPanelConfigs: ControlPanels, + dataView: DataView | undefined +): [ControlPanels, (state: ControlPanels) => void] => { + const defaultState = useMemo( + () => getVisibleControlPanelsConfig(controlPanelConfigs, dataView), + [controlPanelConfigs, dataView] + ); + + const [controlPanels, setControlPanels] = useUrlState({ + defaultState, + decodeUrlState, + encodeUrlState, + urlStateKey: CONTROL_PANELS_URL_KEY, + }); + + /** + * Configure the control panels as + * 1. Available fields from the data view + * 2. Existing filters from the URL parameter (not colliding with allowed fields from data view) + * 3. Enhanced with dataView.id + */ + const controlsPanelsWithId = dataView + ? mergeDefaultPanelsWithUrlConfig(dataView, controlPanels, controlPanelConfigs) + : ({} as ControlPanels); + + return [controlsPanelsWithId, setControlPanels]; +}; diff --git a/x-pack/plugins/observability_solution/observability_shared/public/index.ts b/x-pack/plugins/observability_solution/observability_shared/public/index.ts index bf9d2b98c298e..f9566fd38d703 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/index.ts @@ -106,3 +106,4 @@ export { FieldValueSelection, FieldValueSuggestions } from './components'; export { AddDataPanel, type AddDataPanelProps } from './components/add_data_panel'; export { useUrlState } from './hooks/use_url_state'; +export { type ControlPanels, useControlPanels } from './hooks/use_control_panels_url_state'; From 05519cdb5539b06da80157f574818779ae35b99d Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 13:58:59 +0000 Subject: [PATCH 06/22] fixing tests --- .../inventory/e2e/cypress/e2e/home.cy.ts | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index 501b6b8078da5..7004ddc9a10a6 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -134,7 +134,7 @@ describe('Home page', () => { ); }); - it('Navigates to discover with kuery filter', () => { + it.skip('Navigates to discover with kuery filter', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); @@ -165,16 +165,17 @@ describe('Home page', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); + cy.intercept('POST', 'internal/controls/optionsList/entities-*-latest').as( + 'entityTypeControlGroupOptions' + ); cy.intercept('GET', '/internal/inventory/entities?**').as('getEntities'); cy.intercept('GET', '/internal/inventory/entities/group_by/**').as('getGroups'); cy.visitKibana('/app/inventory'); cy.wait('@getEEMStatus'); - cy.getByTestSubj('entityTypesFilterComboBox') - .click() - .getByTestSubj('entityTypesFilterserviceOption') - .click(); + cy.getByTestSubj('optionsList-control-entity.type').click(); + cy.wait('@entityTypeControlGroupOptions'); + cy.getByTestSubj('optionsList-control-selection-service').click(); cy.wait('@getGroups'); - cy.contains('service'); cy.getByTestSubj('inventoryGroupTitle_entity.type_service').click(); cy.wait('@getEntities'); cy.get('server1').should('not.exist'); @@ -188,16 +189,17 @@ describe('Home page', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); + cy.intercept('POST', 'internal/controls/optionsList/entities-*-latest').as( + 'entityTypeControlGroupOptions' + ); cy.intercept('GET', '/internal/inventory/entities?**').as('getEntities'); cy.intercept('GET', '/internal/inventory/entities/group_by/**').as('getGroups'); cy.visitKibana('/app/inventory'); cy.wait('@getEEMStatus'); - cy.getByTestSubj('entityTypesFilterComboBox') - .click() - .getByTestSubj('entityTypesFilterhostOption') - .click(); + cy.getByTestSubj('optionsList-control-entity.type').click(); + cy.wait('@entityTypeControlGroupOptions'); + cy.getByTestSubj('optionsList-control-selection-host').click(); cy.wait('@getGroups'); - cy.contains('host'); cy.getByTestSubj('inventoryGroupTitle_entity.type_host').click(); cy.wait('@getEntities'); cy.contains('server1'); @@ -211,16 +213,17 @@ describe('Home page', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); + cy.intercept('POST', 'internal/controls/optionsList/entities-*-latest').as( + 'entityTypeControlGroupOptions' + ); cy.intercept('GET', '/internal/inventory/entities?**').as('getEntities'); cy.intercept('GET', '/internal/inventory/entities/group_by/**').as('getGroups'); cy.visitKibana('/app/inventory'); cy.wait('@getEEMStatus'); - cy.getByTestSubj('entityTypesFilterComboBox') - .click() - .getByTestSubj('entityTypesFiltercontainerOption') - .click(); + cy.getByTestSubj('optionsList-control-entity.type').click(); + cy.wait('@entityTypeControlGroupOptions'); + cy.getByTestSubj('optionsList-control-selection-container').click(); cy.wait('@getGroups'); - cy.contains('container'); cy.getByTestSubj('inventoryGroupTitle_entity.type_container').click(); cy.wait('@getEntities'); cy.contains('server1').should('not.exist'); From f358bf1670384bee1aeb56d8e1283713e23becba Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 14:10:03 +0000 Subject: [PATCH 07/22] fixing ci --- .../public/components/unified_inventory/index.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx index ba0325cfc3c0f..1bec6dee990d1 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/unified_inventory/index.tsx @@ -100,19 +100,6 @@ export function UnifiedInventory() { }); } - function handleTypeFilter(type: string) { - const { pagination: _, ...rest } = query; - // TODO: caue check it - inventoryRoute.push('/', { - path: {}, - query: { - ...rest, - // Override the current entity types - // entityTypes: [type], - }, - }); - } - return ( <> @@ -124,7 +111,6 @@ export function UnifiedInventory() { onChangePage={handlePageChange} onChangeSort={handleSortChange} pageIndex={pageIndex} - onFilterByType={handleTypeFilter} /> ); From d301233f814c9233d0b86aeeddc686ca09f065c9 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 14:26:07 +0000 Subject: [PATCH 08/22] removing kuery from alerts search --- .../server/routes/entities/get_latest_entities_alerts.ts | 8 +++----- .../inventory/server/routes/entities/route.ts | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts index e969f1d537e99..8126c69de6922 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/get_latest_entities_alerts.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { kqlQuery, termQuery } from '@kbn/observability-plugin/server'; -import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; +import { termQuery } from '@kbn/observability-plugin/server'; import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import { ALERT_STATUS, ALERT_STATUS_ACTIVE } from '@kbn/rule-data-utils'; import { AlertsClient } from '../../lib/create_alerts_client.ts/create_alerts_client'; import { getGroupByTermsAgg } from './get_group_by_terms_agg'; import { IdentityFieldsPerEntityType } from './get_identity_fields_per_entity_type'; @@ -21,11 +21,9 @@ type EntityTypeBucketsAggregation = Record; export async function getLatestEntitiesAlerts({ alertsClient, - kuery, identityFieldsPerEntityType, }: { alertsClient: AlertsClient; - kuery?: string; identityFieldsPerEntityType: IdentityFieldsPerEntityType; }): Promise> { if (identityFieldsPerEntityType.size === 0) { @@ -37,7 +35,7 @@ export async function getLatestEntitiesAlerts({ track_total_hits: false, query: { bool: { - filter: [...termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE), ...kqlQuery(kuery)], + filter: termQuery(ALERT_STATUS, ALERT_STATUS_ACTIVE), }, }, }; diff --git a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts index 714bbb1f0abf2..ae99713375b19 100644 --- a/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts +++ b/x-pack/plugins/observability_solution/inventory/server/routes/entities/route.ts @@ -87,8 +87,6 @@ export const listLatestEntitiesRoute = createInventoryServerRoute({ const alerts = await getLatestEntitiesAlerts({ identityFieldsPerEntityType, alertsClient, - // TODO: caue fix this - kuery: '', }); const joined = joinByKey( From 22ecb88a685a176af06a489b5617ea612c25d4b1 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 7 Nov 2024 14:43:00 +0000 Subject: [PATCH 09/22] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- .../plugins/observability_solution/inventory/tsconfig.json | 5 ++++- .../observability_shared/tsconfig.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index bd77df478cad1..0550e0e6a1bdc 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -55,6 +55,9 @@ "@kbn/storybook", "@kbn/zod", "@kbn/dashboard-plugin", - "@kbn/deeplinks-analytics" + "@kbn/deeplinks-analytics", + "@kbn/controls-plugin", + "@kbn/utility-types", + "@kbn/securitysolution-io-ts-types" ] } diff --git a/x-pack/plugins/observability_solution/observability_shared/tsconfig.json b/x-pack/plugins/observability_solution/observability_shared/tsconfig.json index f68649c85cea6..f7b8a7ff6c573 100644 --- a/x-pack/plugins/observability_solution/observability_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_shared/tsconfig.json @@ -45,6 +45,7 @@ "@kbn/rule-data-utils", "@kbn/es-query", "@kbn/serverless", + "@kbn/data-views-plugin", ], "exclude": ["target/**/*", ".storybook/**/*.js"] } From 9118ccde323b5164fbec3d1316bb0ee3cd61b409 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Thu, 7 Nov 2024 17:00:21 +0000 Subject: [PATCH 10/22] fixing ci --- .../badge_filter_with_popover.test.tsx | 28 ++------ .../entities_grid/entities_grid.stories.tsx | 2 - .../inventory_group_accordion.test.tsx | 8 ++- .../components/search_bar/discover_button.tsx | 20 ++---- .../search_bar/entity_types_controls.tsx | 67 ------------------- 5 files changed, 19 insertions(+), 106 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx diff --git a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/badge_filter_with_popover.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/badge_filter_with_popover.test.tsx index 6018b66d37991..f3c518ef49b16 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/badge_filter_with_popover.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/badge_filter_with_popover/badge_filter_with_popover.test.tsx @@ -5,11 +5,11 @@ * 2.0. */ +import { copyToClipboard } from '@elastic/eui'; +import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import { fireEvent, render, screen } from '@testing-library/react'; import React from 'react'; -import { render, fireEvent, screen } from '@testing-library/react'; import { BadgeFilterWithPopover } from '.'; -import { EuiThemeProvider, copyToClipboard } from '@elastic/eui'; -import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; jest.mock('@elastic/eui', () => ({ ...jest.requireActual('@elastic/eui'), @@ -17,10 +17,8 @@ jest.mock('@elastic/eui', () => ({ })); describe('BadgeFilterWithPopover', () => { - const mockOnFilter = jest.fn(); const field = ENTITY_TYPE; const value = 'host'; - const label = 'Host'; const popoverContentDataTestId = 'inventoryBadgeFilterWithPopoverContent'; const popoverContentTitleTestId = 'inventoryBadgeFilterWithPopoverTitle'; @@ -28,32 +26,16 @@ describe('BadgeFilterWithPopover', () => { jest.clearAllMocks(); }); - it('renders the badge with the correct label', () => { - render( - , - { wrapper: EuiThemeProvider } - ); - expect(screen.queryByText(label)).toBeInTheDocument(); - expect(screen.getByText(label).textContent).toBe(label); - }); - it('opens the popover when the badge is clicked', () => { - render(); + render(); expect(screen.queryByTestId(popoverContentDataTestId)).not.toBeInTheDocument(); fireEvent.click(screen.getByText(value)); expect(screen.queryByTestId(popoverContentDataTestId)).toBeInTheDocument(); expect(screen.queryByTestId(popoverContentTitleTestId)?.textContent).toBe(`${field}:${value}`); }); - it('calls onFilter when the "Filter for" button is clicked', () => { - render(); - fireEvent.click(screen.getByText(value)); - fireEvent.click(screen.getByTestId('inventoryBadgeFilterWithPopoverFilterForButton')); - expect(mockOnFilter).toHaveBeenCalled(); - }); - it('copies value to clipboard when the "Copy value" button is clicked', () => { - render(); + render(); fireEvent.click(screen.getByText(value)); fireEvent.click(screen.getByTestId('inventoryBadgeFilterWithPopoverCopyValueButton')); expect(copyToClipboard).toHaveBeenCalledWith(value); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx index 047c2e73d0d3e..a3f2834934cd8 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/entities_grid/entities_grid.stories.tsx @@ -77,7 +77,6 @@ export const Grid: Story = (args) => { onChangePage={setPageIndex} onChangeSort={setSort} pageIndex={pageIndex} - onFilterByType={(selectedEntityType) => updateArgs({ entityType: selectedEntityType })} /> @@ -100,7 +99,6 @@ export const EmptyGrid: Story = (args) => { onChangePage={setPageIndex} onChangeSort={setSort} pageIndex={pageIndex} - onFilterByType={() => {}} /> ); }; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.test.tsx b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.test.tsx index 2cddbb8e46d79..bf0b7064033f4 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.test.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/grouped_inventory/inventory_group_accordion.test.tsx @@ -25,7 +25,13 @@ describe('Grouped Inventory Accordion', () => { }, ], }; - render(); + render( + + ); expect(screen.getByText(props.groups[0]['entity.type'])).toBeInTheDocument(); const container = screen.getByTestId('inventoryPanelBadgeEntitiesCount_entity.type_host'); expect(within(container).getByText('Entities:')).toBeInTheDocument(); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx index d5ed5b5af8cf9..d01a1ba896535 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx @@ -7,7 +7,7 @@ import { EuiButton } from '@elastic/eui'; import { DataView } from '@kbn/data-views-plugin/public'; -import { buildPhrasesFilter, PhrasesFilter } from '@kbn/es-query'; +import { PhrasesFilter } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; @@ -18,8 +18,8 @@ import { ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; import { EntityColumnIds } from '../../../common/entities'; -import { useInventoryParams } from '../../hooks/use_inventory_params'; import { useKibana } from '../../hooks/use_kibana'; +import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; const ACTIVE_COLUMNS: EntityColumnIds[] = [ENTITY_DISPLAY_NAME, ENTITY_TYPE, ENTITY_LAST_SEEN]; @@ -27,9 +27,7 @@ export function DiscoverButton({ dataView }: { dataView: DataView }) { const { services: { share, application }, } = useKibana(); - const { - query: { kuery, entityTypes }, - } = useInventoryParams('/*'); + const { searchState } = useUnifiedSearchContext(); const discoverLocator = useMemo( () => share.url.locators.get('DISCOVER_APP_LOCATOR'), @@ -38,14 +36,10 @@ export function DiscoverButton({ dataView }: { dataView: DataView }) { const filters: PhrasesFilter[] = []; - const entityTypeField = dataView.getFieldByName(ENTITY_TYPE); - - if (entityTypes && entityTypeField) { - const entityTypeFilter = buildPhrasesFilter(entityTypeField, entityTypes, dataView); - filters.push(entityTypeFilter); - } - - const kueryWithEntityDefinitionFilters = [kuery, `${ENTITY_DEFINITION_ID} : builtin*`] + const kueryWithEntityDefinitionFilters = [ + searchState.query.query as string, + `${ENTITY_DEFINITION_ID} : builtin*`, + ] .filter(Boolean) .join(' AND '); diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx deleted file mode 100644 index e2d9dba2709f1..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/entity_types_controls.tsx +++ /dev/null @@ -1,67 +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 { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; -import { css } from '@emotion/react'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { useInventoryAbortableAsync } from '../../hooks/use_inventory_abortable_async'; -import { useInventoryParams } from '../../hooks/use_inventory_params'; -import { useKibana } from '../../hooks/use_kibana'; - -interface Props { - onChange: (entityTypes: string[]) => void; -} - -const toComboBoxOption = (entityType: string): EuiComboBoxOptionOption => ({ - key: entityType, - label: entityType, - 'data-test-subj': `entityTypesFilter${entityType}Option`, -}); - -export function EntityTypesControls({ onChange }: Props) { - const { - query: { entityTypes = [] }, - } = useInventoryParams('/*'); - - const { - services: { inventoryAPIClient }, - } = useKibana(); - - const { value, loading } = useInventoryAbortableAsync( - ({ signal }) => { - return inventoryAPIClient.fetch('GET /internal/inventory/entities/types', { signal }); - }, - [inventoryAPIClient] - ); - - const options = value?.entityTypes.map(toComboBoxOption); - const selectedOptions = entityTypes.map(toComboBoxOption); - - return ( - { - onChange(newOptions.map((option) => option.key).filter((key): key is string => !!key)); - }} - isClearable - /> - ); -} From f29d81035d1c25d8ce42e81835adc9d4db91e087 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 14:05:54 +0000 Subject: [PATCH 11/22] fixing TS --- .../inventory/public/components/app_root/index.tsx | 2 +- .../observability_solution/inventory/public/routes/config.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx index 52f46268da2ef..fd3d99b36713b 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx @@ -43,7 +43,7 @@ export function AppRoot({ return ( - + diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index bf5f8324aab25..16a2f759585e7 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Outlet, createRouter } from '@kbn/typed-react-router-config'; +import { Outlet, Route, createRouter } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; import { InventoryPageTemplate } from '../components/inventory_page_template'; @@ -15,7 +15,7 @@ import { defaultEntitySortField, entityColumnIdsRt, entityViewRt } from '../../c * The array of route definitions to be used when the application * creates the routes. */ -const inventoryRoutes = { +const inventoryRoutes: Record = { '/': { element: ( From aeff7ac77e57b3bd6db609d4a4f9c7a3ed750c73 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 14:13:10 +0000 Subject: [PATCH 12/22] addressing pr comments --- .../infra/public/pages/logs/stream/page.tsx | 3 +- .../metrics/hosts/hooks/use_unified_search.ts | 2 +- .../components/search_bar/discover_button.tsx | 3 +- .../public/hooks/use_kibana_query_settings.ts | 31 ------------------- .../hooks/use_unified_search_context.ts | 2 +- .../components/log_stream/log_stream.tsx | 2 +- .../public/utils/use_kibana_query_settings.ts | 31 ------------------- .../public/hooks/use_kibana_query_settings.ts | 0 .../observability_shared/public/index.ts | 1 + 9 files changed, 6 insertions(+), 69 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts delete mode 100644 x-pack/plugins/observability_solution/logs_shared/public/utils/use_kibana_query_settings.ts rename x-pack/plugins/observability_solution/{infra => observability_shared}/public/hooks/use_kibana_query_settings.ts (100%) diff --git a/x-pack/plugins/observability_solution/infra/public/pages/logs/stream/page.tsx b/x-pack/plugins/observability_solution/infra/public/pages/logs/stream/page.tsx index 33ff9300c4d94..706fb7811fa78 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/logs/stream/page.tsx +++ b/x-pack/plugins/observability_solution/infra/public/pages/logs/stream/page.tsx @@ -6,7 +6,7 @@ */ import { EuiErrorBoundary } from '@elastic/eui'; -import { useTrackPageview } from '@kbn/observability-shared-plugin/public'; +import { useKibanaQuerySettings, useTrackPageview } from '@kbn/observability-shared-plugin/public'; import React from 'react'; import { useLogViewContext } from '@kbn/logs-shared-plugin/public'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; @@ -14,7 +14,6 @@ import { useLogsBreadcrumbs } from '../../../hooks/use_logs_breadcrumbs'; import { LogStreamPageStateProvider } from '../../../observability_logs/log_stream_page/state'; import { streamTitle } from '../../../translations'; import { useKbnUrlStateStorageFromRouterContext } from '../../../containers/kbn_url_state_context'; -import { useKibanaQuerySettings } from '../../../hooks/use_kibana_query_settings'; import { ConnectedStreamPageContent } from './page_content'; export const StreamPage = () => { diff --git a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts index 8912ec480e3b7..8a7302da1a223 100644 --- a/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts +++ b/x-pack/plugins/observability_solution/infra/public/pages/metrics/hosts/hooks/use_unified_search.ts @@ -10,9 +10,9 @@ import { buildEsQuery, Filter, fromKueryExpression, TimeRange, type Query } from import { Subscription, map, tap } from 'rxjs'; import deepEqual from 'fast-deep-equal'; import useEffectOnce from 'react-use/lib/useEffectOnce'; +import { useKibanaQuerySettings } from '@kbn/observability-shared-plugin/public'; import { useSearchSessionContext } from '../../../../hooks/use_search_session'; import { parseDateRange } from '../../../../utils/datemath'; -import { useKibanaQuerySettings } from '../../../../hooks/use_kibana_query_settings'; import { useKibanaContextForPlugin } from '../../../../hooks/use_kibana'; import { telemetryTimeRangeFormatter } from '../../../../../common/formatters/telemetry_time_range'; import { useMetricsDataViewContext } from '../../../../containers/metrics_source'; diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx index d01a1ba896535..37742480ab3c1 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/discover_button.tsx @@ -7,10 +7,9 @@ import { EuiButton } from '@elastic/eui'; import { DataView } from '@kbn/data-views-plugin/public'; -import { PhrasesFilter } from '@kbn/es-query'; +import type { PhrasesFilter } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; - import { ENTITY_DEFINITION_ID, ENTITY_DISPLAY_NAME, diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts deleted file mode 100644 index 521cd0142303b..0000000000000 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_kibana_query_settings.ts +++ /dev/null @@ -1,31 +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 { EsQueryConfig } from '@kbn/es-query'; -import { SerializableRecord } from '@kbn/utility-types'; -import { useMemo } from 'react'; -import { UI_SETTINGS } from '@kbn/data-plugin/public'; -import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; - -export const useKibanaQuerySettings = (): EsQueryConfig => { - const [allowLeadingWildcards] = useUiSetting$(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); - const [queryStringOptions] = useUiSetting$(UI_SETTINGS.QUERY_STRING_OPTIONS); - const [dateFormatTZ] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); - const [ignoreFilterIfFieldNotInIndex] = useUiSetting$( - UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX - ); - - return useMemo( - () => ({ - allowLeadingWildcards, - queryStringOptions, - dateFormatTZ, - ignoreFilterIfFieldNotInIndex, - }), - [allowLeadingWildcards, dateFormatTZ, ignoreFilterIfFieldNotInIndex, queryStringOptions] - ); -}; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts index 9fb1f644570fa..94df3a035f3bb 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_context.ts @@ -12,8 +12,8 @@ import { generateFilters } from '@kbn/data-plugin/public'; import useEffectOnce from 'react-use/lib/useEffectOnce'; import deepEqual from 'fast-deep-equal'; import { i18n } from '@kbn/i18n'; +import { useKibanaQuerySettings } from '@kbn/observability-shared-plugin/public'; import { useAdHocInventoryDataView } from './use_adhoc_inventory_data_view'; -import { useKibanaQuerySettings } from './use_kibana_query_settings'; import { useUnifiedSearchUrl } from './use_unified_search_url'; import { useKibana } from './use_kibana'; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx index a9c45828a7555..f82e9436fe5cd 100644 --- a/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/observability_solution/logs_shared/public/components/log_stream/log_stream.tsx @@ -16,13 +16,13 @@ import { noop } from 'lodash'; import React, { useCallback, useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; import type { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/public'; +import { useKibanaQuerySettings } from '@kbn/observability-shared-plugin/public'; import { LogEntryCursor } from '../../../common/log_entry'; import { defaultLogViewsStaticConfig, LogViewReference } from '../../../common/log_views'; import { BuiltEsQuery, useLogStream } from '../../containers/logs/log_stream'; import { useLogView } from '../../hooks/use_log_view'; import { LogViewsClient } from '../../services/log_views'; import { LogColumnRenderConfiguration } from '../../utils/log_column_render_configuration'; -import { useKibanaQuerySettings } from '../../utils/use_kibana_query_settings'; import { useLogEntryFlyout } from '../logging/log_entry_flyout'; import { ScrollableLogTextStreamView, VisibleInterval } from '../logging/log_text_stream'; import { LogStreamErrorBoundary } from './log_stream_error_boundary'; diff --git a/x-pack/plugins/observability_solution/logs_shared/public/utils/use_kibana_query_settings.ts b/x-pack/plugins/observability_solution/logs_shared/public/utils/use_kibana_query_settings.ts deleted file mode 100644 index 521cd0142303b..0000000000000 --- a/x-pack/plugins/observability_solution/logs_shared/public/utils/use_kibana_query_settings.ts +++ /dev/null @@ -1,31 +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 { EsQueryConfig } from '@kbn/es-query'; -import { SerializableRecord } from '@kbn/utility-types'; -import { useMemo } from 'react'; -import { UI_SETTINGS } from '@kbn/data-plugin/public'; -import { useUiSetting$ } from '@kbn/kibana-react-plugin/public'; - -export const useKibanaQuerySettings = (): EsQueryConfig => { - const [allowLeadingWildcards] = useUiSetting$(UI_SETTINGS.QUERY_ALLOW_LEADING_WILDCARDS); - const [queryStringOptions] = useUiSetting$(UI_SETTINGS.QUERY_STRING_OPTIONS); - const [dateFormatTZ] = useUiSetting$(UI_SETTINGS.DATEFORMAT_TZ); - const [ignoreFilterIfFieldNotInIndex] = useUiSetting$( - UI_SETTINGS.COURIER_IGNORE_FILTER_IF_FIELD_NOT_IN_INDEX - ); - - return useMemo( - () => ({ - allowLeadingWildcards, - queryStringOptions, - dateFormatTZ, - ignoreFilterIfFieldNotInIndex, - }), - [allowLeadingWildcards, dateFormatTZ, ignoreFilterIfFieldNotInIndex, queryStringOptions] - ); -}; diff --git a/x-pack/plugins/observability_solution/infra/public/hooks/use_kibana_query_settings.ts b/x-pack/plugins/observability_solution/observability_shared/public/hooks/use_kibana_query_settings.ts similarity index 100% rename from x-pack/plugins/observability_solution/infra/public/hooks/use_kibana_query_settings.ts rename to x-pack/plugins/observability_solution/observability_shared/public/hooks/use_kibana_query_settings.ts diff --git a/x-pack/plugins/observability_solution/observability_shared/public/index.ts b/x-pack/plugins/observability_solution/observability_shared/public/index.ts index f9566fd38d703..bc9f2c452cc8c 100644 --- a/x-pack/plugins/observability_solution/observability_shared/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_shared/public/index.ts @@ -107,3 +107,4 @@ export { AddDataPanel, type AddDataPanelProps } from './components/add_data_pane export { useUrlState } from './hooks/use_url_state'; export { type ControlPanels, useControlPanels } from './hooks/use_control_panels_url_state'; +export { useKibanaQuerySettings } from './hooks/use_kibana_query_settings'; From 5b412e9a5dd2f3d77154c29a6b200e85d85f5276 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 14:17:38 +0000 Subject: [PATCH 13/22] feedback --- .../inventory/public/hooks/use_unified_search_url.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts index b663061f024a3..17cf0ef0d9597 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_unified_search_url.ts @@ -63,12 +63,12 @@ const INITIAL_VALUE: SearchState = { filters: [], }; -export type HostsStateAction = +export type StateAction = | { type: 'SET_FILTERS'; filters: SearchState['filters'] } | { type: 'SET_QUERY'; query: SearchState['query'] } | { type: 'SET_PANEL_FILTERS'; panelFilters: SearchState['panelFilters'] }; -const reducer = (state: SearchState, action: HostsStateAction): SearchState => { +const reducer = (state: SearchState, action: StateAction): SearchState => { switch (action.type) { case 'SET_FILTERS': return { ...state, filters: action.filters }; From 2eab8b1e97432eda2c2bebbdcd5a89c68c0b582b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:27:42 +0000 Subject: [PATCH 14/22] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/observability_solution/inventory/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/tsconfig.json b/x-pack/plugins/observability_solution/inventory/tsconfig.json index 0550e0e6a1bdc..008241b11ace6 100644 --- a/x-pack/plugins/observability_solution/inventory/tsconfig.json +++ b/x-pack/plugins/observability_solution/inventory/tsconfig.json @@ -57,7 +57,6 @@ "@kbn/dashboard-plugin", "@kbn/deeplinks-analytics", "@kbn/controls-plugin", - "@kbn/utility-types", "@kbn/securitysolution-io-ts-types" ] } From 47c8755688534f0f110f32c2fdefd1e1ab9d4a2f Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 14:59:53 +0000 Subject: [PATCH 15/22] reverting --- .../inventory/public/components/app_root/index.tsx | 2 +- .../observability_solution/inventory/public/routes/config.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx index fd3d99b36713b..52f46268da2ef 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/app_root/index.tsx @@ -43,7 +43,7 @@ export function AppRoot({ return ( - + diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index 16a2f759585e7..f8ce500ef0f9e 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -15,7 +15,7 @@ import { defaultEntitySortField, entityColumnIdsRt, entityViewRt } from '../../c * The array of route definitions to be used when the application * creates the routes. */ -const inventoryRoutes: Record = { +const inventoryRoutes = { '/': { element: ( From 94f4e5c35f44a920dd118d91cf2d6724684d5efe Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 15:00:23 +0000 Subject: [PATCH 16/22] unused import --- .../observability_solution/inventory/public/routes/config.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx index f8ce500ef0f9e..bf5f8324aab25 100644 --- a/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/routes/config.tsx @@ -4,7 +4,7 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { Outlet, Route, createRouter } from '@kbn/typed-react-router-config'; +import { Outlet, createRouter } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; import { InventoryPageTemplate } from '../components/inventory_page_template'; From 740845b3eacc010352b8dbba4b0f4698d47c78c1 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Fri, 8 Nov 2024 15:14:42 +0000 Subject: [PATCH 17/22] removing useless code --- .../components/search_bar/control_groups.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx index 42381d266aeec..9c263e39562f1 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/control_groups.tsx @@ -5,16 +5,15 @@ * 2.0. */ -import { css } from '@emotion/react'; import { ControlGroupRenderer, ControlGroupRendererApi, ControlGroupRuntimeState, } from '@kbn/controls-plugin/public'; +import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; +import { ControlPanels, useControlPanels } from '@kbn/observability-shared-plugin/public'; import React, { useCallback, useEffect, useRef } from 'react'; import { skip, Subscription } from 'rxjs'; -import { ControlPanels, useControlPanels } from '@kbn/observability-shared-plugin/public'; -import { ENTITY_TYPE } from '@kbn/observability-shared-plugin/common'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; const controlPanelDefinitions: ControlPanels = { @@ -86,17 +85,7 @@ export function ControlGroups() { } return ( -
+
Date: Fri, 8 Nov 2024 15:44:16 +0000 Subject: [PATCH 18/22] fixing i18n --- x-pack/plugins/translations/translations/fr-FR.json | 2 -- x-pack/plugins/translations/translations/ja-JP.json | 2 -- x-pack/plugins/translations/translations/zh-CN.json | 2 -- 3 files changed, 6 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 031d30da1d689..3f3ac43fc1c72 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -26276,8 +26276,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "Horodatage des dernières données reçues pour l'entité (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "Type", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "Type d'entité (entity.type)", - "xpack.inventory.entityTypesControls.euiComboBox.accessibleScreenReaderLabel": "Filtre des types d'entités", - "xpack.inventory.entityTypesControls.euiComboBox.placeHolderLabel": "Types", "xpack.inventory.featureRegistry.inventoryFeatureName": "Inventory", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "Alertes actives", "xpack.inventory.inventoryLinkTitle": "Inventory", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 598d3ec88bdcf..cf30b8a42c607 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -26247,8 +26247,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "エンティティで最後に受信したデータのタイムスタンプ(entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "エンティティのタイプ(entity.type)", - "xpack.inventory.entityTypesControls.euiComboBox.accessibleScreenReaderLabel": "エンティティタイプフィルター", - "xpack.inventory.entityTypesControls.euiComboBox.placeHolderLabel": "タイプ", "xpack.inventory.featureRegistry.inventoryFeatureName": "インベントリ", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "アクティブアラート", "xpack.inventory.inventoryLinkTitle": "インベントリ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 04094ba79b878..2a4c96e9b7aac 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -25774,8 +25774,6 @@ "xpack.inventory.entitiesGrid.euiDataGrid.lastSeenTooltip": "上次接收的实体数据的时间戳 (entity.lastSeenTimestamp)", "xpack.inventory.entitiesGrid.euiDataGrid.typeLabel": "类型", "xpack.inventory.entitiesGrid.euiDataGrid.typeTooltip": "实体的类型 (entity.type)", - "xpack.inventory.entityTypesControls.euiComboBox.accessibleScreenReaderLabel": "实体类型筛选", - "xpack.inventory.entityTypesControls.euiComboBox.placeHolderLabel": "类型", "xpack.inventory.featureRegistry.inventoryFeatureName": "库存", "xpack.inventory.home.serviceAlertsTable.tooltip.activeAlertsExplanation": "活动告警", "xpack.inventory.inventoryLinkTitle": "库存", From 405f7e9c07e80781522f2811567c4a601077338a Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 11 Nov 2024 12:08:45 +0000 Subject: [PATCH 19/22] Addressing PR comments --- .../hooks/use_abortable_async.ts | 4 ++- .../public/components/search_bar/index.tsx | 5 ++- .../public/hooks/use_discover_redirect.ts | 31 +++++-------------- 3 files changed, 15 insertions(+), 25 deletions(-) diff --git a/x-pack/packages/observability/observability_utils/hooks/use_abortable_async.ts b/x-pack/packages/observability/observability_utils/hooks/use_abortable_async.ts index 433ca877b0f62..477d765ef7a7f 100644 --- a/x-pack/packages/observability/observability_utils/hooks/use_abortable_async.ts +++ b/x-pack/packages/observability/observability_utils/hooks/use_abortable_async.ts @@ -54,7 +54,9 @@ export function useAbortableAsync( }) .catch((err) => { setValue(undefined); - setError(err); + if (!controller.signal.aborted) { + setError(err); + } }) .finally(() => setLoading(false)); } else { diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 5a98e60ace7a8..58d86648255c0 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -78,7 +78,6 @@ export function SearchBar() { } onQuerySubmit={handleQuerySubmit} @@ -86,6 +85,10 @@ export function SearchBar() { defaultMessage: 'Search for your entities by name or its metadata (e.g. entity.type : service)', })} + showDatePicker={false} + showFilterBar + showQueryInput + showQueryMenu /> diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts index d4d81a3400b54..1d1bf7a817e7f 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts @@ -5,18 +5,15 @@ * 2.0. */ import { - ENTITY_TYPE, ENTITY_DEFINITION_ID, ENTITY_DISPLAY_NAME, ENTITY_LAST_SEEN, + ENTITY_TYPE, } from '@kbn/observability-shared-plugin/common'; import { useCallback } from 'react'; -import { type PhrasesFilter, buildPhrasesFilter } from '@kbn/es-query'; -import type { DataViewField } from '@kbn/data-views-plugin/public'; import type { Entity, EntityColumnIds } from '../../common/entities'; import { unflattenEntity } from '../../common/utils/unflatten_entity'; import { useKibana } from './use_kibana'; -import { useInventoryParams } from './use_inventory_params'; import { useUnifiedSearchContext } from './use_unified_search_context'; const ACTIVE_COLUMNS: EntityColumnIds[] = [ENTITY_DISPLAY_NAME, ENTITY_TYPE, ENTITY_LAST_SEEN]; @@ -25,32 +22,19 @@ export const useDiscoverRedirect = () => { const { services: { share, application, entityManager }, } = useKibana(); - const { - query: { kuery, entityTypes }, - } = useInventoryParams('/*'); - const { dataView } = useUnifiedSearchContext(); + const { dataView, searchState } = useUnifiedSearchContext(); const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); const getDiscoverEntitiesRedirectUrl = useCallback( (entity?: Entity) => { - const filters: PhrasesFilter[] = []; - - const entityTypeField = (dataView?.getFieldByName(ENTITY_TYPE) ?? - entity?.[ENTITY_TYPE]) as DataViewField; - - if (entityTypes && entityTypeField && dataView) { - const entityTypeFilter = buildPhrasesFilter(entityTypeField, entityTypes, dataView); - filters.push(entityTypeFilter); - } - const entityKqlFilter = entity ? entityManager.entityClient.asKqlFilter(unflattenEntity(entity)) : ''; const kueryWithEntityDefinitionFilters = [ - kuery, + searchState.query.query, entityKqlFilter, `${ENTITY_DEFINITION_ID} : builtin*`, ] @@ -62,17 +46,18 @@ export const useDiscoverRedirect = () => { indexPatternId: dataView?.id ?? '', columns: ACTIVE_COLUMNS, query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' }, - filters, + filters: [...searchState.filters, ...searchState.panelFilters], }) : undefined; }, [ application.capabilities.discover?.show, + dataView?.id, discoverLocator, entityManager.entityClient, - entityTypes, - kuery, - dataView, + searchState.filters, + searchState.panelFilters, + searchState.query.query, ] ); From ce078af32ade681d727e1cddd48cd33dc4d434f4 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 11 Nov 2024 12:55:48 +0000 Subject: [PATCH 20/22] updating limits --- packages/kbn-optimizer/limits.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index b0357853720cb..c168ccaa6c806 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -119,7 +119,7 @@ pageLoadAssetSize: observabilityAiAssistantManagement: 19279 observabilityLogsExplorer: 46650 observabilityOnboarding: 19573 - observabilityShared: 80000 + observabilityShared: 111036 osquery: 107090 painlessLab: 179748 presentationPanel: 55463 From c94adec2e478600eadb9083f51eeb00b4500d2d1 Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 11 Nov 2024 13:28:43 +0000 Subject: [PATCH 21/22] unskipping test --- .../observability_solution/inventory/e2e/cypress/e2e/home.cy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts index 51ea0a537fc82..9c9011609740b 100644 --- a/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts +++ b/x-pack/plugins/observability_solution/inventory/e2e/cypress/e2e/home.cy.ts @@ -134,7 +134,7 @@ describe('Home page', () => { ); }); - it.skip('Navigates to discover with kuery filter', () => { + it('Navigates to discover with kuery filter', () => { cy.intercept('GET', '/internal/entities/managed/enablement', { fixture: 'eem_enabled.json', }).as('getEEMStatus'); From 827f89ece33bfd7ad95651acd152a98f6959541b Mon Sep 17 00:00:00 2001 From: Caue Marcondes Date: Mon, 11 Nov 2024 13:53:33 +0000 Subject: [PATCH 22/22] pr comments --- .../public/components/search_bar/index.tsx | 6 +++--- .../public/hooks/use_discover_redirect.ts | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx index 58d86648255c0..d1ccfd3f358e3 100644 --- a/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx +++ b/x-pack/plugins/observability_solution/inventory/public/components/search_bar/index.tsx @@ -5,11 +5,11 @@ * 2.0. */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { Query } from '@kbn/es-query'; +import type { Query } from '@kbn/es-query'; import { i18n } from '@kbn/i18n'; -import { SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; -import React, { useCallback, useEffect } from 'react'; +import type { SearchBarOwnProps } from '@kbn/unified-search-plugin/public/search_bar'; import deepEqual from 'fast-deep-equal'; +import React, { useCallback, useEffect } from 'react'; import { useKibana } from '../../hooks/use_kibana'; import { useUnifiedSearchContext } from '../../hooks/use_unified_search_context'; import { getKqlFieldsWithFallback } from '../../utils/get_kql_field_names_with_fallback'; diff --git a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts index 1d1bf7a817e7f..c29caca7e5b77 100644 --- a/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts +++ b/x-pack/plugins/observability_solution/inventory/public/hooks/use_discover_redirect.ts @@ -23,7 +23,10 @@ export const useDiscoverRedirect = () => { services: { share, application, entityManager }, } = useKibana(); - const { dataView, searchState } = useUnifiedSearchContext(); + const { + dataView, + searchState: { query, filters, panelFilters }, + } = useUnifiedSearchContext(); const discoverLocator = share.url.locators.get('DISCOVER_APP_LOCATOR'); @@ -34,7 +37,7 @@ export const useDiscoverRedirect = () => { : ''; const kueryWithEntityDefinitionFilters = [ - searchState.query.query, + query.query, entityKqlFilter, `${ENTITY_DEFINITION_ID} : builtin*`, ] @@ -46,7 +49,7 @@ export const useDiscoverRedirect = () => { indexPatternId: dataView?.id ?? '', columns: ACTIVE_COLUMNS, query: { query: kueryWithEntityDefinitionFilters, language: 'kuery' }, - filters: [...searchState.filters, ...searchState.panelFilters], + filters: [...filters, ...panelFilters], }) : undefined; }, @@ -55,9 +58,9 @@ export const useDiscoverRedirect = () => { dataView?.id, discoverLocator, entityManager.entityClient, - searchState.filters, - searchState.panelFilters, - searchState.query.query, + filters, + panelFilters, + query.query, ] );