From a4620252f7e0f13109f114d7a5e2e31defb9e821 Mon Sep 17 00:00:00 2001 From: Milton Hultgren Date: Thu, 19 Dec 2024 17:06:07 +0100 Subject: [PATCH] [EEM][PoC] Add entity driven Dashboard action to view entity details --- .../shared/entity_manager/kibana.jsonc | 8 +- .../public/lib/entity_navigation_action.tsx | 217 ++++++++++++++++++ .../shared/entity_manager/public/plugin.ts | 14 +- .../shared/entity_manager/public/types.ts | 13 +- .../built_in/hosts_from_ecs_data.ts | 1 + .../entity_manager/server/lib/v2/types.ts | 1 + 6 files changed, 248 insertions(+), 6 deletions(-) create mode 100644 x-pack/platform/plugins/shared/entity_manager/public/lib/entity_navigation_action.tsx diff --git a/x-pack/platform/plugins/shared/entity_manager/kibana.jsonc b/x-pack/platform/plugins/shared/entity_manager/kibana.jsonc index 02919893a8757..31dcde0791484 100644 --- a/x-pack/platform/plugins/shared/entity_manager/kibana.jsonc +++ b/x-pack/platform/plugins/shared/entity_manager/kibana.jsonc @@ -14,8 +14,12 @@ "security", "encryptedSavedObjects", "licensing", - "features" + "features", + "uiActions", ], - "requiredBundles": [] + "requiredBundles": [ + "data", + "dashboard", + ] } } diff --git a/x-pack/platform/plugins/shared/entity_manager/public/lib/entity_navigation_action.tsx b/x-pack/platform/plugins/shared/entity_manager/public/lib/entity_navigation_action.tsx new file mode 100644 index 0000000000000..8570e2ce09c41 --- /dev/null +++ b/x-pack/platform/plugins/shared/entity_manager/public/lib/entity_navigation_action.tsx @@ -0,0 +1,217 @@ +/* + * 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, { useState } from 'react'; +import { toMountPoint } from '@kbn/react-kibana-mount'; +import { CoreStart } from '@kbn/core/public'; +import { + EuiButtonIcon, + EuiCard, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import { isPhraseFilter, type Filter } from '@kbn/es-query'; +import { DashboardCreationOptions, DashboardRenderer } from '@kbn/dashboard-plugin/public'; +import { compact, isEqual } from 'lodash'; +import { DashboardState } from '@kbn/dashboard-plugin/public/dashboard_api/types'; +import { EntityClient } from './entity_client'; + +interface FilterTriggerContext { + filters: Filter[]; +} + +export function createEntityNavigationAction(coreStart: CoreStart, entityClient: EntityClient) { + return { + id: 'eem_navigation_action', + getDisplayName: () => 'Go to entity view', + getIconType: () => 'bullseye', + isCompatible: async (context: FilterTriggerContext) => { + try { + const filterFields = compact( + context.filters.filter(isPhraseFilter).map((filter) => filter.meta.key) + ); + + if (filterFields.length === 0) { + return false; + } + + const types = await entityClient.repositoryClient( + 'GET /internal/entities/v2/definitions/types' + ); + + if (types.types.length === 0) { + return false; + } + + const sources = await entityClient.repositoryClient( + 'GET /internal/entities/v2/definitions/sources' + ); + + if (sources.sources.length === 0) { + return false; + } + + const typesWithSources = types.types.map((type) => ({ + ...type, + sources: sources.sources.filter((source) => source.type_id === type.id), + })); + + return typesWithSources + .filter((type) => type.dashboard_id) + .some((typeWithSource) => + typeWithSource.sources.some((source) => isEqual(source.identity_fields, filterFields)) + ); + } catch (error) { + coreStart.notifications.toasts.addError(error, { title: 'Unexpected error' }); + return false; + } + }, + execute: async (context: FilterTriggerContext) => { + try { + const filters = compact( + context.filters.filter(isPhraseFilter).map((filter) => ({ + field: filter.meta.key, + value: filter.meta.params?.query, + })) + ); + + if (filters.length === 0) { + return; + } + + const types = await entityClient.repositoryClient( + 'GET /internal/entities/v2/definitions/types' + ); + + if (types.types.length === 0) { + return; + } + + const sources = await entityClient.repositoryClient( + 'GET /internal/entities/v2/definitions/sources' + ); + + if (sources.sources.length === 0) { + return; + } + + const typesWithSources = types.types.map((type) => ({ + ...type, + sources: sources.sources.filter((source) => source.type_id === type.id), + })); + + const matchingTypes = typesWithSources + .filter((type) => type.dashboard_id) + .filter((typeWithSource) => + typeWithSource.sources.some((source) => + isEqual( + source.identity_fields, + filters.map((filter) => filter.field) + ) + ) + ); + + if (matchingTypes.length === 0) { + return; + } + + coreStart.overlays.openFlyout( + toMountPoint( + , + coreStart + ) + ); + } catch (error) { + coreStart.notifications.toasts.addError(error, { title: 'Unexpected error' }); + } + }, + }; +} + +interface Type { + id: string; + display_name: string; + dashboard_id?: string; +} + +interface EntityNavigationModalProps { + matchingTypes: Type[]; + filters: Filter[]; +} + +function EntityNavigationModal({ matchingTypes, filters }: EntityNavigationModalProps) { + const [selectedType, setSelectedType] = useState( + matchingTypes.length === 1 ? matchingTypes[0] : undefined + ); + + async function getCreationOptions(): Promise { + function getInitialInput(): Partial { + return { filters: filters.filter(isPhraseFilter) }; + } + + return { + getInitialInput, + }; + } + + if (selectedType) { + return ( + + + + {matchingTypes.length > 1 ? ( + { + setSelectedType(undefined); + }} + iconType="arrowLeft" + aria-label="Back" + /> + ) : null} + {selectedType.display_name} + + + + + + + + ); + } else { + return ( + + + Matching entity types + + + + + {matchingTypes.map((type) => ( + + } + title={type.display_name} + onClick={() => { + setSelectedType(type); + }} + /> + + ))} + + + + ); + } +} diff --git a/x-pack/platform/plugins/shared/entity_manager/public/plugin.ts b/x-pack/platform/plugins/shared/entity_manager/public/plugin.ts index 7ff6354c997eb..5f041ec441291 100644 --- a/x-pack/platform/plugins/shared/entity_manager/public/plugin.ts +++ b/x-pack/platform/plugins/shared/entity_manager/public/plugin.ts @@ -7,10 +7,12 @@ import { CoreSetup, CoreStart, PluginInitializerContext } from '@kbn/core/public'; import { Logger } from '@kbn/logging'; +import { APPLY_FILTER_TRIGGER } from '@kbn/data-plugin/public'; -import { EntityManagerPluginClass } from './types'; +import { EntityManagerPluginClass, EntityManagerPublicPluginStartDependencies } from './types'; import type { EntityManagerPublicConfig } from '../common/config'; import { EntityClient } from './lib/entity_client'; +import { createEntityNavigationAction } from './lib/entity_navigation_action'; export class Plugin implements EntityManagerPluginClass { public config: EntityManagerPublicConfig; @@ -27,9 +29,15 @@ export class Plugin implements EntityManagerPluginClass { }; } - start(core: CoreStart) { + start(core: CoreStart, plugins: EntityManagerPublicPluginStartDependencies) { + const entityClient = new EntityClient(core); + plugins.uiActions.addTriggerAction( + APPLY_FILTER_TRIGGER, + createEntityNavigationAction(core, entityClient) + ); + return { - entityClient: new EntityClient(core), + entityClient, }; } diff --git a/x-pack/platform/plugins/shared/entity_manager/public/types.ts b/x-pack/platform/plugins/shared/entity_manager/public/types.ts index 90d9026e8b9ba..70fc743546261 100644 --- a/x-pack/platform/plugins/shared/entity_manager/public/types.ts +++ b/x-pack/platform/plugins/shared/entity_manager/public/types.ts @@ -5,6 +5,7 @@ * 2.0. */ import type { Plugin as PluginClass } from '@kbn/core/public'; +import { UiActionsSetup, UiActionsStart } from '@kbn/ui-actions-plugin/public'; import type { EntityClient } from './lib/entity_client'; export interface EntityManagerPublicPluginSetup { @@ -14,7 +15,17 @@ export interface EntityManagerPublicPluginStart { entityClient: EntityClient; } +export interface EntityManagerPublicPluginSetupDependencies { + uiActions: UiActionsSetup; +} + +export interface EntityManagerPublicPluginStartDependencies { + uiActions: UiActionsStart; +} + export type EntityManagerPluginClass = PluginClass< EntityManagerPublicPluginSetup, - EntityManagerPublicPluginStart + EntityManagerPublicPluginStart, + EntityManagerPublicPluginSetupDependencies, + EntityManagerPublicPluginStartDependencies >; diff --git a/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/definitions/built_in/hosts_from_ecs_data.ts b/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/definitions/built_in/hosts_from_ecs_data.ts index a6aa14991c0d8..cf6cf3cb8dd69 100644 --- a/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/definitions/built_in/hosts_from_ecs_data.ts +++ b/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/definitions/built_in/hosts_from_ecs_data.ts @@ -12,6 +12,7 @@ export const builtInHostsFromEcsEntityDefinition: BuiltInDefinition = { type: { id: `${BUILT_IN_ID_PREFIX}hosts_from_ecs_data`, display_name: 'Hosts', + dashboard_id: '2e0c5f85-3599-40a9-9280-b60c6f2145dc', }, sources: [ { diff --git a/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/types.ts b/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/types.ts index 9515a60d7eec2..83f8ffe47a791 100644 --- a/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/types.ts +++ b/x-pack/platform/plugins/shared/entity_manager/server/lib/v2/types.ts @@ -16,6 +16,7 @@ export type InternalClusterClient = Pick; export const entityTypeDefinitionRt = z.object({ id: z.string(), display_name: z.string(), + dashboard_id: z.string().optional(), }); export type EntityTypeDefinition = z.TypeOf;