Skip to content

Commit 588faec

Browse files
committed
fetch instance state when loading details page
1 parent ad6d76e commit 588faec

11 files changed

Lines changed: 266 additions & 7 deletions

File tree

x-pack/legacy/plugins/alerting/common/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
*/
66

77
export * from './types';
8+
export { AlertTaskState } from '../server';

x-pack/legacy/plugins/alerting/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ export type AlertsClient = PublicMethodsOf<AlertsClientClass>;
1010

1111
export { init } from './init';
1212
export { AlertType, AlertingPlugin, AlertExecutorOptions } from './types';
13+
export { AlertTaskState } from './task_runner';
1314
export { PluginSetupContract, PluginStartContract } from './plugin';

x-pack/legacy/plugins/alerting/server/task_runner/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
export { AlertTaskState } from './alert_task_instance';
78
export { TaskRunnerFactory } from './task_runner_factory';

x-pack/legacy/plugins/alerting/server/types.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ import { AlertInstance } from './alert_instance';
88
import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry';
99
import { PluginSetupContract, PluginStartContract } from './plugin';
1010
import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../../src/core/server';
11-
import { Alert } from '../common';
12-
13-
export * from '../common';
11+
import { Alert } from '../common/types';
12+
export * from '../common/types';
1413

1514
export type State = Record<string, any>;
1615
export type Context = Record<string, any>;

x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,31 @@ describe('loadAlert', () => {
7676
});
7777
});
7878

79+
describe('loadAlertState', () => {
80+
test('should call get API with base parameters', async () => {
81+
const alertId = uuid.v4();
82+
const resolvedValue = {
83+
id: alertId,
84+
name: 'name',
85+
tags: [],
86+
enabled: true,
87+
alertTypeId: '.noop',
88+
schedule: { interval: '1s' },
89+
actions: [],
90+
params: {},
91+
createdBy: null,
92+
updatedBy: null,
93+
throttle: null,
94+
muteAll: false,
95+
mutedInstanceIds: [],
96+
};
97+
http.get.mockResolvedValueOnce(resolvedValue);
98+
99+
expect(await loadAlert({ http, alertId })).toEqual(resolvedValue);
100+
expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}/state`);
101+
});
102+
});
103+
79104
describe('loadAlerts', () => {
80105
test('should call find API with base parameters', async () => {
81106
const resolvedValue = {

x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { HttpSetup } from 'kibana/public';
88
import { BASE_ALERT_API_PATH } from '../constants';
9-
import { Alert, AlertType, AlertWithoutId } from '../../types';
9+
import { Alert, AlertType, AlertWithoutId, AlertTaskState } from '../../types';
1010

1111
export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise<AlertType[]> {
1212
return await http.get(`${BASE_ALERT_API_PATH}/types`);
@@ -22,6 +22,16 @@ export async function loadAlert({
2222
return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`);
2323
}
2424

25+
export async function loadAlertState({
26+
http,
27+
alertId,
28+
}: {
29+
http: HttpSetup;
30+
alertId: string;
31+
}): Promise<AlertTaskState> {
32+
return await http.get(`${BASE_ALERT_API_PATH}/${alertId}/state`);
33+
}
34+
2535
export async function loadAlerts({
2636
http,
2737
page,

x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_details/components/alert_details.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
ComponentOpts as BulkOperationsComponentOpts,
2929
withBulkAlertOperations,
3030
} from '../../common/components/with_bulk_alert_api_operations';
31+
import { AlertInstancesRouteWithApi } from './alert_instances_route';
3132

3233
type AlertDetailsProps = {
3334
alert: Alert;
@@ -166,6 +167,9 @@ export const AlertDetails: React.FunctionComponent<AlertDetailsProps> = ({
166167
</EuiFlexGroup>
167168
</EuiFlexItem>
168169
</EuiFlexGroup>
170+
<EuiFlexGroup>
171+
<AlertInstancesRouteWithApi alert={alert} />
172+
</EuiFlexGroup>
169173
</EuiPageContentBody>
170174
</EuiPageContent>
171175
</EuiPageBody>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import * as React from 'react';
7+
import uuid from 'uuid';
8+
import { shallow } from 'enzyme';
9+
import { ToastsApi } from 'kibana/public';
10+
import { AlertInstancesRoute, getAlertState } from './alert_instances_route';
11+
import { Alert } from '../../../../types';
12+
import { EuiLoadingSpinner } from '@elastic/eui';
13+
14+
jest.mock('../../../app_context', () => {
15+
const toastNotifications = jest.fn();
16+
return {
17+
useAppDependencies: jest.fn(() => ({ toastNotifications })),
18+
};
19+
});
20+
describe('alert_state_route', () => {
21+
it('render a loader while fetching data', () => {
22+
const alert = mockAlert();
23+
24+
expect(
25+
shallow(<AlertInstancesRoute alert={alert} {...mockApis()} />).containsMatchingElement(
26+
<EuiLoadingSpinner size="l" />
27+
)
28+
).toBeTruthy();
29+
});
30+
});
31+
32+
describe('getAlertState useEffect handler', () => {
33+
beforeEach(() => {
34+
jest.clearAllMocks();
35+
});
36+
37+
it('fetches alert state', async () => {
38+
const alert = mockAlert();
39+
const alertState = mockAlertState();
40+
const { loadAlertState } = mockApis();
41+
const { setAlertState } = mockStateSetter();
42+
43+
loadAlertState.mockImplementationOnce(async () => alertState);
44+
45+
const toastNotifications = ({
46+
addDanger: jest.fn(),
47+
} as unknown) as ToastsApi;
48+
49+
await getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications);
50+
51+
expect(loadAlertState).toHaveBeenCalledWith(alert.id);
52+
expect(setAlertState).toHaveBeenCalledWith(alertState);
53+
});
54+
55+
it('displays an error if the alert state isnt found', async () => {
56+
const actionType = {
57+
id: '.server-log',
58+
name: 'Server log',
59+
enabled: true,
60+
};
61+
const alert = mockAlert({
62+
actions: [
63+
{
64+
group: '',
65+
id: uuid.v4(),
66+
actionTypeId: actionType.id,
67+
params: {},
68+
},
69+
],
70+
});
71+
72+
const { loadAlertState } = mockApis();
73+
const { setAlertState } = mockStateSetter();
74+
75+
loadAlertState.mockImplementation(async () => {
76+
throw new Error('OMG');
77+
});
78+
79+
const toastNotifications = ({
80+
addDanger: jest.fn(),
81+
} as unknown) as ToastsApi;
82+
await getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications);
83+
expect(toastNotifications.addDanger).toHaveBeenCalledTimes(1);
84+
expect(toastNotifications.addDanger).toHaveBeenCalledWith({
85+
title: 'Unable to load alert state: OMG',
86+
});
87+
});
88+
});
89+
90+
function mockApis() {
91+
return {
92+
loadAlertState: jest.fn(),
93+
};
94+
}
95+
96+
function mockStateSetter() {
97+
return {
98+
setAlertState: jest.fn(),
99+
};
100+
}
101+
102+
function mockAlert(overloads: Partial<Alert> = {}): Alert {
103+
return {
104+
id: uuid.v4(),
105+
enabled: true,
106+
name: `alert-${uuid.v4()}`,
107+
tags: [],
108+
alertTypeId: '.noop',
109+
consumer: 'consumer',
110+
schedule: { interval: '1m' },
111+
actions: [],
112+
params: {},
113+
createdBy: null,
114+
updatedBy: null,
115+
createdAt: new Date(),
116+
updatedAt: new Date(),
117+
apiKeyOwner: null,
118+
throttle: null,
119+
muteAll: false,
120+
mutedInstanceIds: [],
121+
...overloads,
122+
};
123+
}
124+
125+
function mockAlertState(overloads: Partial<any> = {}): any {
126+
return {
127+
alertTypeState: {
128+
some: 'value',
129+
},
130+
alertInstances: {
131+
first_instance: {
132+
state: {},
133+
meta: {
134+
lastScheduledActions: {
135+
group: 'first_group',
136+
date: new Date(),
137+
},
138+
},
139+
},
140+
second_instance: {},
141+
},
142+
};
143+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { i18n } from '@kbn/i18n';
8+
import { ToastsApi } from 'kibana/public';
9+
import React, { useState, useEffect } from 'react';
10+
import { EuiLoadingSpinner } from '@elastic/eui';
11+
import { Alert, AlertTaskState } from '../../../../types';
12+
import { useAppDependencies } from '../../../app_context';
13+
import {
14+
ComponentOpts as AlertApis,
15+
withBulkAlertOperations,
16+
} from '../../common/components/with_bulk_alert_api_operations';
17+
18+
type WithAlertStateProps = {
19+
alert: Alert;
20+
} & Pick<AlertApis, 'loadAlertState'>;
21+
22+
export const AlertInstancesRoute: React.FunctionComponent<WithAlertStateProps> = ({
23+
alert,
24+
loadAlertState,
25+
}) => {
26+
const { http, toastNotifications } = useAppDependencies();
27+
28+
const [alertState, setAlertState] = useState<AlertTaskState | null>(null);
29+
30+
useEffect(() => {
31+
getAlertState(alert.id, loadAlertState, setAlertState, toastNotifications);
32+
}, [alert, http, loadAlertState, toastNotifications]);
33+
34+
return alertState ? (
35+
<div>{JSON.stringify({ alert, alertState })}</div>
36+
) : (
37+
<div
38+
style={{
39+
textAlign: 'center',
40+
margin: '4em 0em',
41+
}}
42+
>
43+
<EuiLoadingSpinner size="l" />
44+
</div>
45+
);
46+
};
47+
48+
export async function getAlertState(
49+
alertId: string,
50+
loadAlertState: AlertApis['loadAlertState'],
51+
setAlertState: React.Dispatch<React.SetStateAction<AlertTaskState | null>>,
52+
toastNotifications: Pick<ToastsApi, 'addDanger'>
53+
) {
54+
try {
55+
const loadedState = await loadAlertState(alertId);
56+
setAlertState(loadedState);
57+
} catch (e) {
58+
toastNotifications.addDanger({
59+
title: i18n.translate(
60+
'xpack.triggersActionsUI.sections.alertDetails.unableToLoadAlertStateMessage',
61+
{
62+
defaultMessage: 'Unable to load alert state: {message}',
63+
values: {
64+
message: e.message,
65+
},
66+
}
67+
),
68+
});
69+
}
70+
}
71+
72+
export const AlertInstancesRouteWithApi = withBulkAlertOperations(AlertInstancesRoute);

x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/common/components/with_bulk_alert_api_operations.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import React from 'react';
88

9-
import { Alert, AlertType } from '../../../../types';
9+
import { Alert, AlertType, AlertTaskState } from '../../../../types';
1010
import { useAppDependencies } from '../../../app_context';
1111
import {
1212
deleteAlerts,
@@ -20,6 +20,7 @@ import {
2020
muteAlert,
2121
unmuteAlert,
2222
loadAlert,
23+
loadAlertState,
2324
loadAlertTypes,
2425
} from '../../../lib/alert_api';
2526

@@ -35,6 +36,7 @@ export interface ComponentOpts {
3536
disableAlert: (alert: Alert) => Promise<void>;
3637
deleteAlert: (alert: Alert) => Promise<void>;
3738
loadAlert: (id: Alert['id']) => Promise<Alert>;
39+
loadAlertState: (id: Alert['id']) => Promise<AlertTaskState>;
3840
loadAlertTypes: () => Promise<AlertType[]>;
3941
}
4042

@@ -88,6 +90,7 @@ export function withBulkAlertOperations<T>(
8890
}}
8991
deleteAlert={async (alert: Alert) => deleteAlert({ http, id: alert.id })}
9092
loadAlert={async (alertId: Alert['id']) => loadAlert({ http, alertId })}
93+
loadAlertState={async (alertId: Alert['id']) => loadAlertState({ http, alertId })}
9194
loadAlertTypes={async () => loadAlertTypes({ http })}
9295
/>
9396
);

0 commit comments

Comments
 (0)