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

adds the AlertDetails page #55671

Merged
merged 40 commits into from
Feb 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0fd0a80
link AlertsList to AlertDetails page
gmmorris Jan 23, 2020
fa9d38e
fixed details jest test
gmmorris Jan 23, 2020
2723a48
load alert when entering alert details page
gmmorris Jan 23, 2020
6581e62
fixed usage of history to navigate in alertslist
gmmorris Jan 23, 2020
b5ddae0
separated route from details
gmmorris Jan 23, 2020
1dc6360
moved details page out from under the alerts section
gmmorris Jan 23, 2020
2f93c37
fixed shared types across alerting
gmmorris Jan 24, 2020
d245834
link AlertsList to AlertDetails page
gmmorris Jan 23, 2020
4aa7305
fixed details jest test
gmmorris Jan 23, 2020
927f813
load alert when entering alert details page
gmmorris Jan 23, 2020
0800648
fixed usage of history to navigate in alertslist
gmmorris Jan 23, 2020
fdaf7da
separated route from details
gmmorris Jan 23, 2020
761fe9d
moved details page out from under the alerts section
gmmorris Jan 23, 2020
648ebe2
Merge branch 'alerting/details-ui' of github.com:gmmorris/kibana into…
gmmorris Jan 24, 2020
a8fdf2a
fixed date issue in snapshots
gmmorris Jan 24, 2020
ab8c101
introduce a SanitizedAlert which omits encrypted fields
gmmorris Jan 24, 2020
1f3dc22
render action labels
gmmorris Jan 24, 2020
880bed5
Merge branch 'alerting/types' into alerting/details-ui
gmmorris Jan 24, 2020
d18c5bb
added validation around api calls for alert details
gmmorris Jan 27, 2020
bb16a03
split bulk_actions component into sub components for better composition
gmmorris Jan 27, 2020
595b7c4
added mute and enable buttonss to details page
gmmorris Jan 27, 2020
f3ac682
removed unused component
gmmorris Jan 27, 2020
a0803a0
added function tests around details page ui
gmmorris Jan 27, 2020
e8079d5
fixed types
gmmorris Jan 27, 2020
046ac9a
fixed jest tests in detials page
gmmorris Jan 28, 2020
c011aa9
Merge branch 'master' into alerting/details-ui
gmmorris Jan 28, 2020
fb4f3e1
removed mock alert
gmmorris Jan 28, 2020
3472a1b
cleaned up refactor of extracting alert api usage
gmmorris Jan 28, 2020
82f0cd6
fixed jest tests for details
gmmorris Jan 28, 2020
c0efea9
removed mock alert
gmmorris Jan 28, 2020
03ed564
added missing tests in details route
gmmorris Jan 28, 2020
100aa8d
Merge branch 'master' into alerting/details-ui
gmmorris Jan 28, 2020
e265278
fixed FTs for details page
gmmorris Jan 28, 2020
20e048b
removed unused import
gmmorris Jan 29, 2020
2961ecd
Merge branch 'master' into alerting/details-ui
gmmorris Jan 29, 2020
1520209
fixed usage of capablities in details page
gmmorris Jan 29, 2020
3e21d40
Merge branch 'master' into alerting/details-ui
gmmorris Jan 29, 2020
467bbc4
Merge branch 'master' into alerting/details-ui
gmmorris Jan 31, 2020
900d7f3
cleaned up typing and duplication
gmmorris Jan 31, 2020
50d74ad
Merge branch 'master' into alerting/details-ui
elasticmachine Feb 3, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import {
IUiSettingsClient,
ApplicationStart,
} from 'kibana/public';
import { BASE_PATH, Section } from './constants';
import { BASE_PATH, Section, routeToAlertDetails } from './constants';
import { TriggersActionsUIHome } from './home';
import { AppContextProvider, useAppDependencies } from './app_context';
import { hasShowAlertsCapability } from './lib/capabilities';
import { LegacyDependencies, ActionTypeModel, AlertTypeModel } from '../types';
import { TypeRegistry } from './type_registry';
import { AlertDetailsRouteWithApi as AlertDetailsRoute } from './sections/alert_details/components/alert_details_route';

export interface AppDeps {
chrome: ChromeStart;
Expand Down Expand Up @@ -53,11 +54,8 @@ export const AppWithoutRouter = ({ sectionsRegex }: any) => {
const DEFAULT_SECTION: Section = canShowAlerts ? 'alerts' : 'connectors';
return (
<Switch>
<Route
exact
path={`${BASE_PATH}/:section(${sectionsRegex})`}
component={TriggersActionsUIHome}
/>
<Route path={`${BASE_PATH}/:section(${sectionsRegex})`} component={TriggersActionsUIHome} />
{canShowAlerts && <Route path={routeToAlertDetails} component={AlertDetailsRoute} />}
<Redirect from={`${BASE_PATH}`} to={`${BASE_PATH}/${DEFAULT_SECTION}`} />
</Switch>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export type Section = 'connectors' | 'alerts';
export const routeToHome = `${BASE_PATH}`;
export const routeToConnectors = `${BASE_PATH}/connectors`;
export const routeToAlerts = `${BASE_PATH}/alerts`;
export const routeToAlertDetails = `${BASE_PATH}/alert/:alertId`;

export { TIME_UNITS } from './time_units';
export enum SORT_ORDERS {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ describe('loadActionTypes', () => {
{
id: 'test',
name: 'Test',
enabled: true,
},
];
http.get.mockResolvedValueOnce(resolvedValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ import { Alert, AlertType } from '../../types';
import { httpServiceMock } from '../../../../../../../../src/core/public/mocks';
import {
createAlert,
deleteAlert,
deleteAlerts,
disableAlerts,
enableAlerts,
disableAlert,
enableAlert,
loadAlert,
loadAlerts,
loadAlertTypes,
muteAlerts,
unmuteAlerts,
muteAlert,
unmuteAlert,
updateAlert,
} from './alert_api';
import uuid from 'uuid';

const http = httpServiceMock.createStartContract();

Expand All @@ -42,6 +49,31 @@ describe('loadAlertTypes', () => {
});
});

describe('loadAlert', () => {
test('should call get API with base parameters', async () => {
const alertId = uuid.v4();
const resolvedValue = {
id: alertId,
name: 'name',
tags: [],
enabled: true,
alertTypeId: '.noop',
schedule: { interval: '1s' },
actions: [],
params: {},
createdBy: null,
updatedBy: null,
throttle: null,
muteAll: false,
mutedInstanceIds: [],
};
http.get.mockResolvedValueOnce(resolvedValue);

expect(await loadAlert({ http, alertId })).toEqual(resolvedValue);
expect(http.get).toHaveBeenCalledWith(`/api/alert/${alertId}`);
});
});

describe('loadAlerts', () => {
test('should call find API with base parameters', async () => {
const resolvedValue = {
Expand Down Expand Up @@ -230,6 +262,19 @@ describe('loadAlerts', () => {
});
});

describe('deleteAlert', () => {
test('should call delete API for alert', async () => {
const id = '1';
const result = await deleteAlert({ http, id });
expect(result).toEqual(undefined);
expect(http.delete.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"/api/alert/1",
]
`);
});
});

describe('deleteAlerts', () => {
test('should call delete API for each alert', async () => {
const ids = ['1', '2', '3'];
Expand Down Expand Up @@ -335,6 +380,62 @@ describe('updateAlert', () => {
});
});

describe('enableAlert', () => {
test('should call enable alert API', async () => {
const result = await enableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alert/1/_enable",
],
]
`);
});
});

describe('disableAlert', () => {
test('should call disable alert API', async () => {
const result = await disableAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alert/1/_disable",
],
]
`);
});
});

describe('muteAlert', () => {
test('should call mute alert API', async () => {
const result = await muteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alert/1/_mute_all",
],
]
`);
});
});

describe('unmuteAlert', () => {
test('should call unmute alert API', async () => {
const result = await unmuteAlert({ http, id: '1' });
expect(result).toEqual(undefined);
expect(http.post.mock.calls).toMatchInlineSnapshot(`
Array [
Array [
"/api/alert/1/_unmute_all",
],
]
`);
});
});

describe('enableAlerts', () => {
test('should call enable alert API per alert', async () => {
const ids = ['1', '2', '3'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise<Ale
return await http.get(`${BASE_ALERT_API_PATH}/types`);
}

export async function loadAlert({
http,
alertId,
}: {
http: HttpSetup;
alertId: string;
}): Promise<Alert> {
return await http.get(`${BASE_ALERT_API_PATH}/${alertId}`);
}

export async function loadAlerts({
http,
page,
Expand Down Expand Up @@ -55,14 +65,18 @@ export async function loadAlerts({
});
}

export async function deleteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.delete(`${BASE_ALERT_API_PATH}/${id}`);
}

export async function deleteAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map(id => http.delete(`${BASE_ALERT_API_PATH}/${id}`)));
await Promise.all(ids.map(id => deleteAlert({ http, id })));
}

export async function createAlert({
Expand Down Expand Up @@ -91,14 +105,22 @@ export async function updateAlert({
});
}

export async function enableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERT_API_PATH}/${id}/_enable`);
}

export async function enableAlerts({
ids,
http,
}: {
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_enable`)));
await Promise.all(ids.map(id => enableAlert({ id, http })));
}

export async function disableAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERT_API_PATH}/${id}/_disable`);
}

export async function disableAlerts({
Expand All @@ -108,11 +130,19 @@ export async function disableAlerts({
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_disable`)));
await Promise.all(ids.map(id => disableAlert({ id, http })));
}

export async function muteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERT_API_PATH}/${id}/_mute_all`);
}

export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise<void> {
await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_mute_all`)));
await Promise.all(ids.map(id => muteAlert({ http, id })));
}

export async function unmuteAlert({ id, http }: { id: string; http: HttpSetup }): Promise<void> {
await http.post(`${BASE_ALERT_API_PATH}/${id}/_unmute_all`);
}

export async function unmuteAlerts({
Expand All @@ -122,5 +152,5 @@ export async function unmuteAlerts({
ids: string[];
http: HttpSetup;
}): Promise<void> {
await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_unmute_all`)));
await Promise.all(ids.map(id => unmuteAlert({ id, http })));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { throwIfAbsent, throwIfIsntContained } from './value_validators';
import uuid from 'uuid';

describe('throwIfAbsent', () => {
test('throws if value is absent', () => {
[undefined, null].forEach(val => {
expect(() => {
throwIfAbsent('OMG no value')(val);
}).toThrowErrorMatchingInlineSnapshot(`"OMG no value"`);
});
});

test('doesnt throws if value is present but falsey', () => {
[false, ''].forEach(val => {
expect(throwIfAbsent('OMG no value')(val)).toEqual(val);
});
});

test('doesnt throw if value is present', () => {
expect(throwIfAbsent('OMG no value')({})).toEqual({});
});
});

describe('throwIfIsntContained', () => {
test('throws if value is absent', () => {
expect(() => {
throwIfIsntContained<string>(new Set([uuid.v4()]), 'OMG no value', val => val)([uuid.v4()]);
}).toThrowErrorMatchingInlineSnapshot(`"OMG no value"`);
});

test('throws if value is absent using custom message', () => {
const id = uuid.v4();
expect(() => {
throwIfIsntContained<string>(
new Set([id]),
(value: string) => `OMG no ${value}`,
val => val
)([uuid.v4()]);
}).toThrow(`OMG no ${id}`);
});

test('returns values if value is present', () => {
const id = uuid.v4();
const values = [uuid.v4(), uuid.v4(), id, uuid.v4()];
expect(throwIfIsntContained<string>(new Set([id]), 'OMG no value', val => val)(values)).toEqual(
values
);
});

test('returns values if multiple values is present', () => {
const [firstId, secondId] = [uuid.v4(), uuid.v4()];
const values = [uuid.v4(), uuid.v4(), secondId, uuid.v4(), firstId];
expect(
throwIfIsntContained<string>(new Set([firstId, secondId]), 'OMG no value', val => val)(values)
).toEqual(values);
});

test('allows a custom value extractor', () => {
const [firstId, secondId] = [uuid.v4(), uuid.v4()];
const values = [
{ id: firstId, some: 'prop' },
{ id: secondId, someOther: 'prop' },
];
expect(
throwIfIsntContained<{ id: string }>(
new Set([firstId, secondId]),
'OMG no value',
(val: { id: string }) => val.id
)(values)
).toEqual(values);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { constant } from 'lodash';

export function throwIfAbsent<T>(message: string) {
return (value: T | undefined): T => {
if (value === undefined || value === null) {
throw new Error(message);
}
return value;
};
}

export function throwIfIsntContained<T>(
requiredValues: Set<string>,
message: string | ((requiredValue: string) => string),
valueExtractor: (value: T) => string
) {
const toError = typeof message === 'function' ? message : constant(message);
return (values: T[]) => {
const availableValues = new Set(values.map(valueExtractor));
for (const value of requiredValues.values()) {
if (!availableValues.has(value)) {
throw new Error(toError(value));
}
}
return values;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,11 @@ describe('action_connector_form', () => {
editFlyoutVisible: false,
setEditFlyoutVisibility: () => {},
actionTypesIndex: {
'my-action-type': { id: 'my-action-type', name: 'my-action-type-name' },
'my-action-type': {
id: 'my-action-type',
name: 'my-action-type-name',
enabled: true,
},
},
reloadConnectors: () => {
return new Promise<void>(() => {});
Expand Down
Loading