From 4b2d818b6b2a0f541c0d14cc662ba08e3223625c Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Fri, 15 Dec 2023 18:01:50 +0800 Subject: [PATCH 1/6] temp: enable_ppl_visualization Signed-off-by: SuZhou-Joe --- opensearch_dashboards.json | 3 +- .../components/ppl_visualization_model.tsx | 78 +++++++++++++++++++ public/dependencies/register_assistant.tsx | 64 +++++++++++++++ public/plugin.ts | 3 + public/types.ts | 6 ++ server/parsers/ppl_parser.ts | 47 +++++++++++ server/plugin.ts | 14 +++- 7 files changed, 211 insertions(+), 4 deletions(-) create mode 100644 public/dependencies/components/ppl_visualization_model.tsx create mode 100644 public/dependencies/register_assistant.tsx create mode 100644 server/parsers/ppl_parser.ts diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 7abafdabd9..c05e9a9023 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -19,6 +19,7 @@ "visualizations" ], "optionalPlugins": [ - "managementOverview" + "managementOverview", + "assistantDashboards" ] } \ No newline at end of file diff --git a/public/dependencies/components/ppl_visualization_model.tsx b/public/dependencies/components/ppl_visualization_model.tsx new file mode 100644 index 0000000000..68a01da448 --- /dev/null +++ b/public/dependencies/components/ppl_visualization_model.tsx @@ -0,0 +1,78 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { + EuiButton, + EuiButtonEmpty, + EuiCodeBlock, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, +} from '@elastic/eui'; +import React from 'react'; +import { SavedVisualization } from '../../../common/types/explorer'; +import { SavedObjectVisualization } from '../../components/visualizations/saved_object_visualization'; +import { PPLSavedVisualizationClient } from '../../services/saved_objects/saved_object_client/ppl'; + +interface PPLVisualizationModelProps { + savedVisualization: SavedVisualization; + onClose: () => void; +} + +export const PPLVisualizationModal: React.FC = (props) => { + return ( + <> + + + {props.savedVisualization.name} + + + + +
+ {props.savedVisualization.query} + +
+
+ + + { + const response = await savePPLVisualization(props.savedVisualization); + props.onClose(); + window.open(`./observability-logs#/explorer/${response.objectId}`, '_blank'); + }} + fill + > + Save + + Close + + + ); +}; + +const savePPLVisualization = (savedVisualization: SavedVisualization) => { + const createParams = { + query: savedVisualization.query, + name: savedVisualization.name, + dateRange: [ + savedVisualization.selected_date_range.start, + savedVisualization.selected_date_range.end, + ], + fields: [], + timestamp: '', + type: savedVisualization.type, + sub_type: 'visualization', + }; + return PPLSavedVisualizationClient.getInstance().create(createParams); +}; diff --git a/public/dependencies/register_assistant.tsx b/public/dependencies/register_assistant.tsx new file mode 100644 index 0000000000..4fc84a6562 --- /dev/null +++ b/public/dependencies/register_assistant.tsx @@ -0,0 +1,64 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { merge } from 'lodash'; +import React from 'react'; +import { toMountPoint } from '../../../../src/plugins/opensearch_dashboards_react/public'; +import { SavedVisualization } from '../../common/types/explorer'; +import { SavedObjectVisualization } from '../components/visualizations/saved_object_visualization'; +import { coreRefs } from '../framework/core_refs'; +import { AssistantSetup } from '../types'; +import { PPLVisualizationModal } from './components/ppl_visualization_model'; + +export const registerAsssitantDependencies = (setup?: AssistantSetup) => { + if (!setup) return; + + setup.registerContentRenderer('ppl_visualization', (content) => { + const params = content as Partial; + console.log(params); + const savedVisualization = createSavedVisualization(params); + return ( + + ); + }); + + setup.registerActionExecutor('view_ppl_visualization', async (params) => { + const savedVisualization = createSavedVisualization(params as Partial); + const modal = coreRefs.core!.overlays.openModal( + toMountPoint( + modal.close()} + /> + ) + ); + }); +}; + +const createSavedVisualization = (params: Partial) => { + return merge( + { + query: params.query, + selected_date_range: { start: 'now-14d', end: 'now', text: '' }, + selected_timestamp: { name: 'timestamp', type: 'timestamp' }, + selected_fields: { tokens: [], text: '' }, + name: params.name, + description: '', + type: 'line', + sub_type: 'visualization', + }, + { + selected_date_range: params.selected_date_range, + selected_timestamp: params.selected_timestamp, + type: params.type, + } + ) as SavedVisualization; +}; diff --git a/public/plugin.ts b/public/plugin.ts index 9ac1fe5883..cba98f6394 100644 --- a/public/plugin.ts +++ b/public/plugin.ts @@ -79,6 +79,7 @@ import { S3DataSource } from './framework/datasources/s3_datasource'; import { DataSourcePluggable } from './framework/datasource_pluggables/datasource_pluggable'; import { DirectSearch } from './components/common/search/sql_search'; import { Search } from './components/common/search/search'; +import { registerAsssitantDependencies } from './dependencies/register_assistant'; export class ObservabilityPlugin implements @@ -306,6 +307,8 @@ export class ObservabilityPlugin }, }); + registerAsssitantDependencies(setupDeps.assistantDashboards); + // Return methods that should be available to other plugins return {}; } diff --git a/public/types.ts b/public/types.ts index 4b6cd96a4e..78b1e7e9ab 100644 --- a/public/types.ts +++ b/public/types.ts @@ -21,6 +21,12 @@ export interface AppPluginStartDependencies { data: DataPublicPluginStart; } +export interface AssistantSetup { + registerContentRenderer: (contentType: string, render: ContentRenderer) => void; + registerActionExecutor: (actionType: string, execute: ActionExecutor) => void; + assistantEnabled: () => Promise; +} + export interface SetupDependencies { embeddable: EmbeddableSetup; visualizations: VisualizationsSetup; diff --git a/server/parsers/ppl_parser.ts b/server/parsers/ppl_parser.ts new file mode 100644 index 0000000000..c68cc7d026 --- /dev/null +++ b/server/parsers/ppl_parser.ts @@ -0,0 +1,47 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +const extractPPLQueries = (content: string) => { + return Array.from(content.matchAll(/(^|[\n\r]|:)\s*(source\s*=\s*.+)/gi)).map( + (match) => match[2] + ); +}; + +export const PPLParsers = { + id: 'ppl_visualization_message', + async parserProvider(interaction) { + const ppls: string[] = interaction.additional_info?.["PPLTool.output"]?.flatMap((item: string) => { + let ppl: string = "" + try { + const outputResp = JSON.parse(item); + ppl = outputResp.ppl; + } catch (e) { + ppl = item; + } + + return extractPPLQueries(ppl); + }); + + if (!ppls.length) return []; + + const statsPPLs = ppls.filter((ppl) => /\|\s*stats\s+[^|]+\sby\s/i.test(ppl)); + if (!statsPPLs.length) { + return []; + } + + return statsPPLs.map((query) => ({ + type: 'output', + content: query, + contentType: 'ppl_visualization', + suggestedActions: [ + { + message: 'View details', + actionType: 'view_ppl_visualization', + metadata: { query, question: interaction.input }, + }, + ], + })); + }, +}; diff --git a/server/plugin.ts b/server/plugin.ts index 59e3e41247..070b07d898 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -20,16 +20,22 @@ import { visualizationSavedObject, } from './saved_objects/observability_saved_object'; import { ObservabilityPluginSetup, ObservabilityPluginStart } from './types'; +import { PPLParsers } from './parsers/ppl_parser'; export class ObservabilityPlugin - implements Plugin { + implements Plugin void + } + }> { private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup) { + public setup(core: CoreSetup, deps) { + const { assistantDashboards } = deps; this.logger.debug('Observability: Setup'); const router = core.http.createRouter(); const openSearchObservabilityClient: ILegacyClusterClient = core.opensearch.legacy.createClient( @@ -121,6 +127,8 @@ export class ObservabilityPlugin }, })); + assistantDashboards.registerMessageParser(PPLParsers); + return {}; } @@ -129,5 +137,5 @@ export class ObservabilityPlugin return {}; } - public stop() {} + public stop() { } } From 357b0834d4551c2366c2572d1e6e85a5d5d3918c Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Fri, 15 Dec 2023 18:17:50 +0800 Subject: [PATCH 2/6] feat: add some fix Signed-off-by: SuZhou-Joe --- server/parsers/ppl_parser.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/parsers/ppl_parser.ts b/server/parsers/ppl_parser.ts index c68cc7d026..19460203db 100644 --- a/server/parsers/ppl_parser.ts +++ b/server/parsers/ppl_parser.ts @@ -33,7 +33,9 @@ export const PPLParsers = { return statsPPLs.map((query) => ({ type: 'output', - content: query, + content: query + .replace(/`/g, '') // workaround for https://github.com/opensearch-project/dashboards-observability/issues/509, https://github.com/opensearch-project/dashboards-observability/issues/557 + .replace(/\bSPAN\(/g, 'span('), // workaround for https://github.com/opensearch-project/dashboards-observability/issues/759 contentType: 'ppl_visualization', suggestedActions: [ { From 51ab4aa57978cf0eafd4340543f18ba8c200dd84 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Dec 2023 10:36:33 +0800 Subject: [PATCH 3/6] fix: type declaration Signed-off-by: SuZhou-Joe --- server/parsers/ppl_parser.ts | 8 +++++--- server/plugin.ts | 14 ++++++-------- server/types.ts | 8 ++++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/server/parsers/ppl_parser.ts b/server/parsers/ppl_parser.ts index 19460203db..b45a58bcf4 100644 --- a/server/parsers/ppl_parser.ts +++ b/server/parsers/ppl_parser.ts @@ -3,16 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { MessageParser } from '../types'; + const extractPPLQueries = (content: string) => { return Array.from(content.matchAll(/(^|[\n\r]|:)\s*(source\s*=\s*.+)/gi)).map( (match) => match[2] ); }; -export const PPLParsers = { +export const PPLParsers: MessageParser = { id: 'ppl_visualization_message', async parserProvider(interaction) { - const ppls: string[] = interaction.additional_info?.["PPLTool.output"]?.flatMap((item: string) => { + const ppls: string[] = (interaction.additional_info?.["PPLTool.output"] as string[] | null)?.flatMap((item: string) => { let ppl: string = "" try { const outputResp = JSON.parse(item); @@ -22,7 +24,7 @@ export const PPLParsers = { } return extractPPLQueries(ppl); - }); + }) || []; if (!ppls.length) return []; diff --git a/server/plugin.ts b/server/plugin.ts index 070b07d898..ea98af80ee 100644 --- a/server/plugin.ts +++ b/server/plugin.ts @@ -19,22 +19,20 @@ import { searchSavedObject, visualizationSavedObject, } from './saved_objects/observability_saved_object'; -import { ObservabilityPluginSetup, ObservabilityPluginStart } from './types'; +import { ObservabilityPluginSetup, ObservabilityPluginStart, AssistantPluginSetup } from './types'; import { PPLParsers } from './parsers/ppl_parser'; export class ObservabilityPlugin - implements Plugin void - } - }> { + implements Plugin { private readonly logger: Logger; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); } - public setup(core: CoreSetup, deps) { + public setup(core: CoreSetup, deps: { + assistantDashboards?: AssistantPluginSetup + }) { const { assistantDashboards } = deps; this.logger.debug('Observability: Setup'); const router = core.http.createRouter(); @@ -127,7 +125,7 @@ export class ObservabilityPlugin }, })); - assistantDashboards.registerMessageParser(PPLParsers); + assistantDashboards?.registerMessageParser(PPLParsers); return {}; } diff --git a/server/types.ts b/server/types.ts index e936737f3b..e70eeb0c44 100644 --- a/server/types.ts +++ b/server/types.ts @@ -7,3 +7,11 @@ export interface ObservabilityPluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityPluginStart {} + +/** + * Introduce a compile dependency on dashboards-assistant + * as observerability need some types from the plugin. + * It will gives an type error when dashboards-assistant is not installed so add a ts-ignore to suppress the error. + */ +// @ts-ignore +export type { AssistantPluginSetup, MessageParser } from "../../dashboards-assistant/server"; From 1f9cc08be2398b1fe8bc6c20075674956857be05 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Dec 2023 11:36:27 +0800 Subject: [PATCH 4/6] fix: public type error Signed-off-by: SuZhou-Joe --- public/dependencies/register_assistant.tsx | 1 - public/framework/core_refs.ts | 3 ++- public/plugin.ts | 1 + public/types.ts | 15 ++++++++------- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/public/dependencies/register_assistant.tsx b/public/dependencies/register_assistant.tsx index 4fc84a6562..ebacf4d6bc 100644 --- a/public/dependencies/register_assistant.tsx +++ b/public/dependencies/register_assistant.tsx @@ -17,7 +17,6 @@ export const registerAsssitantDependencies = (setup?: AssistantSetup) => { setup.registerContentRenderer('ppl_visualization', (content) => { const params = content as Partial; - console.log(params); const savedVisualization = createSavedVisualization(params); return ( void; - registerActionExecutor: (actionType: string, execute: ActionExecutor) => void; - assistantEnabled: () => Promise; -} - export interface SetupDependencies { embeddable: EmbeddableSetup; visualizations: VisualizationsSetup; @@ -40,3 +33,11 @@ export interface ObservabilitySetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ObservabilityStart {} + +/** + * Introduce a compile dependency on dashboards-assistant + * as observerability need some types from the plugin. + * It will gives an type error when dashboards-assistant is not installed so add a ts-ignore to suppress the error. + */ +// @ts-ignore +export type { AssistantSetup } from "../../dashboards-assistant/public"; From 6e63c4d5f8b0678dbdf25ae06624e0b22e115cb3 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Dec 2023 11:47:10 +0800 Subject: [PATCH 5/6] fix: type error Signed-off-by: SuZhou-Joe --- public/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/types.ts b/public/types.ts index 024d8bdf21..cfa38acb25 100644 --- a/public/types.ts +++ b/public/types.ts @@ -11,6 +11,7 @@ import { ManagementOverViewPluginSetup } from '../../../src/plugins/management_o import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; import { VisualizationsSetup } from '../../../src/plugins/visualizations/public'; +import { AssistantSetup } from './types'; export interface AppPluginStartDependencies { navigation: NavigationPublicPluginStart; @@ -26,6 +27,7 @@ export interface SetupDependencies { data: DataPublicPluginSetup; uiActions: UiActionsStart; managementOverview?: ManagementOverViewPluginSetup; + assistantDashboards?: AssistantSetup; } // eslint-disable-next-line @typescript-eslint/no-empty-interface From aea073d9dc78349c302b0f51ee4aea1791ad2029 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Tue, 19 Dec 2023 09:10:50 +0800 Subject: [PATCH 6/6] fix: render chart Signed-off-by: SuZhou-Joe --- server/parsers/ppl_parser.ts | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/server/parsers/ppl_parser.ts b/server/parsers/ppl_parser.ts index b45a58bcf4..41c8fa4007 100644 --- a/server/parsers/ppl_parser.ts +++ b/server/parsers/ppl_parser.ts @@ -33,19 +33,22 @@ export const PPLParsers: MessageParser = { return []; } - return statsPPLs.map((query) => ({ - type: 'output', - content: query + return statsPPLs.map((query) => { + const finalQuery = query .replace(/`/g, '') // workaround for https://github.com/opensearch-project/dashboards-observability/issues/509, https://github.com/opensearch-project/dashboards-observability/issues/557 - .replace(/\bSPAN\(/g, 'span('), // workaround for https://github.com/opensearch-project/dashboards-observability/issues/759 - contentType: 'ppl_visualization', - suggestedActions: [ - { - message: 'View details', - actionType: 'view_ppl_visualization', - metadata: { query, question: interaction.input }, - }, - ], - })); + .replace(/\bSPAN\(/g, 'span('); // workaround for https://github.com/opensearch-project/dashboards-observability/issues/759 + return ({ + type: 'output', + content: finalQuery, + contentType: 'ppl_visualization', + suggestedActions: [ + { + message: 'View details', + actionType: 'view_ppl_visualization', + metadata: { query: finalQuery, question: interaction.input }, + }, + ], + }); + }); }, };