Skip to content

Commit 10fba98

Browse files
opensearch-trigger-bot[bot]github-actions[bot]Hailong-am
authored
Context aware alert analysis (#996) (#1097)
* support date_nanos type * support context aware alert analysis * Register summary generation type of IncontextInsight for alert summarization * Fix dashboard unit test failure * Make each alert register its own IncontextInsight * Enable context aware alert only if feature flag is enabled * Avoid unnecessary change and minorly change summary question * Fix undefined alert name * Pass monitor type to additional info object of contextProvider * Address some comments and change feature flag * Add assistant capabilities check to control component rendering * Fix mismatched unit test snapshots * Handle the edge case of multiple indices in search and return more information in additionalInfo * Reduce llm context input size by taking topN active alerts * Distinguish source data and aggregation that trigger the alert * Rename the capability UI rendering flag per assistant plugin change * Remove alert sample data per current requirement from context --------- (cherry picked from commit 6dbdc2e) Signed-off-by: Hailong Cui <ihailong@amazon.com> Signed-off-by: Songkan Tang <songkant@amazon.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Hailong Cui <ihailong@amazon.com>
1 parent 4e7f2af commit 10fba98

File tree

9 files changed

+135
-8
lines changed

9 files changed

+135
-8
lines changed

opensearch_dashboards.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"opensearchDashboardsUtils",
1818
"contentManagement"
1919
],
20+
"optionalPlugins": ["assistantDashboards"],
2021
"server": true,
2122
"ui": true,
2223
"supportedOSDataSourceVersions": ">=2.13.0",

public/app.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export function renderApp(coreStart, params, defaultRoute) {
5252
defaultRoute: defaultRoute,
5353
}}
5454
>
55-
<Route render={(props) => <Main title="Alerting" {...mdsProps} {...navProps} {...props} />} />
55+
<Route
56+
render={(props) => <Main title="Alerting" {...mdsProps} {...navProps} {...props} />}
57+
/>
5658
</CoreContext.Provider>
5759
</ServicesContext.Provider>
5860
</Router>,

public/pages/Dashboard/containers/Dashboard.test.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { mount } from 'enzyme';
99
import Dashboard from './Dashboard';
1010
import { historyMock, httpClientMock } from '../../../../test/mocks';
1111
import { setupCoreStart } from '../../../../test/utils/helpers';
12+
import { setAssistantDashboards } from '../../../services';
1213

1314
const location = {
1415
hash: '',
@@ -62,6 +63,8 @@ beforeAll(() => {
6263
});
6364

6465
describe('Dashboard', () => {
66+
setAssistantDashboards({ getFeatureStatus: () => ({ chat: false, alertInsight: false }) });
67+
6568
beforeEach(() => {
6669
jest.clearAllMocks();
6770
});

public/pages/Dashboard/utils/tableUtils.js

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import React from 'react';
77
import _ from 'lodash';
88
import { EuiLink, EuiToolTip } from '@elastic/eui';
99
import moment from 'moment';
10-
import { ALERT_STATE, DEFAULT_EMPTY_DATA } from '../../../utils/constants';
10+
import { ALERT_STATE, DEFAULT_EMPTY_DATA, MONITOR_TYPE } from '../../../utils/constants';
11+
import { getApplication, getAssistantDashboards } from '../../../services';
12+
import { getDataSourceQueryObj } from '../../../pages/utils/helpers';
1113

1214
export const renderTime = (time, options = { showFromNow: false }) => {
1315
const momentTime = moment(time);
@@ -131,8 +133,10 @@ export const alertColumns = (
131133
sortable: true,
132134
truncateText: false,
133135
render: (total, alert) => {
134-
return (
136+
const alertId = `alerts_${alert.alerts[0].id}`;
137+
const component = (
135138
<EuiLink
139+
key={alertId}
136140
onClick={() => {
137141
openFlyout({
138142
...alert,
@@ -152,6 +156,103 @@ export const alertColumns = (
152156
{`${total} alerts`}
153157
</EuiLink>
154158
);
159+
const contextProvider = async () => {
160+
// 1. get monitor definition
161+
const dataSourceQuery = getDataSourceQueryObj();
162+
const monitorResp = await httpClient.get(
163+
`../api/alerting/monitors/${alert.monitor_id}`,
164+
dataSourceQuery
165+
);
166+
const monitorDefinition = monitorResp.resp;
167+
delete monitorDefinition.ui_metadata;
168+
delete monitorDefinition.data_sources;
169+
170+
let monitorDefinitionStr = JSON.stringify(monitorDefinition);
171+
172+
// 2. get data triggers the alert
173+
let alertTriggeredByValue = '';
174+
let dsl = '';
175+
let index = '';
176+
if (
177+
monitorResp.resp.monitor_type === MONITOR_TYPE.QUERY_LEVEL ||
178+
monitorResp.resp.monitor_type === MONITOR_TYPE.BUCKET_LEVEL
179+
) {
180+
const search = monitorResp.resp.inputs[0].search;
181+
const indices = String(search.indices);
182+
const splitIndices = indices.split(',');
183+
index = splitIndices.length > 0 ? splitIndices[0].trim() : '';
184+
let query = JSON.stringify(search.query);
185+
// Only keep the query part
186+
dsl = JSON.stringify({ query: search.query.query });
187+
if (query.indexOf('{{period_end}}') !== -1) {
188+
query = query.replaceAll('{{period_end}}', alert.start_time);
189+
const alertStartTime = moment.utc(alert.start_time).format('YYYY-MM-DDTHH:mm:ss');
190+
dsl = dsl.replaceAll('{{period_end}}', alertStartTime);
191+
// as we changed the format, remove it
192+
dsl = dsl.replaceAll('"format":"epoch_millis",', '');
193+
monitorDefinitionStr = monitorDefinitionStr.replaceAll(
194+
'{{period_end}}',
195+
alertStartTime
196+
);
197+
// as we changed the format, remove it
198+
monitorDefinitionStr = monitorDefinitionStr.replaceAll('"format":"epoch_millis",', '');
199+
}
200+
if (index) {
201+
const alertData = await httpClient.post(`/api/console/proxy`, {
202+
query: {
203+
path: `${index}/_search`,
204+
method: 'GET',
205+
dataSourceId: dataSourceQuery ? dataSourceQuery.query.dataSourceId : '',
206+
},
207+
body: query,
208+
prependBasePath: true,
209+
asResponse: true,
210+
withLongNumeralsSupport: true,
211+
});
212+
213+
alertTriggeredByValue = JSON.stringify(
214+
alertData.body.aggregations?.metric.value || alertData.body.hits.total.value
215+
);
216+
}
217+
}
218+
219+
const filteredAlert = { ...alert };
220+
const topN = 10;
221+
const activeAlerts = alert.alerts.filter((alert) => alert.state === 'ACTIVE');
222+
// Reduce llm input token size by taking topN active alerts
223+
filteredAlert.alerts = activeAlerts.slice(0, topN);
224+
225+
// 3. build the context
226+
return {
227+
context: `
228+
Here is the detail information about alert ${alert.trigger_name}
229+
### Monitor definition\n ${monitorDefinitionStr}\n
230+
### Active Alert\n ${JSON.stringify(filteredAlert)}\n
231+
### Value triggers this alert\n ${alertTriggeredByValue}\n
232+
### Alert query DSL ${dsl} \n`,
233+
additionalInfo: {
234+
monitorType: monitorResp.resp.monitor_type,
235+
dsl: dsl,
236+
index: index,
237+
},
238+
};
239+
};
240+
241+
const assistantEnabled = getApplication().capabilities?.assistant?.enabled === true;
242+
const assistantFeatureStatus = getAssistantDashboards().getFeatureStatus();
243+
if (assistantFeatureStatus.alertInsight && assistantEnabled) {
244+
getAssistantDashboards().registerIncontextInsight([
245+
{
246+
key: alertId,
247+
type: 'generate',
248+
suggestions: [`Please summarize this alert, do not use any tool.`],
249+
contextProvider,
250+
},
251+
]);
252+
return getAssistantDashboards().renderIncontextInsight({ children: component });
253+
} else {
254+
return component;
255+
}
155256
},
156257
},
157258
{

public/pages/Home/Home.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export default class Home extends Component {
125125
/>
126126
)}
127127
/>
128-
<Redirect to={defaultRoute || "/dashboard"} />
128+
<Redirect to={defaultRoute || '/dashboard'} />
129129
</Switch>
130130
</div>
131131
</div>

public/pages/Main/Main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ class Main extends Component {
9191

9292
handleDataSourceChange = ([dataSource]) => {
9393
const dataSourceId = dataSource?.id;
94-
const dataSourceLabel = dataSource?.label
94+
const dataSourceLabel = dataSource?.label;
9595
if (this.props.dataSourceEnabled && dataSourceId === undefined) {
9696
getNotifications().toasts.addDanger('Unable to set data source.');
9797
} else if (this.state.selectedDataSourceId != dataSourceId) {

public/plugin.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ import { alertingTriggerAd } from './utils/contextMenu/triggers';
2121
import { ExpressionsSetup } from '../../../src/plugins/expressions/public';
2222
import { UiActionsSetup } from '../../../src/plugins/ui_actions/public';
2323
import { overlayAlertsFunction } from './expressions/overlay_alerts';
24-
import { setClient, setEmbeddable, setNotifications, setOverlays, setSavedAugmentVisLoader, setUISettings, setQueryService, setSavedObjectsClient, setDataSourceEnabled, setDataSourceManagementPlugin, setNavigationUI, setApplication, setContentManagementStart } from './services';
24+
import { setClient, setEmbeddable, setNotifications, setOverlays, setSavedAugmentVisLoader, setUISettings, setQueryService, setSavedObjectsClient, setDataSourceEnabled, setDataSourceManagementPlugin, setNavigationUI, setApplication, setContentManagementStart, setAssistantDashboards } from './services';
2525
import { VisAugmenterStart } from '../../../src/plugins/vis_augmenter/public';
2626
import { DataPublicPluginStart } from '../../../src/plugins/data/public';
27+
import { AssistantSetup } from './types';
2728
import { DataSourceManagementPluginSetup } from '../../../src/plugins/data_source_management/public';
2829
import { DataSourcePluginSetup } from '../../../src/plugins/data_source/public';
2930
import { NavigationPublicPluginStart } from '../../../src/plugins/navigation/public';
@@ -47,6 +48,7 @@ export interface AlertingSetupDeps {
4748
uiActions: UiActionsSetup;
4849
dataSourceManagement: DataSourceManagementPluginSetup;
4950
dataSource: DataSourcePluginSetup;
51+
assistantDashboards?: AssistantSetup;
5052
}
5153

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

7173

72-
public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { expressions, uiActions, dataSourceManagement, dataSource }: AlertingSetupDeps) {
74+
public setup(core: CoreSetup<AlertingStartDeps, AlertingStart>, { expressions, uiActions, dataSourceManagement, dataSource, assistantDashboards }: AlertingSetupDeps) {
7375

7476
const mountWrapper = async (params: AppMountParameters, redirect: string) => {
7577
const { renderApp } = await import("./app");
7678
const [coreStart] = await core.getStartServices();
7779
return renderApp(coreStart, params, redirect);
7880
};
79-
8081
core.application.register({
8182
id: PLUGIN_NAME,
8283
title: 'Alerting',
@@ -178,6 +179,8 @@ export class AlertingPlugin implements Plugin<void, AlertingStart, AlertingSetup
178179
);
179180
}
180181

182+
setAssistantDashboards(assistantDashboards || { getFeatureStatus: () => ({ chat: false, alertInsight: false }) });
183+
181184
setUISettings(core.uiSettings);
182185

183186
// Set the HTTP client so it can be pulled into expression fns to make

public/services/services.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
1414
import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public';
1515
import { ContentManagementPluginStart } from '../../../../src/plugins/content_management/public';
1616
import { createNullableGetterSetter } from './utils/helper';
17+
import { AssistantSetup } from '../types';
1718

1819
const ServicesContext = createContext<BrowserServices | null>(null);
1920

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

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

34+
export const [getAssistantDashboards, setAssistantDashboards] = createGetterSetter<
35+
AssistantSetup | {}
36+
>('assistantDashboards');
37+
3338
export const [getEmbeddable, setEmbeddable] = createGetterSetter<EmbeddableStart>('embeddable');
3439

3540
export const [getOverlays, setOverlays] =

public/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* Introduce a compile dependency on dashboards-assistant
8+
* as alerting need some types from the plugin.
9+
* It will give a type error when dashboards-assistant is not installed so add a ts-ignore to suppress the error.
10+
*/
11+
// @ts-ignore
12+
export type { AssistantSetup } from '../../dashboards-assistant/public';

0 commit comments

Comments
 (0)