Skip to content

Commit

Permalink
[7.x] [Metrics UI] Add framework for recovery messaging to metric thr…
Browse files Browse the repository at this point in the history
…eshold alerts (non-functional) (elastic#65339) (elastic#66678)
  • Loading branch information
Zacqary committed May 15, 2020
1 parent 15d02d2 commit 6100383
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const stateToAlertMessage = {
[AlertStates.ERROR]: i18n.translate('xpack.infra.metrics.alerting.threshold.errorState', {
defaultMessage: 'ERROR',
}),
// TODO: Implement recovered message state
[AlertStates.OK]: i18n.translate('xpack.infra.metrics.alerting.threshold.okState', {
defaultMessage: 'OK [Recovered]',
}),
Expand Down Expand Up @@ -62,6 +61,33 @@ const comparatorToI18n = (comparator: Comparator, threshold: number[], currentVa
}
};

const recoveredComparatorToI18n = (
comparator: Comparator,
threshold: number[],
currentValue: number
) => {
const belowText = i18n.translate('xpack.infra.metrics.alerting.threshold.belowRecovery', {
defaultMessage: 'below',
});
const aboveText = i18n.translate('xpack.infra.metrics.alerting.threshold.aboveRecovery', {
defaultMessage: 'above',
});
switch (comparator) {
case Comparator.BETWEEN:
return currentValue < threshold[0] ? belowText : aboveText;
case Comparator.OUTSIDE_RANGE:
return i18n.translate('xpack.infra.metrics.alerting.threshold.betweenRecovery', {
defaultMessage: 'between',
});
case Comparator.GT:
case Comparator.GT_OR_EQ:
return belowText;
case Comparator.LT:
case Comparator.LT_OR_EQ:
return aboveText;
}
};

const thresholdToI18n = ([a, b]: number[]) => {
if (typeof b === 'undefined') return a;
return i18n.translate('xpack.infra.metrics.alerting.threshold.thresholdRange', {
Expand All @@ -87,6 +113,23 @@ export const buildFiredAlertReason: (alertResult: {
},
});

export const buildRecoveredAlertReason: (alertResult: {
metric: string;
comparator: Comparator;
threshold: number[];
currentValue: number;
}) => string = ({ metric, comparator, threshold, currentValue }) =>
i18n.translate('xpack.infra.metrics.alerting.threshold.recoveredAlertReason', {
defaultMessage:
'{metric} is now {comparator} a threshold of {threshold} (current value is {currentValue})',
values: {
metric,
comparator: recoveredComparatorToI18n(comparator, threshold, currentValue),
threshold: thresholdToI18n(threshold),
currentValue,
},
});

export const buildNoDataAlertReason: (alertResult: {
metric: string;
timeSize: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ interface AlertTestInstance {
state: any;
}

let persistAlertInstances = false; // eslint-disable-line

describe('The metric threshold alert type', () => {
describe('querying the entire infrastructure', () => {
const instanceID = 'test-*';
Expand Down Expand Up @@ -313,6 +315,50 @@ describe('The metric threshold alert type', () => {
expect(getState(instanceID).alertState).toBe(AlertStates.NO_DATA);
});
});

// describe('querying a metric that later recovers', () => {
// const instanceID = 'test-*';
// const execute = (threshold: number[]) =>
// executor({
// services,
// params: {
// criteria: [
// {
// ...baseCriterion,
// comparator: Comparator.GT,
// threshold,
// },
// ],
// },
// });
// beforeAll(() => (persistAlertInstances = true));
// afterAll(() => (persistAlertInstances = false));

// test('sends a recovery alert as soon as the metric recovers', async () => {
// await execute([0.5]);
// expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
// expect(getState(instanceID).alertState).toBe(AlertStates.ALERT);
// await execute([2]);
// expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
// expect(getState(instanceID).alertState).toBe(AlertStates.OK);
// });
// test('does not continue to send a recovery alert if the metric is still OK', async () => {
// await execute([2]);
// expect(mostRecentAction(instanceID)).toBe(undefined);
// expect(getState(instanceID).alertState).toBe(AlertStates.OK);
// await execute([2]);
// expect(mostRecentAction(instanceID)).toBe(undefined);
// expect(getState(instanceID).alertState).toBe(AlertStates.OK);
// });
// test('sends a recovery alert again once the metric alerts and recovers again', async () => {
// await execute([0.5]);
// expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
// expect(getState(instanceID).alertState).toBe(AlertStates.ALERT);
// await execute([2]);
// expect(mostRecentAction(instanceID).id).toBe(FIRED_ACTIONS.id);
// expect(getState(instanceID).alertState).toBe(AlertStates.OK);
// });
// });
});

const createMockStaticConfiguration = (sources: any) => ({
Expand Down Expand Up @@ -397,12 +443,19 @@ services.savedObjectsClient.get.mockImplementation(async (type: string, sourceId

const alertInstances = new Map<string, AlertTestInstance>();
services.alertInstanceFactory.mockImplementation((instanceID: string) => {
const alertInstance: AlertTestInstance = {
const newAlertInstance: AlertTestInstance = {
instance: alertsMock.createAlertInstanceFactory(),
actionQueue: [],
state: {},
};
const alertInstance: AlertTestInstance = persistAlertInstances
? alertInstances.get(instanceID) || newAlertInstance
: newAlertInstance;
alertInstances.set(instanceID, alertInstance);

alertInstance.instance.getState.mockImplementation(() => {
return alertInstance.state;
});
alertInstance.instance.replaceState.mockImplementation((newState: any) => {
alertInstance.state = newState;
return alertInstance.instance;
Expand Down

0 comments on commit 6100383

Please sign in to comment.