Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3d5ff27
Add bulk_untrack API
Zacqary Sep 28, 2023
e5ac9ec
Add bulk action for untracking alerts
Zacqary Sep 28, 2023
f5b6a81
Clear task state after bulk untrack
Zacqary Sep 28, 2023
177f954
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Sep 28, 2023
f793562
Fix jest
Zacqary Sep 29, 2023
1d6ad2e
Fix typecheck
Zacqary Sep 29, 2023
b4c691c
Fix alert table test
Zacqary Sep 29, 2023
b4d6e09
Add permissions check for untracked action
Zacqary Sep 29, 2023
652b96a
Merge remote-tracking branch 'origin/bulkaction-164059' into bulkacti…
Zacqary Sep 29, 2023
596f24a
Fix typecheck
Zacqary Sep 29, 2023
bb39962
Add test for bulkUntrackAlerts method
Zacqary Sep 29, 2023
1664888
Remove duplicate bulk untrack hook
Zacqary Sep 29, 2023
834c4cb
Move setAlertsToUntracked test to own file
Zacqary Sep 29, 2023
8391391
Add call snapshots to tests
Zacqary Sep 29, 2023
14a4fa2
Fix i18n
Zacqary Sep 29, 2023
32c5207
Check for empty ruleIds and uuids in setAlertsToUntracked
Zacqary Oct 2, 2023
f65925d
Add untrack call to delete rule api
Zacqary Oct 2, 2023
b151266
Fix jest
Zacqary Oct 2, 2023
ee620f7
Merge remote-tracking branch 'upstream/main' into bulkaction-164059
Zacqary Oct 2, 2023
3050920
Remove untrack alert action from recovered alerts
Zacqary Oct 2, 2023
90760a5
Throw error when only recovered alerts are bulk untracked
Zacqary Oct 2, 2023
eb12625
Filter untracked alerts out of active bucket on alert summary
Zacqary Oct 2, 2023
c22fbdb
Switch bulk untrack permission to cases update
Zacqary Oct 2, 2023
baf4aa4
Remove client-side permissions checks
Zacqary Oct 2, 2023
732c7ae
Add auth check for untracking alerts
Zacqary Oct 2, 2023
a804a2e
Add comment
Zacqary Oct 2, 2023
3c9aef8
Merge remote-tracking branch 'upstream/main' into bulkaction-164059
Zacqary Oct 2, 2023
8f95088
Commit suggestions
Zacqary Oct 2, 2023
00e9aad
Revert "Filter untracked alerts out of active bucket on alert summary"
Zacqary Oct 2, 2023
b2843ca
Update tests
Zacqary Oct 2, 2023
5d1f040
Fix jest
Zacqary Oct 2, 2023
bf887b8
Merge remote-tracking branch 'upstream/main' into bulkaction-164059
Zacqary Oct 2, 2023
ed10945
Add o11y permissions check to bulk actions
Zacqary Oct 2, 2023
c506384
API integration test
JiaweiWu Oct 3, 2023
7b2b491
Fix jest
Zacqary Oct 3, 2023
d7a65f5
Merge branch 'bulkaction-164059' of https://github.com/Zacqary/kibana…
Zacqary Oct 3, 2023
c15286b
Merge remote-tracking branch 'upstream/main' into bulkaction-164059
Zacqary Oct 3, 2023
80c19e8
Add tests for removing expected uuids from task state
Zacqary Oct 3, 2023
3e6c478
Fix snapshot
Zacqary Oct 3, 2023
21f1dbf
delete alert even with conflict
XavierM Oct 3, 2023
ac63b3e
Merge branch 'bulkaction-164059' of github.com:Zacqary/kibana into bu…
XavierM Oct 3, 2023
740a8b6
fix FTR
XavierM Oct 3, 2023
c0728bb
Fix untracking rules from bulk delete API
Zacqary Oct 3, 2023
36780c3
Update permissions
Zacqary Oct 3, 2023
31d27b2
Update permissions
Zacqary Oct 3, 2023
50cb9da
Merge branch 'bulkaction-164059' of https://github.com/Zacqary/kibana…
Zacqary Oct 3, 2023
171b0a6
Add error handling for bulk updater
Zacqary Oct 3, 2023
cd98921
Add bulkUpdateState test
Zacqary Oct 3, 2023
2c6d1f2
Merge branch 'main' into bulkaction-164059
XavierM Oct 3, 2023
be2de2b
Add ensureAuthorized tests for setAlertsToUntracked
Zacqary Oct 3, 2023
e62a1f1
Merge branch 'bulkaction-164059' of https://github.com/Zacqary/kibana…
Zacqary Oct 3, 2023
2a7b1a9
rules/_bulk_untrack -> alerts/_bulk_untrack
Zacqary Oct 3, 2023
25114b3
Add conflicts: proceed to failing test
Zacqary Oct 3, 2023
b85edd2
add conflicts: "proceed"
XavierM Oct 3, 2023
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
@@ -0,0 +1,12 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export { bulkUntrackBodySchema } from './schemas/latest';
export { bulkUntrackBodySchema as bulkUntrackBodySchemaV1 } from './schemas/v1';

export type { BulkUntrackRequestBody } from './types/latest';
export type { BulkUntrackRequestBody as BulkUntrackRequestBodyV1 } from './types/v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { bulkUntrackBodySchema } from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { schema } from '@kbn/config-schema';

export const bulkUntrackBodySchema = schema.object({
indices: schema.arrayOf(schema.string()),
alert_uuids: schema.arrayOf(schema.string()),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export type { BulkUntrackRequestBody } from './v1';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { TypeOf } from '@kbn/config-schema';
import { bulkUntrackBodySchemaV1 } from '..';

export type BulkUntrackRequestBody = TypeOf<typeof bulkUntrackBodySchemaV1>;
47 changes: 0 additions & 47 deletions x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2331,53 +2331,6 @@ describe('Alerts Client', () => {
expect(recoveredAlert.hit).toBeUndefined();
});
});

describe('setAlertStatusToUntracked()', () => {
test('should call updateByQuery on provided ruleIds', async () => {
const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>(
alertsClientParams
);

const opts = {
maxAlerts,
ruleLabel: `test: rule-name`,
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
activeAlertsFromState: {},
recoveredAlertsFromState: {},
};
await alertsClient.initializeExecution(opts);

await alertsClient.setAlertStatusToUntracked(['test-index'], ['test-rule']);

expect(clusterClient.updateByQuery).toHaveBeenCalledTimes(1);
});

test('should retry updateByQuery on failure', async () => {
clusterClient.updateByQuery.mockResponseOnce({
total: 10,
updated: 8,
});
const alertsClient = new AlertsClient<{}, {}, {}, 'default', 'recovered'>(
alertsClientParams
);

const opts = {
maxAlerts,
ruleLabel: `test: rule-name`,
flappingSettings: DEFAULT_FLAPPING_SETTINGS,
activeAlertsFromState: {},
recoveredAlertsFromState: {},
};
await alertsClient.initializeExecution(opts);

await alertsClient.setAlertStatusToUntracked(['test-index'], ['test-rule']);

expect(clusterClient.updateByQuery).toHaveBeenCalledTimes(2);
expect(logger.warn).toHaveBeenCalledWith(
'Attempt 1: Failed to untrack 2 of 10; indices test-index, ruleIds test-rule'
);
});
});
});
}
});
62 changes: 1 addition & 61 deletions x-pack/plugins/alerting/server/alerts_client/alerts_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,7 @@
*/

import { ElasticsearchClient } from '@kbn/core/server';
import {
ALERT_INSTANCE_ID,
ALERT_RULE_UUID,
ALERT_STATUS,
ALERT_STATUS_UNTRACKED,
ALERT_STATUS_ACTIVE,
ALERT_UUID,
} from '@kbn/rule-data-utils';
import { ALERT_INSTANCE_ID, ALERT_RULE_UUID, ALERT_STATUS, ALERT_UUID } from '@kbn/rule-data-utils';
import { chunk, flatMap, get, isEmpty, keys } from 'lodash';
import { SearchRequest } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import type { Alert } from '@kbn/alerts-as-data-utils';
Expand Down Expand Up @@ -206,51 +199,6 @@ export class AlertsClient<
return { hits, total };
}

public async setAlertStatusToUntracked(indices: string[], ruleIds: string[]) {
const esClient = await this.options.elasticsearchClientPromise;
const terms: Array<{ term: Record<string, { value: string }> }> = ruleIds.map((ruleId) => ({
term: {
[ALERT_RULE_UUID]: { value: ruleId },
},
}));
terms.push({
term: {
[ALERT_STATUS]: { value: ALERT_STATUS_ACTIVE },
},
});

try {
// Retry this updateByQuery up to 3 times to make sure the number of documents
// updated equals the number of documents matched
for (let retryCount = 0; retryCount < 3; retryCount++) {
const response = await esClient.updateByQuery({
index: indices,
allow_no_indices: true,
body: {
conflicts: 'proceed',
script: {
source: UNTRACK_UPDATE_PAINLESS_SCRIPT,
lang: 'painless',
},
query: {
bool: {
must: terms,
},
},
},
});
if (response.total === response.updated) break;
this.options.logger.warn(
`Attempt ${retryCount + 1}: Failed to untrack ${
(response.total ?? 0) - (response.updated ?? 0)
} of ${response.total}; indices ${indices}, ruleIds ${ruleIds}`
);
}
} catch (err) {
this.options.logger.error(`Error marking ${ruleIds} as untracked - ${err.message}`);
}
}

public report(
alert: ReportedAlert<
AlertData,
Expand Down Expand Up @@ -621,11 +569,3 @@ export class AlertsClient<
return this._isUsingDataStreams;
}
}

const UNTRACK_UPDATE_PAINLESS_SCRIPT = `
// Certain rule types don't flatten their AAD values, apply the ALERT_STATUS key to them directly
if (!ctx._source.containsKey('${ALERT_STATUS}') || ctx._source['${ALERT_STATUS}'].empty) {
ctx._source.${ALERT_STATUS} = '${ALERT_STATUS_UNTRACKED}';
} else {
ctx._source['${ALERT_STATUS}'] = '${ALERT_STATUS_UNTRACKED}'
}`;
1 change: 0 additions & 1 deletion x-pack/plugins/alerting/server/alerts_client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ export interface IAlertsClient<
alertsToReturn: Record<string, RawAlertInstance>;
recoveredAlertsToReturn: Record<string, RawAlertInstance>;
};
setAlertStatusToUntracked(indices: string[], ruleIds: string[]): Promise<void>;
factory(): PublicAlertFactory<
State,
Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const creatAlertsServiceMock = () => {
isInitialized: jest.fn(),
getContextInitializationPromise: jest.fn(),
createAlertsClient: jest.fn(),
setAlertsToUntracked: jest.fn(),
};
});
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
import type { LegacyAlertsClientParams, AlertRuleData } from '../alerts_client';
import { AlertsClient } from '../alerts_client';
import { IAlertsClient } from '../alerts_client/types';
import { setAlertsToUntracked, SetAlertsToUntrackedOpts } from './lib/set_alerts_to_untracked';

export const TOTAL_FIELDS_LIMIT = 2500;
const LEGACY_ALERT_CONTEXT = 'legacy-alert';
Expand Down Expand Up @@ -458,4 +459,12 @@ export class AlertsService implements IAlertsService {
});
}
}

public async setAlertsToUntracked(opts: SetAlertsToUntrackedOpts) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure that this method actually belongs in AlertsService rather than AlertsClient, because it interacts with and updates alert data. However, we want to make it available without being connected to a Rule (which is a prerequisite for creating an AlertsClient), and it also doesn't actually need a corresponding Rule to operate.

Not sure if there's a specific design goal re: Service vs. Client that I'm violating here with this, but it was the most expedient way I could think of to get this to work.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably fine for now. The initial intent was for the AlertService to handle all of the framework level resource installation that's required to write alerts for a rule and then AlertsClient to be the way for the alerting task runner to interact with those alerts. As you point out, there are use cases for users interacting directly with alerts. In the future, we may want to consolidate all of those interactions into a separate client but this is fine for now.

return setAlertsToUntracked({
logger: this.options.logger,
esClient: await this.options.elasticsearchClientPromise,
...opts,
});
}
}
Loading