Removing unnecessary hit count check from es query alert (#97735)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
ymao1 2021-04-21 11:28:58 -04:00 committed by GitHub
parent e01d5fd255
commit c27245b201
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 76 additions and 41 deletions

View file

@ -216,46 +216,47 @@ export function getAlertType(
const { body: searchResult } = await esClient.search(query);
if (searchResult.hits.hits.length > 0) {
const numMatches = (searchResult.hits.total as estypes.TotalHits).value;
logger.debug(`alert ${ES_QUERY_ID}:${alertId} "${name}" query has ${numMatches} matches`);
logger.debug(
`alert ${ES_QUERY_ID}:${alertId} "${name}" result - ${JSON.stringify(searchResult)}`
);
// apply the alert condition
const conditionMet = compareFn(numMatches, params.threshold);
const numMatches = (searchResult.hits.total as estypes.TotalHits).value;
if (conditionMet) {
const humanFn = i18n.translate(
'xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription',
{
defaultMessage: `Number of matching documents is {thresholdComparator} {threshold}`,
values: {
thresholdComparator: getHumanReadableComparator(params.thresholdComparator),
threshold: params.threshold.join(' and '),
},
}
);
// apply the alert condition
const conditionMet = compareFn(numMatches, params.threshold);
const baseContext: EsQueryAlertActionContext = {
date: new Date().toISOString(),
value: numMatches,
conditions: humanFn,
hits: searchResult.hits.hits,
};
const actionContext = addMessages(options, baseContext, params);
const alertInstance = options.services.alertInstanceFactory(ConditionMetAlertInstanceId);
alertInstance
// store the params we would need to recreate the query that led to this alert instance
.replaceState({ latestTimestamp: timestamp, dateStart, dateEnd })
.scheduleActions(ActionGroupId, actionContext);
// update the timestamp based on the current search results
const firstValidTimefieldSort = getValidTimefieldSort(
searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort
);
if (firstValidTimefieldSort) {
timestamp = firstValidTimefieldSort;
if (conditionMet) {
const humanFn = i18n.translate(
'xpack.stackAlerts.esQuery.alertTypeContextConditionsDescription',
{
defaultMessage: `Number of matching documents is {thresholdComparator} {threshold}`,
values: {
thresholdComparator: getHumanReadableComparator(params.thresholdComparator),
threshold: params.threshold.join(' and '),
},
}
);
const baseContext: EsQueryAlertActionContext = {
date: new Date().toISOString(),
value: numMatches,
conditions: humanFn,
hits: searchResult.hits.hits,
};
const actionContext = addMessages(options, baseContext, params);
const alertInstance = options.services.alertInstanceFactory(ConditionMetAlertInstanceId);
alertInstance
// store the params we would need to recreate the query that led to this alert instance
.replaceState({ latestTimestamp: timestamp, dateStart, dateEnd })
.scheduleActions(ActionGroupId, actionContext);
// update the timestamp based on the current search results
const firstValidTimefieldSort = getValidTimefieldSort(
searchResult.hits.hits.find((hit) => getValidTimefieldSort(hit.sort))?.sort
);
if (firstValidTimefieldSort) {
timestamp = firstValidTimefieldSort;
}
}

View file

@ -53,9 +53,6 @@ export default function alertTests({ getService }: FtrProviderContext) {
// write documents in the future, figure out the end date
const endDateMillis = Date.now() + (ALERT_INTERVALS_TO_WRITE - 1) * ALERT_INTERVAL_MILLIS;
endDate = new Date(endDateMillis).toISOString();
// write documents from now to the future end date in groups
createEsDocumentsInGroups(ES_GROUPS_TO_WRITE);
});
afterEach(async () => {
@ -65,6 +62,9 @@ export default function alertTests({ getService }: FtrProviderContext) {
});
it('runs correctly: threshold on hit count < >', async () => {
// write documents from now to the future end date in groups
createEsDocumentsInGroups(ES_GROUPS_TO_WRITE);
await createAlert({
name: 'never fire',
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
@ -104,6 +104,9 @@ export default function alertTests({ getService }: FtrProviderContext) {
});
it('runs correctly with query: threshold on hit count < >', async () => {
// write documents from now to the future end date in groups
createEsDocumentsInGroups(ES_GROUPS_TO_WRITE);
const rangeQuery = (rangeThreshold: number) => {
return {
query: {
@ -126,8 +129,8 @@ export default function alertTests({ getService }: FtrProviderContext) {
name: 'never fire',
esQuery: JSON.stringify(rangeQuery(ES_GROUPS_TO_WRITE * ALERT_INTERVALS_TO_WRITE + 1)),
size: 100,
thresholdComparator: '>=',
threshold: [0],
thresholdComparator: '<',
threshold: [-1],
});
await createAlert({
@ -154,6 +157,37 @@ export default function alertTests({ getService }: FtrProviderContext) {
}
});
it('runs correctly: no matches', async () => {
await createAlert({
name: 'always fire',
esQuery: `{\n \"query\":{\n \"match_all\" : {}\n }\n}`,
size: 100,
thresholdComparator: '<',
threshold: [1],
});
const docs = await waitForDocs(1);
for (let i = 0; i < docs.length; i++) {
const doc = docs[i];
const { previousTimestamp, hits } = doc._source;
const { name, title, message } = doc._source.params;
expect(name).to.be('always fire');
expect(title).to.be(`alert 'always fire' matched query`);
const messagePattern = /alert 'always fire' is active:\n\n- Value: 0+\n- Conditions Met: Number of matching documents is less than 1 over 15s\n- Timestamp: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/;
expect(message).to.match(messagePattern);
expect(hits).to.be.empty();
// during the first execution, the latestTimestamp value should be empty
// since this alert always fires, the latestTimestamp value should be updated each execution
if (!i) {
expect(previousTimestamp).to.be.empty();
} else {
expect(previousTimestamp).not.to.be.empty();
}
}
});
async function createEsDocumentsInGroups(groups: number) {
await createEsDocuments(
es,