Skip to content

Commit

Permalink
[Feature] Enable ppl visualization in Chatbot (opensearch-project#1304)
Browse files Browse the repository at this point in the history
Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe authored Dec 21, 2023
1 parent f31d969 commit 1110e0c
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 6 deletions.
3 changes: 2 additions & 1 deletion opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"visualizations"
],
"optionalPlugins": [
"managementOverview"
"managementOverview",
"assistantDashboards"
]
}
78 changes: 78 additions & 0 deletions public/dependencies/components/ppl_visualization_model.tsx
Original file line number Diff line number Diff line change
@@ -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<PPLVisualizationModelProps> = (props) => {
return (
<>
<EuiModalHeader>
<EuiModalHeaderTitle style={{ fontSize: '1.25rem' }}>
{props.savedVisualization.name}
</EuiModalHeaderTitle>
</EuiModalHeader>

<EuiModalBody>
<div>
<EuiCodeBlock isCopyable>{props.savedVisualization.query}</EuiCodeBlock>
<SavedObjectVisualization
savedVisualization={props.savedVisualization}
timeRange={{
from: props.savedVisualization.selected_date_range.start,
to: props.savedVisualization.selected_date_range.end,
}}
/>
</div>
</EuiModalBody>

<EuiModalFooter>
<EuiButton
onClick={async () => {
const response = await savePPLVisualization(props.savedVisualization);
props.onClose();
window.open(`./observability-logs#/explorer/${response.objectId}`, '_blank');
}}
fill
>
Save
</EuiButton>
<EuiButtonEmpty onClick={props.onClose}>Close</EuiButtonEmpty>
</EuiModalFooter>
</>
);
};

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);
};
63 changes: 63 additions & 0 deletions public/dependencies/register_assistant.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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<SavedVisualization>;
const savedVisualization = createSavedVisualization(params);
return (
<SavedObjectVisualization
savedVisualization={savedVisualization}
timeRange={{
from: savedVisualization.selected_date_range.start,
to: savedVisualization.selected_date_range.end,
}}
/>
);
});

setup.registerActionExecutor('view_ppl_visualization', async (params) => {
const savedVisualization = createSavedVisualization(params as Partial<SavedVisualization>);
const modal = coreRefs.core!.overlays.openModal(
toMountPoint(
<PPLVisualizationModal
savedVisualization={savedVisualization}
onClose={() => modal.close()}
/>
)
);
});
};

const createSavedVisualization = (params: Partial<SavedVisualization>) => {
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;
};
3 changes: 2 additions & 1 deletion public/framework/core_refs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { ApplicationStart, ChromeStart, HttpStart, IToasts } from '../../../../src/core/public';
import { ApplicationStart, ChromeStart, CoreStart, HttpStart, IToasts } from '../../../../src/core/public';
import { SavedObjectsClientContract } from '../../../../src/core/public';
import { DashboardStart } from '../../../../src/plugins/dashboard/public';
import PPLService from '../services/requests/ppl';

class CoreRefs {
private static _instance: CoreRefs;

public core?: CoreStart;
public http?: HttpStart;
public savedObjectsClient?: SavedObjectsClientContract;
public pplService?: PPLService;
Expand Down
4 changes: 4 additions & 0 deletions public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -306,13 +307,16 @@ export class ObservabilityPlugin
},
});

registerAsssitantDependencies(setupDeps.assistantDashboards);

// Return methods that should be available to other plugins
return {};
}

public start(core: CoreStart, startDeps: AppPluginStartDependencies): ObservabilityStart {
const pplService: PPLService = new PPLService(core.http);

coreRefs.core = core;
coreRefs.http = core.http;
coreRefs.savedObjectsClient = core.savedObjects.client;
coreRefs.pplService = pplService;
Expand Down
11 changes: 10 additions & 1 deletion public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { CoreStart } from '../../../src/core/public';
import { SavedObjectsClient } from '../../../src/core/server';
import { DashboardStart } from '../../../src/plugins/dashboard/public';
import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../src/plugins/data/public';
Expand All @@ -12,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;
Expand All @@ -27,10 +27,19 @@ export interface SetupDependencies {
data: DataPublicPluginSetup;
uiActions: UiActionsStart;
managementOverview?: ManagementOverViewPluginSetup;
assistantDashboards?: AssistantSetup;
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
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";
54 changes: 54 additions & 0 deletions server/parsers/ppl_parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright OpenSearch Contributors
* 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: MessageParser = {
id: 'ppl_visualization_message',
async parserProvider(interaction) {
const ppls: string[] = (interaction.additional_info?.["PPLTool.output"] as string[] | null)?.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) => {
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
return ({
type: 'output',
content: finalQuery,
contentType: 'ppl_visualization',
suggestedActions: [
{
message: 'View details',
actionType: 'view_ppl_visualization',
metadata: { query: finalQuery, question: interaction.input },
},
],
});
});
},
};
12 changes: 9 additions & 3 deletions server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ 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<ObservabilityPluginSetup, ObservabilityPluginStart> {
Expand All @@ -29,7 +30,10 @@ export class ObservabilityPlugin
this.logger = initializerContext.logger.get();
}

public setup(core: CoreSetup) {
public setup(core: CoreSetup, deps: {
assistantDashboards?: AssistantPluginSetup
}) {
const { assistantDashboards } = deps;
this.logger.debug('Observability: Setup');
const router = core.http.createRouter();
const openSearchObservabilityClient: ILegacyClusterClient = core.opensearch.legacy.createClient(
Expand Down Expand Up @@ -121,6 +125,8 @@ export class ObservabilityPlugin
},
}));

assistantDashboards?.registerMessageParser(PPLParsers);

return {};
}

Expand All @@ -129,5 +135,5 @@ export class ObservabilityPlugin
return {};
}

public stop() {}
public stop() { }
}
8 changes: 8 additions & 0 deletions server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

0 comments on commit 1110e0c

Please sign in to comment.