Skip to content

Commit

Permalink
Context aware alert analysis (#996)
Browse files Browse the repository at this point in the history
* support date_nanos type

Signed-off-by: Hailong Cui <[email protected]>

* support context aware alert analysis

Signed-off-by: Hailong Cui <[email protected]>

* Register summary generation type of IncontextInsight for alert summarization

Signed-off-by: Songkan Tang <[email protected]>

* Fix dashboard unit test failure

Signed-off-by: Songkan Tang <[email protected]>

* Make each alert register its own IncontextInsight

Signed-off-by: Songkan Tang <[email protected]>

* Enable context aware alert only if feature flag is enabled

Signed-off-by: Songkan Tang <[email protected]>

* Avoid unnecessary change and minorly change summary question

Signed-off-by: Songkan Tang <[email protected]>

* Fix undefined alert name

Signed-off-by: Songkan Tang <[email protected]>

* Pass monitor type to additional info object of contextProvider

Signed-off-by: Songkan Tang <[email protected]>

* Address some comments and change feature flag

Signed-off-by: Songkan Tang <[email protected]>

* Add assistant capabilities check to control component rendering

Signed-off-by: Songkan Tang <[email protected]>

* Fix mismatched unit test snapshots

Signed-off-by: Songkan Tang <[email protected]>

* Handle the edge case of multiple indices in search and return more information in additionalInfo

Signed-off-by: Songkan Tang <[email protected]>

* Reduce llm context input size by taking topN active alerts

Signed-off-by: Songkan Tang <[email protected]>

* Distinguish source data and aggregation that trigger the alert

Signed-off-by: Songkan Tang <[email protected]>

* Rename the capability UI rendering flag per assistant plugin change

Signed-off-by: Songkan Tang <[email protected]>

* Remove alert sample data per current requirement from context

Signed-off-by: Songkan Tang <[email protected]>

---------

Signed-off-by: Hailong Cui <[email protected]>
Signed-off-by: Songkan Tang <[email protected]>
Co-authored-by: Hailong Cui <[email protected]>
Signed-off-by: AWSHurneyt <[email protected]>
  • Loading branch information
2 people authored and AWSHurneyt committed Sep 19, 2024
1 parent 9378a47 commit 471b376
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 8 deletions.
1 change: 1 addition & 0 deletions opensearch_dashboards.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"opensearchDashboardsUtils",
"contentManagement"
],
"optionalPlugins": ["assistantDashboards"],
"server": true,
"ui": true,
"supportedOSDataSourceVersions": ">=2.13.0",
Expand Down
4 changes: 3 additions & 1 deletion public/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ export function renderApp(coreStart, params, defaultRoute) {
defaultRoute: defaultRoute,
}}
>
<Route render={(props) => <Main title="Alerting" {...mdsProps} {...navProps} {...props} />} />
<Route
render={(props) => <Main title="Alerting" {...mdsProps} {...navProps} {...props} />}
/>
</CoreContext.Provider>
</ServicesContext.Provider>
</Router>,
Expand Down
3 changes: 3 additions & 0 deletions public/pages/Dashboard/containers/Dashboard.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: '',
Expand Down Expand Up @@ -62,6 +63,8 @@ beforeAll(() => {
});

describe('Dashboard', () => {
setAssistantDashboards({ getFeatureStatus: () => ({ chat: false, alertInsight: false }) });

beforeEach(() => {
jest.clearAllMocks();
});
Expand Down
105 changes: 103 additions & 2 deletions public/pages/Dashboard/utils/tableUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -131,8 +133,10 @@ export const alertColumns = (
sortable: true,
truncateText: false,
render: (total, alert) => {
return (
const alertId = `alerts_${alert.alerts[0].id}`;
const component = (
<EuiLink
key={alertId}
onClick={() => {
openFlyout({
...alert,
Expand All @@ -152,6 +156,103 @@ export const alertColumns = (
{`${total} alerts`}
</EuiLink>
);
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;
}
},
},
{
Expand Down
2 changes: 1 addition & 1 deletion public/pages/Home/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export default class Home extends Component {
/>
)}
/>
<Redirect to={defaultRoute || "/dashboard"} />
<Redirect to={defaultRoute || '/dashboard'} />
</Switch>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion public/pages/Main/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
9 changes: 6 additions & 3 deletions public/plugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -47,6 +48,7 @@ export interface AlertingSetupDeps {
uiActions: UiActionsSetup;
dataSourceManagement: DataSourceManagementPluginSetup;
dataSource: DataSourcePluginSetup;
assistantDashboards?: AssistantSetup;
}

export interface AlertingStartDeps {
Expand All @@ -69,14 +71,13 @@ export class AlertingPlugin implements Plugin<void, AlertingStart, AlertingSetup
private appStateUpdater = new BehaviorSubject<AppUpdater>(this.updateDefaultRouteOfManagementApplications);


public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { expressions, uiActions, dataSourceManagement, dataSource }: AlertingSetupDeps) {
public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { 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',
Expand Down Expand Up @@ -178,6 +179,8 @@ export class AlertingPlugin implements Plugin<void, AlertingStart, AlertingSetup
);
}

setAssistantDashboards(assistantDashboards || { getFeatureStatus: () => ({ chat: false, alertInsight: false }) });

setUISettings(core.uiSettings);

// Set the HTTP client so it can be pulled into expression fns to make
Expand Down
5 changes: 5 additions & 0 deletions public/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BrowserServices | null>(null);

Expand All @@ -30,6 +31,10 @@ export const [getSavedAugmentVisLoader, setSavedAugmentVisLoader] = createGetter

export const [getUISettings, setUISettings] = createGetterSetter<IUiSettingsClient>('UISettings');

export const [getAssistantDashboards, setAssistantDashboards] = createGetterSetter<
AssistantSetup | {}
>('assistantDashboards');

export const [getEmbeddable, setEmbeddable] = createGetterSetter<EmbeddableStart>('embeddable');

export const [getOverlays, setOverlays] =
Expand Down
12 changes: 12 additions & 0 deletions public/types.ts
Original file line number Diff line number Diff line change
@@ -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';

0 comments on commit 471b376

Please sign in to comment.