Skip to content

Commit

Permalink
Fixed due to comments, moved getHealth to a plugin level
Browse files Browse the repository at this point in the history
  • Loading branch information
YulNaumenko committed Nov 4, 2020
1 parent 41e338a commit 397a622
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 216 deletions.
1 change: 0 additions & 1 deletion x-pack/plugins/alerts/server/alerts_client.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const createAlertsClientMock = () => {
unmuteInstance: jest.fn(),
listAlertTypes: jest.fn(),
getAlertInstanceSummary: jest.fn(),
getHealth: jest.fn(),
};
return mocked;
};
Expand Down
71 changes: 0 additions & 71 deletions x-pack/plugins/alerts/server/alerts_client/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,77 +420,6 @@ export class AlertsClient {
};
}

public async getHealth(): Promise<AlertsHealth> {
const { saved_objects: data } = await this.unsecuredSavedObjectsClient.find<RawAlert>({
filter: 'alert.attributes.executionStatus.status:error',
fields: ['executionStatus'],
type: 'alert',
});

const healthStatuses = data.reduce(
(prevItem: AlertsHealth, item: SavedObjectsFindResult<RawAlert>) => {
switch (item.attributes.executionStatus.error?.reason) {
case AlertExecutionStatusErrorReasons.Decrypt:
prevItem.decryptionHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
case AlertExecutionStatusErrorReasons.Execute:
prevItem.executionHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
case AlertExecutionStatusErrorReasons.Read:
prevItem.readHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
}
return prevItem;
},
{
decryptionHealth: {
status: HealthStatus.OK,
timestamp: '',
},
executionHealth: {
status: HealthStatus.OK,
timestamp: '',
},
readHealth: {
status: HealthStatus.OK,
timestamp: '',
},
}
);

const { saved_objects: noErrorData } = await this.unsecuredSavedObjectsClient.find<RawAlert>({
filter:
'alert.attributes.executionStatus.status:ok or alert.attributes.executionStatus.status:active or alert.attributes.executionStatus.status:pending',
fields: ['executionStatus'],
type: 'alert',
});
const lastExecutionDate = noErrorData.reduce(
(prev: Date, item) =>
prev > new Date(item.attributes.executionStatus.lastExecutionDate)
? prev
: new Date(item.attributes.executionStatus.lastExecutionDate),
new Date('0001/01/01')
);

Object.entries(healthStatuses).map(([healthType, statusItem]) => {
if (statusItem.status === HealthStatus.OK) {
statusItem.timestamp = lastExecutionDate.toISOString();
}
return [healthType, statusItem];
});

return healthStatuses;
}

public async aggregate({
options: { fields, ...options } = {},
}: { options?: AggregateOptions } = {}): Promise<AggregateResult> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,67 +3,17 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { AlertsClient, ConstructorOptions } from '../alerts_client';
import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks';
import { alertTypeRegistryMock } from '../../alert_type_registry.mock';
import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock';
import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks';
import { actionsAuthorizationMock } from '../../../../actions/server/mocks';
import { AlertsAuthorization } from '../../authorization/alerts_authorization';
import { ActionsAuthorization } from '../../../../actions/server';
import { getBeforeSetup } from './lib';
import { AlertExecutionStatusErrorReasons, HealthStatus } from '../../types';
import { taskManagerMock } from '../../../../task_manager/server/mocks';
import { savedObjectsRepositoryMock } from '../../../../../src/core/server/mocks';
import { AlertExecutionStatusErrorReasons, HealthStatus } from '../types';
import { getHealth } from './get_health';

const taskManager = taskManagerMock.createStart();
const alertTypeRegistry = alertTypeRegistryMock.create();
const unsecuredSavedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createClient();
const authorization = alertsAuthorizationMock.create();
const actionsAuthorization = actionsAuthorizationMock.create();

const kibanaVersion = 'v7.10.0';
const alertsClientParams: jest.Mocked<ConstructorOptions> = {
taskManager,
alertTypeRegistry,
unsecuredSavedObjectsClient,
authorization: (authorization as unknown) as AlertsAuthorization,
actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization,
spaceId: 'default',
namespace: 'default',
getUserName: jest.fn(),
createAPIKey: jest.fn(),
invalidateAPIKey: jest.fn(),
logger: loggingSystemMock.create().get(),
encryptedSavedObjectsClient: encryptedSavedObjects,
getActionsClient: jest.fn(),
getEventLogClient: jest.fn(),
kibanaVersion,
};

beforeEach(() => {
getBeforeSetup(alertsClientParams, taskManager, alertTypeRegistry);
});
const savedObjectsRepository = savedObjectsRepositoryMock.create();

describe('getHealth()', () => {
const listedTypes = new Set([
{
actionGroups: [],
actionVariables: undefined,
defaultActionGroupId: 'default',
id: 'myType',
name: 'myType',
producer: 'myApp',
},
]);
beforeAll(() => {
alertTypeRegistry.list.mockReturnValue(listedTypes);
});

test('return true if some of alerts has a decryption error', async () => {
const lastExecutionDateError = new Date().toISOString();
const lastExecutionDate = new Date().toISOString();
unsecuredSavedObjectsClient.find.mockResolvedValue({
savedObjectsRepository.find.mockResolvedValue({
total: 2,
per_page: 10,
page: 1,
Expand Down Expand Up @@ -126,8 +76,7 @@ describe('getHealth()', () => {
},
],
});
const alertsClient = new AlertsClient(alertsClientParams);
const result = await alertsClient.getHealth();
const result = await getHealth(savedObjectsRepository);
expect(result).toStrictEqual({
executionHealth: {
status: HealthStatus.OK,
Expand All @@ -142,13 +91,13 @@ describe('getHealth()', () => {
timestamp: lastExecutionDateError,
},
});
expect(unsecuredSavedObjectsClient.find).toHaveBeenCalledTimes(2);
expect(savedObjectsRepository.find).toHaveBeenCalledTimes(2);
});

test('return false if no alerts with a decryption error', async () => {
const lastExecutionDateError = new Date().toISOString();
const lastExecutionDate = new Date().toISOString();
unsecuredSavedObjectsClient.find.mockResolvedValue({
savedObjectsRepository.find.mockResolvedValue({
total: 2,
per_page: 10,
page: 1,
Expand Down Expand Up @@ -211,8 +160,7 @@ describe('getHealth()', () => {
},
],
});
const alertsClient = new AlertsClient(alertsClientParams);
const result = await alertsClient.getHealth();
const result = await getHealth(savedObjectsRepository);
expect(result).toStrictEqual({
executionHealth: {
status: HealthStatus.Warning,
Expand Down
78 changes: 78 additions & 0 deletions x-pack/plugins/alerts/server/health/get_health.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* 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 { ISavedObjectsRepository, SavedObjectsFindResult } from 'src/core/server';
import { AlertsHealth, HealthStatus, RawAlert, AlertExecutionStatusErrorReasons } from '../types';

export const getHealth = async (
internalSavedObjectsRepository: ISavedObjectsRepository
): Promise<AlertsHealth> => {
const { saved_objects: data } = await internalSavedObjectsRepository.find<RawAlert>({
filter: 'alert.attributes.executionStatus.status:error',
fields: ['executionStatus'],
type: 'alert',
});

const healthStatuses = data.reduce(
(prevItem: AlertsHealth, item: SavedObjectsFindResult<RawAlert>) => {
switch (item.attributes.executionStatus.error?.reason) {
case AlertExecutionStatusErrorReasons.Decrypt:
prevItem.decryptionHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
case AlertExecutionStatusErrorReasons.Execute:
prevItem.executionHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
case AlertExecutionStatusErrorReasons.Read:
prevItem.readHealth = {
status: HealthStatus.Warning,
timestamp: item.attributes.executionStatus.lastExecutionDate,
};
break;
}
return prevItem;
},
{
decryptionHealth: {
status: HealthStatus.OK,
timestamp: '',
},
executionHealth: {
status: HealthStatus.OK,
timestamp: '',
},
readHealth: {
status: HealthStatus.OK,
timestamp: '',
},
}
);

const { saved_objects: noErrorData } = await internalSavedObjectsRepository.find<RawAlert>({
filter: 'not alert.attributes.executionStatus.status:error',
fields: ['executionStatus'],
type: 'alert',
});
const lastExecutionDate = noErrorData.reduce(
(prev: Date, item) =>
prev > new Date(item.attributes.executionStatus.lastExecutionDate)
? prev
: new Date(item.attributes.executionStatus.lastExecutionDate),
new Date('0001/01/01')
);

for (const [, statusItem] of Object.entries(healthStatuses)) {
if (statusItem.status === HealthStatus.OK) {
statusItem.timestamp = lastExecutionDate.toISOString();
}
}

return healthStatuses;
};
8 changes: 4 additions & 4 deletions x-pack/plugins/alerts/server/health/get_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { taskManagerMock } from '../../../task_manager/server/mocks';
import { healthStatus$ } from '.';
import { getHealthStatusStream } from '.';
import { TaskStatus } from '../../../task_manager/server';
import { HealthStatus } from '../types';

describe('healthStatus$()', () => {
describe('getHealthStatusStream()', () => {
const mockTaskManager = taskManagerMock.createStart();

it('should return an object with the "unavailable" level and proper summary of "Alerting framework is unhealthy"', async () => {
Expand All @@ -35,7 +35,7 @@ describe('healthStatus$()', () => {
};
})
);
healthStatus$(mockTaskManager).subscribe(
getHealthStatusStream(mockTaskManager).subscribe(
(val: { level: Readonly<unknown>; summary: string }) => {
expect(val.level).toBe(false);
}
Expand Down Expand Up @@ -66,7 +66,7 @@ describe('healthStatus$()', () => {
};
})
);
healthStatus$(mockTaskManager).subscribe(
getHealthStatusStream(mockTaskManager).subscribe(
(val: { level: Readonly<unknown>; summary: string }) => {
expect(val.level).toBe(true);
}
Expand Down
54 changes: 34 additions & 20 deletions x-pack/plugins/alerts/server/health/get_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';
import { interval, Observable } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
import { get } from 'lodash';
import { ServiceStatus, ServiceStatusLevels } from '../../../../../src/core/server';
import { TaskManagerStartContract } from '../../../task_manager/server';
import { HEALTH_TASK_ID } from './task';
Expand All @@ -26,33 +26,47 @@ async function getLatestTaskState(taskManager: TaskManagerStartContract) {
return null;
}

export const healthStatus$ = (
const LEVEL_SUMMARY = {
[ServiceStatusLevels.available.toString()]: i18n.translate(
'xpack.alerts.server.healthStatus.available',
{
defaultMessage: 'Alerting framework is available',
}
),
[ServiceStatusLevels.degraded.toString()]: i18n.translate(
'xpack.alerts.server.healthStatus.degraded',
{
defaultMessage: 'Alerting framework is degraded',
}
),
[ServiceStatusLevels.unavailable.toString()]: i18n.translate(
'xpack.alerts.server.healthStatus.unavailable',
{
defaultMessage: 'Alerting framework is unavailable',
}
),
};

export const getHealthStatusStream = (
taskManager: TaskManagerStartContract
): Observable<ServiceStatus<unknown>> => {
return interval(60000 * 5).pipe(
switchMap(async () => {
const doc = await getLatestTaskState(taskManager);
const body = get(doc, 'state');
if (body?.health_status === HealthStatus.OK) {
return {
level: ServiceStatusLevels.available,
summary: 'Alerting framework is available',
};
} else if (body?.health_status === HealthStatus.Warning) {
return {
level: ServiceStatusLevels.degraded,
summary: 'Alerting framework is degraded',
};
} else {
return {
level: ServiceStatusLevels.unavailable,
summary: 'Alerting framework is unavailable',
};
}
const level =
doc?.state?.health_status === HealthStatus.OK
? ServiceStatusLevels.available
: doc?.state?.health_status === HealthStatus.Warning
? ServiceStatusLevels.degraded
: ServiceStatusLevels.unavailable;
return {
level,
summary: LEVEL_SUMMARY[level.toString()],
};
}),
catchError(async (error) => ({
level: ServiceStatusLevels.unavailable,
summary: `Alerting framework is unavailable`,
summary: LEVEL_SUMMARY[ServiceStatusLevels.unavailable.toString()],
meta: { error },
}))
);
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/alerts/server/health/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/

export { healthStatus$ } from './get_state';
export { getHealthStatusStream } from './get_state';
export { scheduleAlertingHealthCheck, initializeAlertingHealth } from './task';
Loading

0 comments on commit 397a622

Please sign in to comment.