Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Enable ppl visualization in Chatbot #1304

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,13 +3,14 @@
* 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 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
9 changes: 8 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 Down Expand Up @@ -34,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";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also add this to

export interface SetupDependencies {
embeddable: EmbeddableSetup;
visualizations: VisualizationsSetup;
data: DataPublicPluginSetup;
uiActions: UiActionsStart;
managementOverview?: ManagementOverViewPluginSetup;
}
?
assistantDashboards?: AssistantSetup;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done for this.

51 changes: 51 additions & 0 deletions server/parsers/ppl_parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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) => ({
type: 'output',
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: [
{
message: 'View details',
actionType: 'view_ppl_visualization',
metadata: { query, 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";
Loading