[Investigate] Create plugin (#184908)

Create the Investigate plugin (naming TBD). Part of
https://github.com/elastic/kibana/pull/183293, splitting up the work in
several PRs.

The investigate plugin is mostly a registry to allow plugins to register
their widgets without creating dependency issues.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Dario Gieselaar 2024-06-07 15:05:36 +02:00 committed by GitHub
parent 2c8201dcde
commit 15b6ba9bd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 607 additions and 0 deletions

1
.github/CODEOWNERS vendored
View file

@ -506,6 +506,7 @@ src/plugins/inspector @elastic/kibana-presentation
src/plugins/interactive_setup @elastic/kibana-security
test/interactive_setup_api_integration/plugins/test_endpoints @elastic/kibana-security
packages/kbn-interpreter @elastic/kibana-visualizations
x-pack/plugins/observability_solution/investigate @elastic/obs-ai-assistant
packages/kbn-io-ts-utils @elastic/obs-knowledge-team
packages/kbn-ipynb @elastic/search-kibana
packages/kbn-jest-serializers @elastic/kibana-operations

View file

@ -638,6 +638,10 @@ the infrastructure monitoring use-case within Kibana.
|The ingest_pipelines plugin provides Kibana support for Elasticsearch's ingest pipelines.
|{kib-repo}blob/{branch}/x-pack/plugins/observability_solution/investigate/README.md[investigate]
|undefined
|{kib-repo}blob/{branch}/x-pack/plugins/kubernetes_security/README.md[kubernetesSecurity]
|This plugin provides interactive visualizations of your Kubernetes workload and session data.

View file

@ -543,6 +543,7 @@
"@kbn/interactive-setup-plugin": "link:src/plugins/interactive_setup",
"@kbn/interactive-setup-test-endpoints-plugin": "link:test/interactive_setup_api_integration/plugins/test_endpoints",
"@kbn/interpreter": "link:packages/kbn-interpreter",
"@kbn/investigate-plugin": "link:x-pack/plugins/observability_solution/investigate",
"@kbn/io-ts-utils": "link:packages/kbn-io-ts-utils",
"@kbn/ipynb": "link:packages/kbn-ipynb",
"@kbn/kbn-health-gateway-status-plugin": "link:test/health_gateway/plugins/status",

View file

@ -83,6 +83,7 @@ pageLoadAssetSize:
inputControlVis: 172675
inspector: 148711
interactiveSetup: 80000
investigate: 17970
kibanaOverview: 56279
kibanaReact: 74422
kibanaUsageCollection: 16463

View file

@ -1006,6 +1006,8 @@
"@kbn/interactive-setup-test-endpoints-plugin/*": ["test/interactive_setup_api_integration/plugins/test_endpoints/*"],
"@kbn/interpreter": ["packages/kbn-interpreter"],
"@kbn/interpreter/*": ["packages/kbn-interpreter/*"],
"@kbn/investigate-plugin": ["x-pack/plugins/observability_solution/investigate"],
"@kbn/investigate-plugin/*": ["x-pack/plugins/observability_solution/investigate/*"],
"@kbn/io-ts-utils": ["packages/kbn-io-ts-utils"],
"@kbn/io-ts-utils/*": ["packages/kbn-io-ts-utils/*"],
"@kbn/ipynb": ["packages/kbn-ipynb"],

View file

@ -51,6 +51,7 @@
"xpack.logsShared": "plugins/observability_solution/logs_shared",
"xpack.fleet": "plugins/fleet",
"xpack.ingestPipelines": "plugins/ingest_pipelines",
"xpack.investigate": "plugins/observability_solution/investigate",
"xpack.kubernetesSecurity": "plugins/kubernetes_security",
"xpack.lens": "plugins/lens",
"xpack.licenseApiGuard": "plugins/license_api_guard",

View file

@ -0,0 +1,14 @@
/*
* 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.
*/
export type {
InvestigateTimeline,
InvestigateWidget,
InvestigateWidgetCreate,
WorkflowBlock,
} from './types';
export { InvestigateWidgetColumnSpan } from './types';

View file

@ -0,0 +1,76 @@
/*
* 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 { EuiThemeComputed } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import type { DeepPartial, PickByValue } from 'utility-types';
export interface InvestigateUser {
name: string;
}
export interface GlobalWidgetParameters {
timeRange: {
from: string;
to: string;
};
query: {
query: string;
language: 'kuery';
};
filters: Filter[];
}
export enum InvestigateWidgetColumnSpan {
One = 1,
Two = 2,
Three = 3,
Four = 4,
}
export interface InvestigateTimeline {
id: string;
title: string;
'@timestamp': number;
user: InvestigateUser;
items: InvestigateWidget[];
}
export interface InvestigateWidget<
TParameters extends Record<string, any> = {},
TData extends Record<string, any> = {}
> {
id: string;
created: number;
last_updated: number;
type: string;
user: InvestigateUser;
parameters: GlobalWidgetParameters & TParameters;
data: TData;
title: string;
description?: string;
columns: InvestigateWidgetColumnSpan;
rows: number;
locked: boolean;
}
export type InvestigateWidgetCreate<TParameters extends Record<string, any> = {}> = Pick<
InvestigateWidget,
'title' | 'description' | 'columns' | 'rows' | 'type' | 'locked'
> & {
parameters: DeepPartial<GlobalWidgetParameters> & TParameters;
};
export interface WorkflowBlock {
id: string;
content?: string;
description?: string;
loading: boolean;
onClick?: () => void;
color?: keyof PickByValue<EuiThemeComputed<{}>['colors'], string>;
children?: React.ReactNode;
}

View file

@ -0,0 +1,23 @@
/*
* 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.
*/
module.exports = {
preset: '@kbn/test',
rootDir: '../../../..',
roots: [
'<rootDir>/x-pack/plugins/observability_solution/investigate/public',
'<rootDir>/x-pack/plugins/observability_solution/investigate/common',
'<rootDir>/x-pack/plugins/observability_solution/investigate/server',
],
setupFiles: [],
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/x-pack/plugins/observability_solution/investigate/{common,public,server}/**/*.{js,ts,tsx}',
],
coverageReporters: ['html'],
};

View file

@ -0,0 +1,18 @@
{
"type": "plugin",
"id": "@kbn/investigate-plugin",
"owner": "@elastic/obs-ai-assistant",
"plugin": {
"id": "investigate",
"server": true,
"browser": true,
"configPath": ["xpack", "investigate"],
"requiredPlugins": [
"observabilityAIAssistant"
],
"requiredBundles": [
],
"optionalPlugins": [],
"extraPublicDirs": []
}
}

View file

@ -0,0 +1,42 @@
/*
* 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 { DeepPartial } from 'utility-types';
import { InvestigateWidgetColumnSpan, InvestigateWidgetCreate } from '../common';
import { GlobalWidgetParameters } from '../common/types';
type MakePartial<T extends Record<string, any>, K extends keyof T> = Omit<T, K> &
DeepPartial<Pick<T, K>>;
type PredefinedKeys = 'rows' | 'columns' | 'locked' | 'type';
type AllowedDefaultKeys = 'rows' | 'columns';
export type WidgetFactory<TParameters extends Record<string, any>> = <
T extends MakePartial<InvestigateWidgetCreate<TParameters>, PredefinedKeys>
>(
widgetCreate: T
) => Pick<InvestigateWidgetCreate<TParameters>, PredefinedKeys> &
Omit<T, 'parameters'> & { parameters: T['parameters'] & DeepPartial<GlobalWidgetParameters> };
export function createWidgetFactory<TParameters extends Record<string, any>>(
type: string,
defaults?: Pick<Partial<InvestigateWidgetCreate>, AllowedDefaultKeys>
): WidgetFactory<TParameters> {
const createWidget: WidgetFactory<TParameters> = (widgetCreate) => {
return {
rows: 12,
columns: InvestigateWidgetColumnSpan.Four,
locked: false,
type,
...defaults,
...widgetCreate,
};
};
return createWidget;
}

View file

@ -0,0 +1,8 @@
/*
* 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.
*/
export const ESQL_WIDGET_NAME = 'esql';

View file

@ -0,0 +1,12 @@
/*
* 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 { createWidgetFactory } from '../create_widget';
import { ESQL_WIDGET_NAME } from './constants';
import type { EsqlWidgetParameters } from './types';
export const createEsqlWidget = createWidgetFactory<EsqlWidgetParameters>(ESQL_WIDGET_NAME);

View file

@ -0,0 +1,39 @@
/*
* 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 { IconType } from '@elastic/eui';
import type { Ast } from '@kbn/interpreter';
import type { InvestigateWidgetCreate } from '../../common';
import type { GlobalWidgetParameters } from '../../common/types';
// copied over from the Lens plugin to prevent dependency hell
type TableChangeType = 'initial' | 'unchanged' | 'reduced' | 'extended' | 'reorder' | 'layers';
interface Suggestion<T = unknown, V = unknown> {
visualizationId: string;
datasourceState?: V;
datasourceId?: string;
columns: number;
score: number;
title: string;
visualizationState: T;
previewExpression?: Ast | string;
previewIcon: IconType;
hide?: boolean;
// flag to indicate if the visualization is incomplete
incomplete?: boolean;
changeType: TableChangeType;
keptLayerIds: string[];
}
export interface EsqlWidgetParameters {
esql: string;
suggestion?: Suggestion;
predefined?: Partial<GlobalWidgetParameters>;
}
export type EsqlWidgetCreate = InvestigateWidgetCreate<EsqlWidgetParameters>;

View file

@ -0,0 +1,47 @@
/*
* 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 { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
import { InvestigatePlugin } from './plugin';
import type {
InvestigatePublicSetup,
InvestigatePublicStart,
InvestigateSetupDependencies,
InvestigateStartDependencies,
ConfigSchema,
OnWidgetAdd,
WidgetRenderAPI,
} from './types';
export type { InvestigatePublicSetup, InvestigatePublicStart, OnWidgetAdd, WidgetRenderAPI };
export {
type InvestigateTimeline,
type InvestigateWidget,
type InvestigateWidgetCreate,
InvestigateWidgetColumnSpan,
type GlobalWidgetParameters,
type InvestigateUser,
type WorkflowBlock,
} from '../common/types';
export { ChromeOption } from './types';
export { createWidgetFactory } from './create_widget';
export { getEsFilterFromGlobalParameters } from './util/get_es_filters_from_global_parameters';
export { ESQL_WIDGET_NAME } from './esql_widget/constants';
export { createEsqlWidget } from './esql_widget/create_esql_widget';
export type { EsqlWidgetParameters } from './esql_widget/types';
export const plugin: PluginInitializer<
InvestigatePublicSetup,
InvestigatePublicStart,
InvestigateSetupDependencies,
InvestigateStartDependencies
> = (pluginInitializerContext: PluginInitializerContext<ConfigSchema>) =>
new InvestigatePlugin(pluginInitializerContext);

View file

@ -0,0 +1,45 @@
/*
* 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 { CoreSetup, CoreStart, PluginInitializerContext, Plugin } from '@kbn/core/public';
import type { Logger } from '@kbn/logging';
import type {
ConfigSchema,
InvestigatePublicSetup,
InvestigatePublicStart,
InvestigateSetupDependencies,
InvestigateStartDependencies,
} from './types';
import { WidgetRegistry } from './widget_registry';
export class InvestigatePlugin
implements
Plugin<
InvestigatePublicSetup,
InvestigatePublicStart,
InvestigateSetupDependencies,
InvestigateStartDependencies
>
{
logger: Logger;
widgetRegistry: WidgetRegistry = new WidgetRegistry();
constructor(context: PluginInitializerContext<ConfigSchema>) {
this.logger = context.logger.get();
}
setup(coreSetup: CoreSetup, pluginsSetup: InvestigateSetupDependencies): InvestigatePublicSetup {
return {
registerWidget: this.widgetRegistry.registerWidget,
};
}
start(coreStart: CoreStart, pluginsStart: InvestigateStartDependencies): InvestigatePublicStart {
return {
getWidgetDefinitions: this.widgetRegistry.getWidgetDefinitions,
};
}
}

View file

@ -0,0 +1,84 @@
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-empty-interface*/
import type { FromSchema } from 'json-schema-to-ts';
import type { CompatibleJSONSchema } from '@kbn/observability-ai-assistant-plugin/public';
import type { InvestigateWidget, WorkflowBlock } from '../common';
import type { GlobalWidgetParameters, InvestigateWidgetCreate } from '../common/types';
export enum ChromeOption {
disabled = 'disabled',
static = 'static',
dynamic = 'dynamic',
}
export type OnWidgetAdd = (create: InvestigateWidgetCreate) => Promise<void>;
type UnregisterFunction = () => void;
export interface WidgetRenderAPI {
onDelete: () => void;
onWidgetAdd: OnWidgetAdd;
blocks: {
publish: (blocks: WorkflowBlock[]) => UnregisterFunction;
};
}
type WidgetRenderOptions<TInvestigateWidget extends InvestigateWidget> = {
widget: TInvestigateWidget;
} & WidgetRenderAPI;
export interface WidgetDefinition {
type: string;
description: string;
schema: CompatibleJSONSchema;
generate: (options: {
parameters: GlobalWidgetParameters;
signal: AbortSignal;
}) => Promise<Record<string, any>>;
render: (options: WidgetRenderOptions<InvestigateWidget>) => React.ReactNode;
chrome?: ChromeOption;
}
type RegisterWidgetOptions = Omit<WidgetDefinition, 'generate' | 'render'>;
type MaybeSchemaFrom<TSchema extends CompatibleJSONSchema | undefined> =
{} & (TSchema extends CompatibleJSONSchema ? FromSchema<TSchema> : {});
type GenerateCallback<
TSchema extends CompatibleJSONSchema | undefined,
TData extends Record<string, any> | undefined
> = (options: {
parameters: MaybeSchemaFrom<TSchema> & GlobalWidgetParameters;
signal: AbortSignal;
}) => Promise<TData>;
export type RegisterWidget = <
TSchema extends CompatibleJSONSchema,
TData extends Record<string, any>
>(
definition: Omit<RegisterWidgetOptions, 'schema'> & { schema: TSchema },
generateCallback: GenerateCallback<TSchema, TData>,
renderCallback: (
options: WidgetRenderOptions<InvestigateWidget<MaybeSchemaFrom<TSchema>, TData>>
) => React.ReactNode
) => void;
export interface ConfigSchema {}
export interface InvestigateSetupDependencies {}
export interface InvestigateStartDependencies {}
export interface InvestigatePublicSetup {
registerWidget: RegisterWidget;
}
export interface InvestigatePublicStart {
getWidgetDefinitions: () => WidgetDefinition[];
}

View file

@ -0,0 +1,30 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { type BoolQuery, buildEsQuery } from '@kbn/es-query';
import type { GlobalWidgetParameters } from '../../common/types';
export function getEsFilterFromGlobalParameters({
query,
filters,
timeRange,
}: Partial<GlobalWidgetParameters>): { bool: BoolQuery } {
const esFilter = buildEsQuery(undefined, query ?? [], filters ?? []);
if (timeRange) {
esFilter.bool.filter.push({
range: {
'@timestamp': {
gte: timeRange.from,
lte: timeRange.to,
},
},
});
}
return esFilter;
}

View file

@ -0,0 +1,26 @@
/*
* 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 { RegisterWidget, WidgetDefinition } from './types';
export class WidgetRegistry {
private readonly definitions: WidgetDefinition[] = [];
constructor() {}
registerWidget: RegisterWidget = (definition, generateCallback, renderCallback) => {
this.definitions.push({
...definition,
generate: generateCallback as WidgetDefinition['generate'],
render: renderCallback as WidgetDefinition['render'],
});
};
getWidgetDefinitions = (): WidgetDefinition[] => {
return this.definitions;
};
}

View file

@ -0,0 +1,14 @@
/*
* 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 { schema, type TypeOf } from '@kbn/config-schema';
export const config = schema.object({
enabled: schema.boolean({ defaultValue: true }),
});
export type InvestigateConfig = TypeOf<typeof config>;

View file

@ -0,0 +1,36 @@
/*
* 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 {
PluginInitializer,
PluginInitializerContext,
PluginConfigDescriptor,
} from '@kbn/core/server';
import { InvestigateConfig } from './config';
import { InvestigatePlugin } from './plugin';
import type {
InvestigateServerSetup,
InvestigateServerStart,
InvestigateSetupDependencies,
InvestigateStartDependencies,
} from './types';
import { config as configSchema } from './config';
export type { InvestigateServerSetup, InvestigateServerStart };
export const config: PluginConfigDescriptor<InvestigateConfig> = {
schema: configSchema,
};
export const plugin: PluginInitializer<
InvestigateServerSetup,
InvestigateServerStart,
InvestigateSetupDependencies,
InvestigateStartDependencies
> = async (pluginInitializerContext: PluginInitializerContext<InvestigateConfig>) =>
await new InvestigatePlugin(pluginInitializerContext);

View file

@ -0,0 +1,39 @@
/*
* 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 { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from '@kbn/core/server';
import type { Logger } from '@kbn/logging';
import type { InvestigateConfig } from './config';
import type {
InvestigateServerSetup,
InvestigateServerStart,
InvestigateSetupDependencies,
InvestigateStartDependencies,
} from './types';
export class InvestigatePlugin
implements
Plugin<
InvestigateServerSetup,
InvestigateServerStart,
InvestigateSetupDependencies,
InvestigateStartDependencies
>
{
logger: Logger;
constructor(context: PluginInitializerContext<InvestigateConfig>) {
this.logger = context.logger.get();
}
setup(coreSetup: CoreSetup, pluginsSetup: InvestigateSetupDependencies): InvestigateServerSetup {
return {};
}
start(coreStart: CoreStart, pluginsStart: InvestigateStartDependencies): InvestigateServerStart {
return {};
}
}

View file

@ -0,0 +1,16 @@
/*
* 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.
*/
/* eslint-disable @typescript-eslint/no-empty-interface*/
export interface InvestigateSetupDependencies {}
export interface InvestigateStartDependencies {}
export interface InvestigateServerSetup {}
export interface InvestigateServerStart {}

View file

@ -0,0 +1,23 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": [
"../../../typings/**/*",
"common/**/*",
"public/**/*",
"typings/**/*",
"public/**/*.json",
"server/**/*"
],
"kbn_references": [
"@kbn/core",
"@kbn/logging",
"@kbn/config-schema",
"@kbn/observability-ai-assistant-plugin",
"@kbn/es-query",
"@kbn/interpreter",
],
"exclude": ["target/**/*"]
}

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import type { PluginInitializer, PluginInitializerContext } from '@kbn/core/public';
export type { CompatibleJSONSchema } from '../common/functions/types';
import { ObservabilityAIAssistantPlugin } from './plugin';
import type {

View file

@ -5180,6 +5180,10 @@
version "0.0.0"
uid ""
"@kbn/investigate-plugin@link:x-pack/plugins/observability_solution/investigate":
version "0.0.0"
uid ""
"@kbn/io-ts-utils@link:packages/kbn-io-ts-utils":
version "0.0.0"
uid ""