diff --git a/opensearch_dashboards.json b/opensearch_dashboards.json index 8f8f1a39d..12c57b351 100644 --- a/opensearch_dashboards.json +++ b/opensearch_dashboards.json @@ -17,6 +17,7 @@ "opensearchDashboardsUtils", "contentManagement" ], + "optionalPlugins": ["assistantDashboards"], "server": true, "ui": true, "supportedOSDataSourceVersions": ">=2.13.0", diff --git a/public/app.js b/public/app.js index 5a40bf94e..8ea486028 100644 --- a/public/app.js +++ b/public/app.js @@ -52,7 +52,9 @@ export function renderApp(coreStart, params, defaultRoute) { defaultRoute: defaultRoute, }} > -
} /> +
} + /> , diff --git a/public/pages/Dashboard/containers/Dashboard.test.js b/public/pages/Dashboard/containers/Dashboard.test.js index 9fb6dda12..c5486e7b2 100644 --- a/public/pages/Dashboard/containers/Dashboard.test.js +++ b/public/pages/Dashboard/containers/Dashboard.test.js @@ -9,6 +9,7 @@ import { mount } from 'enzyme'; import Dashboard from './Dashboard'; import { historyMock, httpClientMock } from '../../../../test/mocks'; import { setupCoreStart } from '../../../../test/utils/helpers'; +import { setAssistantDashboards } from '../../../services'; const location = { hash: '', @@ -62,6 +63,8 @@ beforeAll(() => { }); describe('Dashboard', () => { + setAssistantDashboards({ getFeatureStatus: () => ({ chat: false, alertInsight: false }) }); + beforeEach(() => { jest.clearAllMocks(); }); diff --git a/public/pages/Dashboard/utils/tableUtils.js b/public/pages/Dashboard/utils/tableUtils.js index 800767679..9959af144 100644 --- a/public/pages/Dashboard/utils/tableUtils.js +++ b/public/pages/Dashboard/utils/tableUtils.js @@ -7,7 +7,9 @@ import React from 'react'; import _ from 'lodash'; import { EuiLink, EuiToolTip } from '@elastic/eui'; import moment from 'moment'; -import { ALERT_STATE, DEFAULT_EMPTY_DATA } from '../../../utils/constants'; +import { ALERT_STATE, DEFAULT_EMPTY_DATA, MONITOR_TYPE } from '../../../utils/constants'; +import { getApplication, getAssistantDashboards } from '../../../services'; +import { getDataSourceQueryObj } from '../../../pages/utils/helpers'; export const renderTime = (time, options = { showFromNow: false }) => { const momentTime = moment(time); @@ -131,8 +133,10 @@ export const alertColumns = ( sortable: true, truncateText: false, render: (total, alert) => { - return ( + const alertId = `alerts_${alert.alerts[0].id}`; + const component = ( { openFlyout({ ...alert, @@ -152,6 +156,103 @@ export const alertColumns = ( {`${total} alerts`} ); + const contextProvider = async () => { + // 1. get monitor definition + const dataSourceQuery = getDataSourceQueryObj(); + const monitorResp = await httpClient.get( + `../api/alerting/monitors/${alert.monitor_id}`, + dataSourceQuery + ); + const monitorDefinition = monitorResp.resp; + delete monitorDefinition.ui_metadata; + delete monitorDefinition.data_sources; + + let monitorDefinitionStr = JSON.stringify(monitorDefinition); + + // 2. get data triggers the alert + let alertTriggeredByValue = ''; + let dsl = ''; + let index = ''; + if ( + monitorResp.resp.monitor_type === MONITOR_TYPE.QUERY_LEVEL || + monitorResp.resp.monitor_type === MONITOR_TYPE.BUCKET_LEVEL + ) { + const search = monitorResp.resp.inputs[0].search; + const indices = String(search.indices); + const splitIndices = indices.split(','); + index = splitIndices.length > 0 ? splitIndices[0].trim() : ''; + let query = JSON.stringify(search.query); + // Only keep the query part + dsl = JSON.stringify({ query: search.query.query }); + if (query.indexOf('{{period_end}}') !== -1) { + query = query.replaceAll('{{period_end}}', alert.start_time); + const alertStartTime = moment.utc(alert.start_time).format('YYYY-MM-DDTHH:mm:ss'); + dsl = dsl.replaceAll('{{period_end}}', alertStartTime); + // as we changed the format, remove it + dsl = dsl.replaceAll('"format":"epoch_millis",', ''); + monitorDefinitionStr = monitorDefinitionStr.replaceAll( + '{{period_end}}', + alertStartTime + ); + // as we changed the format, remove it + monitorDefinitionStr = monitorDefinitionStr.replaceAll('"format":"epoch_millis",', ''); + } + if (index) { + const alertData = await httpClient.post(`/api/console/proxy`, { + query: { + path: `${index}/_search`, + method: 'GET', + dataSourceId: dataSourceQuery ? dataSourceQuery.query.dataSourceId : '', + }, + body: query, + prependBasePath: true, + asResponse: true, + withLongNumeralsSupport: true, + }); + + alertTriggeredByValue = JSON.stringify( + alertData.body.aggregations?.metric.value || alertData.body.hits.total.value + ); + } + } + + const filteredAlert = { ...alert }; + const topN = 10; + const activeAlerts = alert.alerts.filter((alert) => alert.state === 'ACTIVE'); + // Reduce llm input token size by taking topN active alerts + filteredAlert.alerts = activeAlerts.slice(0, topN); + + // 3. build the context + return { + context: ` + Here is the detail information about alert ${alert.trigger_name} + ### Monitor definition\n ${monitorDefinitionStr}\n + ### Active Alert\n ${JSON.stringify(filteredAlert)}\n + ### Value triggers this alert\n ${alertTriggeredByValue}\n + ### Alert query DSL ${dsl} \n`, + additionalInfo: { + monitorType: monitorResp.resp.monitor_type, + dsl: dsl, + index: index, + }, + }; + }; + + const assistantEnabled = getApplication().capabilities?.assistant?.enabled === true; + const assistantFeatureStatus = getAssistantDashboards().getFeatureStatus(); + if (assistantFeatureStatus.alertInsight && assistantEnabled) { + getAssistantDashboards().registerIncontextInsight([ + { + key: alertId, + type: 'generate', + suggestions: [`Please summarize this alert, do not use any tool.`], + contextProvider, + }, + ]); + return getAssistantDashboards().renderIncontextInsight({ children: component }); + } else { + return component; + } }, }, { diff --git a/public/pages/Home/Home.js b/public/pages/Home/Home.js index 9174e1556..d5df162dc 100644 --- a/public/pages/Home/Home.js +++ b/public/pages/Home/Home.js @@ -125,7 +125,7 @@ export default class Home extends Component { /> )} /> - + diff --git a/public/pages/Main/Main.js b/public/pages/Main/Main.js index ca67c308e..26633c14b 100644 --- a/public/pages/Main/Main.js +++ b/public/pages/Main/Main.js @@ -91,7 +91,7 @@ class Main extends Component { handleDataSourceChange = ([dataSource]) => { const dataSourceId = dataSource?.id; - const dataSourceLabel = dataSource?.label + const dataSourceLabel = dataSource?.label; if (this.props.dataSourceEnabled && dataSourceId === undefined) { getNotifications().toasts.addDanger('Unable to set data source.'); } else if (this.state.selectedDataSourceId != dataSourceId) { diff --git a/public/plugin.tsx b/public/plugin.tsx index f0aa4431f..dc4e6d6ce 100644 --- a/public/plugin.tsx +++ b/public/plugin.tsx @@ -21,9 +21,10 @@ import { alertingTriggerAd } from './utils/contextMenu/triggers'; import { ExpressionsSetup } from '../../../src/plugins/expressions/public'; import { UiActionsSetup } from '../../../src/plugins/ui_actions/public'; import { overlayAlertsFunction } from './expressions/overlay_alerts'; -import { setClient, setEmbeddable, setNotifications, setOverlays, setSavedAugmentVisLoader, setUISettings, setQueryService, setSavedObjectsClient, setDataSourceEnabled, setDataSourceManagementPlugin, setNavigationUI, setApplication, setContentManagementStart } from './services'; +import { setClient, setEmbeddable, setNotifications, setOverlays, setSavedAugmentVisLoader, setUISettings, setQueryService, setSavedObjectsClient, setDataSourceEnabled, setDataSourceManagementPlugin, setNavigationUI, setApplication, setContentManagementStart, setAssistantDashboards } from './services'; import { VisAugmenterStart } from '../../../src/plugins/vis_augmenter/public'; import { DataPublicPluginStart } from '../../../src/plugins/data/public'; +import { AssistantSetup } from './types'; import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public'; import { DataSourcePluginSetup } from '../../../src/plugins/data_source/public'; import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public'; @@ -47,6 +48,7 @@ export interface AlertingSetupDeps { uiActions: UiActionsSetup; dataSourceManagement: DataSourceManagementPluginSetup; dataSource: DataSourcePluginSetup; + assistantDashboards?: AssistantSetup; } export interface AlertingStartDeps { @@ -69,14 +71,13 @@ export class AlertingPlugin implements Plugin(this.updateDefaultRouteOfManagementApplications); - public setup(core: CoreSetup, { expressions, uiActions, dataSourceManagement, dataSource }: AlertingSetupDeps) { + public setup(core: CoreSetup, { expressions, uiActions, dataSourceManagement, dataSource, assistantDashboards }: AlertingSetupDeps) { const mountWrapper = async (params: AppMountParameters, redirect: string) => { const { renderApp } = await import("./app"); const [coreStart] = await core.getStartServices(); return renderApp(coreStart, params, redirect); }; - core.application.register({ id: PLUGIN_NAME, title: 'Alerting', @@ -178,6 +179,8 @@ export class AlertingPlugin implements Plugin ({ chat: false, alertInsight: false }) }); + setUISettings(core.uiSettings); // Set the HTTP client so it can be pulled into expression fns to make diff --git a/public/services/services.ts b/public/services/services.ts index 8d098463e..f908cbb82 100644 --- a/public/services/services.ts +++ b/public/services/services.ts @@ -14,6 +14,7 @@ import { EmbeddableStart } from '../../../../src/plugins/embeddable/public'; import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; import { ContentManagementPluginStart } from '../../../../src/plugins/content_management/public'; import { createNullableGetterSetter } from './utils/helper'; +import { AssistantSetup } from '../types'; const ServicesContext = createContext(null); @@ -30,6 +31,10 @@ export const [getSavedAugmentVisLoader, setSavedAugmentVisLoader] = createGetter export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); +export const [getAssistantDashboards, setAssistantDashboards] = createGetterSetter< + AssistantSetup | {} +>('assistantDashboards'); + export const [getEmbeddable, setEmbeddable] = createGetterSetter('embeddable'); export const [getOverlays, setOverlays] = diff --git a/public/types.ts b/public/types.ts new file mode 100644 index 000000000..4734f3a91 --- /dev/null +++ b/public/types.ts @@ -0,0 +1,12 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Introduce a compile dependency on dashboards-assistant + * as alerting need some types from the plugin. + * It will give a 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';