mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.12`: - [[RAM] Fix rule log aggregations when filtering >1k logs (#172228)](https://github.com/elastic/kibana/pull/172228) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Zacqary Adam Xeper","email":"Zacqary@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-01-09T23:17:13Z","message":"[RAM] Fix rule log aggregations when filtering >1k logs (#172228)\n\n## Summary\r\n\r\nFixes #171409 \r\n\r\nThis fixes weird behavior when attempting to filter more than 1000 rule\r\nexecution logs.\r\n\r\nThe execution log aggregator had two problems:\r\n\r\n- It had a max bucket limit of 1000, while the KPI agg had a max bucket\r\nlimit of 10000, leading to inconsistent results\r\n- For pagination, it reported the total number of logs by using a\r\ncardinality aggregator, which wasn't subject to any bucket limit at all\r\n\r\nThe endpoint responds with a `total` (the cardinality) and an array of\r\n`data` (a paginated slice of the aggregated logs). The way the data\r\ntable UI works is that it takes the `total` value, caps it at 1000, and\r\nthen generates that many blank rows to fill with whatever's in the\r\n`data` array.\r\n\r\nSo as seen in the issue, we could easily run into a situation where:\r\n\r\n1. User enters a filter query that last appeared more than 1000 logs ago\r\n2. The cardinality agg accurately reports the number of logs that should\r\nbe fetched, and generates enough blank rows for these logs\r\n3. The actual execution log agg hits the 1000 bucket limit, and returns\r\nan empty `data` array\r\n\r\nThis PR fixes this by using a bucket sum aggregation to determine the\r\ndata table's `total` instead of a cardinality aggregation.\r\n\r\nIt also ups the bucket limit from 1000 to 10000, to match the bucket\r\nlimit from the summary KPI endpoint. This prevents a situation where:\r\n\r\n1. User enters a filter query that last appeared in <1000 logs, but more\r\nthan 1000 logs ago\r\n2. Summary KPI widget, with a bucket limit of 10000, accurately displays\r\na count of the <1000 logs this query matches\r\n3. The data table is empty because the execution log bucket limit tops\r\nout at 1000\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>","sha":"9d32f889683cb6bc099d561d94e02af2feaf0d10","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:fix","Team:ResponseOps","Feature:Alerting/RulesManagement","v8.12.0","v8.13.0"],"title":"[RAM] Fix rule log aggregations when filtering >1k logs","number":172228,"url":"https://github.com/elastic/kibana/pull/172228","mergeCommit":{"message":"[RAM] Fix rule log aggregations when filtering >1k logs (#172228)\n\n## Summary\r\n\r\nFixes #171409 \r\n\r\nThis fixes weird behavior when attempting to filter more than 1000 rule\r\nexecution logs.\r\n\r\nThe execution log aggregator had two problems:\r\n\r\n- It had a max bucket limit of 1000, while the KPI agg had a max bucket\r\nlimit of 10000, leading to inconsistent results\r\n- For pagination, it reported the total number of logs by using a\r\ncardinality aggregator, which wasn't subject to any bucket limit at all\r\n\r\nThe endpoint responds with a `total` (the cardinality) and an array of\r\n`data` (a paginated slice of the aggregated logs). The way the data\r\ntable UI works is that it takes the `total` value, caps it at 1000, and\r\nthen generates that many blank rows to fill with whatever's in the\r\n`data` array.\r\n\r\nSo as seen in the issue, we could easily run into a situation where:\r\n\r\n1. User enters a filter query that last appeared more than 1000 logs ago\r\n2. The cardinality agg accurately reports the number of logs that should\r\nbe fetched, and generates enough blank rows for these logs\r\n3. The actual execution log agg hits the 1000 bucket limit, and returns\r\nan empty `data` array\r\n\r\nThis PR fixes this by using a bucket sum aggregation to determine the\r\ndata table's `total` instead of a cardinality aggregation.\r\n\r\nIt also ups the bucket limit from 1000 to 10000, to match the bucket\r\nlimit from the summary KPI endpoint. This prevents a situation where:\r\n\r\n1. User enters a filter query that last appeared in <1000 logs, but more\r\nthan 1000 logs ago\r\n2. Summary KPI widget, with a bucket limit of 10000, accurately displays\r\na count of the <1000 logs this query matches\r\n3. The data table is empty because the execution log bucket limit tops\r\nout at 1000\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>","sha":"9d32f889683cb6bc099d561d94e02af2feaf0d10"}},"sourceBranch":"main","suggestedTargetBranches":["8.12"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","branchLabelMappingKey":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/172228","number":172228,"mergeCommit":{"message":"[RAM] Fix rule log aggregations when filtering >1k logs (#172228)\n\n## Summary\r\n\r\nFixes #171409 \r\n\r\nThis fixes weird behavior when attempting to filter more than 1000 rule\r\nexecution logs.\r\n\r\nThe execution log aggregator had two problems:\r\n\r\n- It had a max bucket limit of 1000, while the KPI agg had a max bucket\r\nlimit of 10000, leading to inconsistent results\r\n- For pagination, it reported the total number of logs by using a\r\ncardinality aggregator, which wasn't subject to any bucket limit at all\r\n\r\nThe endpoint responds with a `total` (the cardinality) and an array of\r\n`data` (a paginated slice of the aggregated logs). The way the data\r\ntable UI works is that it takes the `total` value, caps it at 1000, and\r\nthen generates that many blank rows to fill with whatever's in the\r\n`data` array.\r\n\r\nSo as seen in the issue, we could easily run into a situation where:\r\n\r\n1. User enters a filter query that last appeared more than 1000 logs ago\r\n2. The cardinality agg accurately reports the number of logs that should\r\nbe fetched, and generates enough blank rows for these logs\r\n3. The actual execution log agg hits the 1000 bucket limit, and returns\r\nan empty `data` array\r\n\r\nThis PR fixes this by using a bucket sum aggregation to determine the\r\ndata table's `total` instead of a cardinality aggregation.\r\n\r\nIt also ups the bucket limit from 1000 to 10000, to match the bucket\r\nlimit from the summary KPI endpoint. This prevents a situation where:\r\n\r\n1. User enters a filter query that last appeared in <1000 logs, but more\r\nthan 1000 logs ago\r\n2. Summary KPI widget, with a bucket limit of 10000, accurately displays\r\na count of the <1000 logs this query matches\r\n3. The data table is empty because the execution log bucket limit tops\r\nout at 1000\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Xavier Mouligneau <xavier.mouligneau@elastic.co>","sha":"9d32f889683cb6bc099d561d94e02af2feaf0d10"}}]}] BACKPORT--> Co-authored-by: Zacqary Adam Xeper <Zacqary@users.noreply.github.com>
This commit is contained in:
parent
47b78f0d18
commit
cec6529170
12 changed files with 813 additions and 132 deletions
|
@ -51,6 +51,7 @@ import { getOAuthClientCredentialsAccessToken } from '../lib/get_oauth_client_cr
|
|||
import { OAuthParams } from '../routes/get_oauth_access_token';
|
||||
import { eventLogClientMock } from '@kbn/event-log-plugin/server/event_log_client.mock';
|
||||
import { GetGlobalExecutionKPIParams, GetGlobalExecutionLogParams } from '../../common';
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
|
||||
jest.mock('@kbn/core-saved-objects-utils-server', () => {
|
||||
const actual = jest.requireActual('@kbn/core-saved-objects-utils-server');
|
||||
|
@ -3419,6 +3420,10 @@ describe('getGlobalExecutionLogWithAuth()', () => {
|
|||
executionUuidCardinality: { doc_count: 5, executionUuidCardinality: { value: 5 } },
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 5, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
describe('authorization', () => {
|
||||
test('ensures user is authorised to access logs', async () => {
|
||||
|
@ -3474,6 +3479,10 @@ describe('getGlobalExecutionKpiWithAuth()', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 5, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
describe('authorization', () => {
|
||||
test('ensures user is authorised to access kpi', async () => {
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { fromKueryExpression } from '@kbn/es-query';
|
||||
import {
|
||||
getExecutionLogAggregation,
|
||||
|
@ -485,7 +486,15 @@ describe('getExecutionLogAggregation', () => {
|
|||
|
||||
describe('formatExecutionLogResult', () => {
|
||||
test('should return empty results if aggregations are undefined', () => {
|
||||
expect(formatExecutionLogResult({ aggregations: undefined })).toEqual({
|
||||
expect(
|
||||
formatExecutionLogResult({
|
||||
aggregations: undefined,
|
||||
hits: {
|
||||
total: { value: 0, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
})
|
||||
).toEqual({
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
@ -494,6 +503,10 @@ describe('formatExecutionLogResult', () => {
|
|||
expect(
|
||||
formatExecutionLogResult({
|
||||
aggregations: { executionLogAgg: undefined as unknown as ExecutionUuidAggResult },
|
||||
hits: {
|
||||
total: { value: 5, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
})
|
||||
).toEqual({
|
||||
total: 0,
|
||||
|
@ -554,6 +567,10 @@ describe('formatExecutionLogResult', () => {
|
|||
executionUuidCardinality: { doc_count: 1, executionUuidCardinality: { value: 1 } },
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 5, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
data: [
|
||||
|
@ -675,6 +692,10 @@ describe('formatExecutionLogResult', () => {
|
|||
executionUuidCardinality: { doc_count: 2, executionUuidCardinality: { value: 2 } },
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 10, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
data: [
|
||||
|
@ -918,6 +939,10 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
expect(
|
||||
formatExecutionKPIResult({
|
||||
aggregations: undefined,
|
||||
hits: {
|
||||
total: { value: 0, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
})
|
||||
).toEqual({ failure: 0, success: 0, unknown: 0, warning: 0 });
|
||||
});
|
||||
|
@ -951,6 +976,10 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 21, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
|
||||
expect(formatExecutionKPIResult(results)).toEqual({
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { estypes } from '@elastic/elasticsearch';
|
||||
import { fromKueryExpression } from '@kbn/es-query';
|
||||
import {
|
||||
getNumExecutions,
|
||||
|
@ -73,7 +74,7 @@ describe('getNumExecutions', () => {
|
|||
new Date('2020-12-02T00:00:00.000Z'),
|
||||
'1s'
|
||||
)
|
||||
).toEqual(1000);
|
||||
).toEqual(10000);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -146,38 +147,48 @@ describe('getExecutionLogAggregation', () => {
|
|||
},
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
cardinality: { field: 'kibana.alert.rule.execution.uuid' },
|
||||
},
|
||||
sum_bucket: {
|
||||
buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count',
|
||||
},
|
||||
filter: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
executionUuidCardinalityBuckets: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 10000,
|
||||
order: [{ 'ruleExecution>executeStartTime': 'desc' }],
|
||||
},
|
||||
aggs: {
|
||||
ruleExecution: {
|
||||
filter: {
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
aggs: { executeStartTime: { min: { field: 'event.start' } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
executionUuid: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 1000,
|
||||
size: 10000,
|
||||
order: [
|
||||
{ 'ruleExecution>executeStartTime': 'asc' },
|
||||
{ 'ruleExecution>executionDuration': 'desc' },
|
||||
|
@ -330,50 +341,60 @@ describe('getExecutionLogAggregation', () => {
|
|||
},
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
cardinality: { field: 'kibana.alert.rule.execution.uuid' },
|
||||
},
|
||||
sum_bucket: {
|
||||
buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count',
|
||||
},
|
||||
filter: {
|
||||
bool: {
|
||||
},
|
||||
executionUuidCardinalityBuckets: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 10000,
|
||||
order: [{ 'ruleExecution>executeStartTime': 'desc' }],
|
||||
},
|
||||
aggs: {
|
||||
ruleExecution: {
|
||||
filter: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
filter: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match: {
|
||||
test: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
test: 'test',
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
aggs: { executeStartTime: { min: { field: 'event.start' } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
executionUuid: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 1000,
|
||||
size: 10000,
|
||||
order: [
|
||||
{ 'ruleExecution>executeStartTime': 'asc' },
|
||||
{ 'ruleExecution>executionDuration': 'desc' },
|
||||
|
@ -538,50 +559,60 @@ describe('getExecutionLogAggregation', () => {
|
|||
},
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
cardinality: { field: 'kibana.alert.rule.execution.uuid' },
|
||||
},
|
||||
sum_bucket: {
|
||||
buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count',
|
||||
},
|
||||
filter: {
|
||||
bool: {
|
||||
},
|
||||
executionUuidCardinalityBuckets: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 10000,
|
||||
order: [{ 'ruleExecution>executeStartTime': 'desc' }],
|
||||
},
|
||||
aggs: {
|
||||
ruleExecution: {
|
||||
filter: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
filter: {
|
||||
bool: {
|
||||
minimum_should_match: 1,
|
||||
should: [
|
||||
{
|
||||
match: {
|
||||
test: 'test',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
test: 'test',
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
must: [
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
match: {
|
||||
'event.action': 'execute',
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'event.provider': 'alerting',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
aggs: { executeStartTime: { min: { field: 'event.start' } } },
|
||||
},
|
||||
},
|
||||
},
|
||||
executionUuid: {
|
||||
terms: {
|
||||
field: 'kibana.alert.rule.execution.uuid',
|
||||
size: 1000,
|
||||
size: 10000,
|
||||
order: [
|
||||
{ 'ruleExecution>executeStartTime': 'asc' },
|
||||
{ 'ruleExecution>executionDuration': 'desc' },
|
||||
|
@ -726,7 +757,12 @@ describe('getExecutionLogAggregation', () => {
|
|||
|
||||
describe('formatExecutionLogResult', () => {
|
||||
test('should return empty results if aggregations are undefined', () => {
|
||||
expect(formatExecutionLogResult({ aggregations: undefined })).toEqual({
|
||||
expect(
|
||||
formatExecutionLogResult({
|
||||
aggregations: undefined,
|
||||
hits: { total: { value: 0, relation: 'eq' }, hits: [] },
|
||||
})
|
||||
).toEqual({
|
||||
total: 0,
|
||||
data: [],
|
||||
});
|
||||
|
@ -735,6 +771,7 @@ describe('formatExecutionLogResult', () => {
|
|||
expect(
|
||||
formatExecutionLogResult({
|
||||
aggregations: { excludeExecuteStart: undefined as unknown as ExecutionUuidAggResult },
|
||||
hits: { total: { value: 0, relation: 'eq' }, hits: [] },
|
||||
})
|
||||
).toEqual({
|
||||
total: 0,
|
||||
|
@ -932,12 +969,14 @@ describe('formatExecutionLogResult', () => {
|
|||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: {
|
||||
value: 374,
|
||||
},
|
||||
value: 374,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
total: 374,
|
||||
|
@ -1188,12 +1227,14 @@ describe('formatExecutionLogResult', () => {
|
|||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: {
|
||||
value: 374,
|
||||
},
|
||||
value: 374,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
total: 374,
|
||||
|
@ -1436,12 +1477,14 @@ describe('formatExecutionLogResult', () => {
|
|||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: {
|
||||
value: 374,
|
||||
},
|
||||
value: 374,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
total: 374,
|
||||
|
@ -1689,12 +1732,14 @@ describe('formatExecutionLogResult', () => {
|
|||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: {
|
||||
value: 417,
|
||||
},
|
||||
value: 417,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(formatExecutionLogResult(results)).toEqual({
|
||||
total: 417,
|
||||
|
@ -1750,6 +1795,211 @@ describe('formatExecutionLogResult', () => {
|
|||
],
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw an error when document is above 10,000', () => {
|
||||
const results = {
|
||||
aggregations: {
|
||||
excludeExecuteStart: {
|
||||
meta: {},
|
||||
doc_count: 875,
|
||||
executionUuid: {
|
||||
meta: {},
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'ecf7ac4c-1c15-4a1d-818a-cacbf57f6158',
|
||||
doc_count: 32,
|
||||
timeoutMessage: {
|
||||
meta: {},
|
||||
doc_count: 0,
|
||||
},
|
||||
ruleExecution: {
|
||||
meta: {},
|
||||
doc_count: 1,
|
||||
numTriggeredActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numGeneratedActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numActiveAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numNewAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numRecoveredAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
outcomeMessageAndMaintenanceWindow: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: 1.0,
|
||||
hits: [
|
||||
{
|
||||
_index: '.kibana-event-log-8.2.0-000001',
|
||||
_id: '7xKcb38BcntAq5ycFwiu',
|
||||
_score: 1.0,
|
||||
_source: {
|
||||
rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' },
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
kibana: {
|
||||
version: '8.2.0',
|
||||
alerting: {
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
message:
|
||||
"rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
scheduleDelay: {
|
||||
value: 3.126e9,
|
||||
},
|
||||
totalSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
esSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
executionDuration: {
|
||||
value: 1.374e9,
|
||||
},
|
||||
executeStartTime: {
|
||||
value: 1.646844973039e12,
|
||||
value_as_string: '2022-03-09T16:56:13.039Z',
|
||||
},
|
||||
},
|
||||
actionExecution: {
|
||||
meta: {},
|
||||
doc_count: 5,
|
||||
actionOutcomes: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'failure',
|
||||
doc_count: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '61bb867b-661a-471f-bf92-23471afa10b3',
|
||||
doc_count: 32,
|
||||
timeoutMessage: {
|
||||
meta: {},
|
||||
doc_count: 0,
|
||||
},
|
||||
ruleExecution: {
|
||||
meta: {},
|
||||
doc_count: 1,
|
||||
numTriggeredActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numGeneratedActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numActiveAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numNewAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numRecoveredAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
outcomeMessageAndMaintenanceWindow: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: 1.0,
|
||||
hits: [
|
||||
{
|
||||
_index: '.kibana-event-log-8.2.0-000001',
|
||||
_id: 'zRKbb38BcntAq5ycOwgk',
|
||||
_score: 1.0,
|
||||
_source: {
|
||||
rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' },
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
kibana: {
|
||||
version: '8.2.0',
|
||||
alert: {
|
||||
maintenance_window_ids: ['254699b0-dfb2-11ed-bb3d-c91b918d0260'],
|
||||
},
|
||||
alerting: {
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
message:
|
||||
"rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
scheduleDelay: {
|
||||
value: 3.133e9,
|
||||
},
|
||||
totalSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
esSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
executionDuration: {
|
||||
value: 4.18e8,
|
||||
},
|
||||
executeStartTime: {
|
||||
value: 1.646844917518e12,
|
||||
value_as_string: '2022-03-09T16:55:17.518Z',
|
||||
},
|
||||
},
|
||||
actionExecution: {
|
||||
meta: {},
|
||||
doc_count: 5,
|
||||
actionOutcomes: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'success',
|
||||
doc_count: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
value: 417,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 10000, relation: 'gte' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(() => formatExecutionLogResult(results)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Results are limited to 10,000 documents, refine your search to see others."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExecutionKPIAggregation', () => {
|
||||
|
@ -2254,6 +2504,10 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
expect(
|
||||
formatExecutionKPIResult({
|
||||
aggregations: undefined,
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
})
|
||||
).toEqual({
|
||||
activeAlerts: 0,
|
||||
|
@ -2375,6 +2629,10 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
|
||||
expect(formatExecutionKPIResult(results)).toEqual({
|
||||
|
@ -2497,6 +2755,10 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
|
||||
expect(formatExecutionKPIResult(results)).toEqual({
|
||||
|
@ -2511,4 +2773,209 @@ describe('formatExecutionKPIAggBuckets', () => {
|
|||
triggeredActions: 10,
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw an error when document is above 10,000', () => {
|
||||
const results = {
|
||||
aggregations: {
|
||||
excludeExecuteStart: {
|
||||
meta: {},
|
||||
doc_count: 875,
|
||||
executionUuid: {
|
||||
meta: {},
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'ecf7ac4c-1c15-4a1d-818a-cacbf57f6158',
|
||||
doc_count: 32,
|
||||
timeoutMessage: {
|
||||
meta: {},
|
||||
doc_count: 0,
|
||||
},
|
||||
ruleExecution: {
|
||||
meta: {},
|
||||
doc_count: 1,
|
||||
numTriggeredActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numGeneratedActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numActiveAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numNewAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numRecoveredAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
outcomeMessageAndMaintenanceWindow: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: 1.0,
|
||||
hits: [
|
||||
{
|
||||
_index: '.kibana-event-log-8.2.0-000001',
|
||||
_id: '7xKcb38BcntAq5ycFwiu',
|
||||
_score: 1.0,
|
||||
_source: {
|
||||
rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' },
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
kibana: {
|
||||
version: '8.2.0',
|
||||
alerting: {
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
message:
|
||||
"rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
scheduleDelay: {
|
||||
value: 3.126e9,
|
||||
},
|
||||
totalSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
esSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
executionDuration: {
|
||||
value: 1.374e9,
|
||||
},
|
||||
executeStartTime: {
|
||||
value: 1.646844973039e12,
|
||||
value_as_string: '2022-03-09T16:56:13.039Z',
|
||||
},
|
||||
},
|
||||
actionExecution: {
|
||||
meta: {},
|
||||
doc_count: 5,
|
||||
actionOutcomes: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'failure',
|
||||
doc_count: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
key: '61bb867b-661a-471f-bf92-23471afa10b3',
|
||||
doc_count: 32,
|
||||
timeoutMessage: {
|
||||
meta: {},
|
||||
doc_count: 0,
|
||||
},
|
||||
ruleExecution: {
|
||||
meta: {},
|
||||
doc_count: 1,
|
||||
numTriggeredActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numGeneratedActions: {
|
||||
value: 5.0,
|
||||
},
|
||||
numActiveAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numNewAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
numRecoveredAlerts: {
|
||||
value: 5.0,
|
||||
},
|
||||
outcomeMessageAndMaintenanceWindow: {
|
||||
hits: {
|
||||
total: {
|
||||
value: 1,
|
||||
relation: 'eq',
|
||||
},
|
||||
max_score: 1.0,
|
||||
hits: [
|
||||
{
|
||||
_index: '.kibana-event-log-8.2.0-000001',
|
||||
_id: 'zRKbb38BcntAq5ycOwgk',
|
||||
_score: 1.0,
|
||||
_source: {
|
||||
rule: { id: 'a348a740-9e2c-11ec-bd64-774ed95c43ef', name: 'rule_name' },
|
||||
event: {
|
||||
outcome: 'success',
|
||||
},
|
||||
kibana: {
|
||||
version: '8.2.0',
|
||||
alert: {
|
||||
maintenance_window_ids: ['254699b0-dfb2-11ed-bb3d-c91b918d0260'],
|
||||
},
|
||||
alerting: {
|
||||
outcome: 'success',
|
||||
},
|
||||
},
|
||||
message:
|
||||
"rule executed: example.always-firing:a348a740-9e2c-11ec-bd64-774ed95c43ef: 'test rule'",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
scheduleDelay: {
|
||||
value: 3.133e9,
|
||||
},
|
||||
totalSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
esSearchDuration: {
|
||||
value: 0.0,
|
||||
},
|
||||
executionDuration: {
|
||||
value: 4.18e8,
|
||||
},
|
||||
executeStartTime: {
|
||||
value: 1.646844917518e12,
|
||||
value_as_string: '2022-03-09T16:55:17.518Z',
|
||||
},
|
||||
},
|
||||
actionExecution: {
|
||||
meta: {},
|
||||
doc_count: 5,
|
||||
actionOutcomes: {
|
||||
doc_count_error_upper_bound: 0,
|
||||
sum_other_doc_count: 0,
|
||||
buckets: [
|
||||
{
|
||||
key: 'success',
|
||||
doc_count: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
value: 417,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 10000, relation: 'gte' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
expect(() => formatExecutionKPIResult(results)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Results are limited to 10,000 documents, refine your search to see others."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { KueryNode } from '@kbn/es-query';
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import Boom from '@hapi/boom';
|
||||
|
@ -14,7 +15,7 @@ import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query';
|
|||
import { parseDuration } from '.';
|
||||
import { IExecutionLog, IExecutionLogResult, EMPTY_EXECUTION_KPI_RESULT } from '../../common';
|
||||
|
||||
const DEFAULT_MAX_BUCKETS_LIMIT = 1000; // do not retrieve more than this number of executions
|
||||
const DEFAULT_MAX_BUCKETS_LIMIT = 10000; // do not retrieve more than this number of executions. UI limits 1000 to display, but we need to fetch all 10000 to accurately reflect the KPIs
|
||||
const DEFAULT_MAX_KPI_BUCKETS_LIMIT = 10000;
|
||||
|
||||
const RULE_ID_FIELD = 'rule.id';
|
||||
|
@ -104,9 +105,7 @@ export interface ExecutionUuidKPIAggResult<TBucket = IExecutionUuidKpiAggBucket>
|
|||
|
||||
interface ExcludeExecuteStartAggResult extends estypes.AggregationsAggregateBase {
|
||||
executionUuid: ExecutionUuidAggResult;
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: estypes.AggregationsCardinalityAggregate;
|
||||
};
|
||||
executionUuidCardinality: estypes.AggregationsCardinalityAggregate; // This is an accurate type even though we're actually using a sum bucket agg
|
||||
}
|
||||
|
||||
interface ExcludeExecuteStartKpiAggResult extends estypes.AggregationsAggregateBase {
|
||||
|
@ -301,21 +300,37 @@ export function getExecutionLogAggregation({
|
|||
},
|
||||
aggs: {
|
||||
// Get total number of executions
|
||||
executionUuidCardinality: {
|
||||
filter: {
|
||||
bool: {
|
||||
...(dslFilterQuery ? { filter: dslFilterQuery } : {}),
|
||||
must: [getProviderAndActionFilter('alerting', 'execute')],
|
||||
},
|
||||
executionUuidCardinalityBuckets: {
|
||||
terms: {
|
||||
field: EXECUTION_UUID_FIELD,
|
||||
size: DEFAULT_MAX_BUCKETS_LIMIT,
|
||||
order: formatSortForTermSort([{ timestamp: { order: 'desc' } }]),
|
||||
},
|
||||
aggs: {
|
||||
executionUuidCardinality: {
|
||||
cardinality: {
|
||||
field: EXECUTION_UUID_FIELD,
|
||||
ruleExecution: {
|
||||
filter: {
|
||||
bool: {
|
||||
...(dslFilterQuery ? { filter: dslFilterQuery } : {}),
|
||||
must: [getProviderAndActionFilter('alerting', 'execute')],
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
executeStartTime: {
|
||||
min: {
|
||||
field: START_FIELD,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Cardinality aggregation isn't accurate for this use case because we want to limit the cardinality
|
||||
// to DEFAULT_MAX_BUCKETS_LIMIT. Instead, we sum the buckets and call it a cardinality.
|
||||
executionUuidCardinality: {
|
||||
sum_bucket: {
|
||||
buckets_path: 'executionUuidCardinalityBuckets>ruleExecution._count',
|
||||
},
|
||||
},
|
||||
executionUuid: {
|
||||
// Bucket by execution UUID
|
||||
terms: {
|
||||
|
@ -592,8 +607,31 @@ function formatExecutionKPIAggBuckets(buckets: IExecutionUuidKpiAggBucket[]) {
|
|||
return objToReturn;
|
||||
}
|
||||
|
||||
function validTotalHitsLimitationOnExecutionLog(esHitsTotal: estypes.SearchTotalHits) {
|
||||
if (
|
||||
esHitsTotal &&
|
||||
esHitsTotal.relation &&
|
||||
esHitsTotal.value &&
|
||||
esHitsTotal.relation === 'gte' &&
|
||||
esHitsTotal.value === 10000
|
||||
) {
|
||||
throw Boom.entityTooLarge(
|
||||
i18n.translate('xpack.alerting.feature.executionLogAggs.limitationQueryMsg', {
|
||||
defaultMessage:
|
||||
'Results are limited to 10,000 documents, refine your search to see others.',
|
||||
}),
|
||||
EMPTY_EXECUTION_LOG_RESULT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function formatExecutionKPIResult(results: AggregateEventsBySavedObjectResult) {
|
||||
const { aggregations } = results;
|
||||
const { aggregations, hits } = results;
|
||||
|
||||
if (hits && hits.total) {
|
||||
validTotalHitsLimitationOnExecutionLog(hits.total as estypes.SearchTotalHits);
|
||||
}
|
||||
|
||||
if (!aggregations || !aggregations.excludeExecuteStart) {
|
||||
return EMPTY_EXECUTION_KPI_RESULT;
|
||||
}
|
||||
|
@ -605,7 +643,11 @@ export function formatExecutionKPIResult(results: AggregateEventsBySavedObjectRe
|
|||
export function formatExecutionLogResult(
|
||||
results: AggregateEventsBySavedObjectResult
|
||||
): IExecutionLogResult {
|
||||
const { aggregations } = results;
|
||||
const { aggregations, hits } = results;
|
||||
|
||||
if (hits && hits.total) {
|
||||
validTotalHitsLimitationOnExecutionLog(hits.total as estypes.SearchTotalHits);
|
||||
}
|
||||
|
||||
if (!aggregations || !aggregations.excludeExecuteStart) {
|
||||
return EMPTY_EXECUTION_LOG_RESULT;
|
||||
|
@ -613,7 +655,7 @@ export function formatExecutionLogResult(
|
|||
|
||||
const aggs = aggregations.excludeExecuteStart as ExcludeExecuteStartAggResult;
|
||||
|
||||
const total = aggs.executionUuidCardinality.executionUuidCardinality.value;
|
||||
const total = aggs.executionUuidCardinality.value;
|
||||
const buckets = aggs.executionUuid.buckets;
|
||||
|
||||
return {
|
||||
|
|
|
@ -338,12 +338,14 @@ const aggregateResults = {
|
|||
],
|
||||
},
|
||||
executionUuidCardinality: {
|
||||
executionUuidCardinality: {
|
||||
value: 374,
|
||||
},
|
||||
value: 374,
|
||||
},
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
total: { value: 875, relation: 'eq' },
|
||||
hits: [],
|
||||
} as estypes.SearchHitsMetadata<unknown>,
|
||||
};
|
||||
|
||||
function getRuleSavedObject(attributes: Partial<RawRule> = {}): SavedObject<RawRule> {
|
||||
|
|
|
@ -673,6 +673,13 @@ describe('aggregateEventsBySavedObject', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
total: {
|
||||
relation: 'eq',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -771,6 +778,13 @@ describe('aggregateEventsWithAuthFilter', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
total: {
|
||||
relation: 'eq',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -919,6 +933,13 @@ describe('aggregateEventsWithAuthFilter', () => {
|
|||
],
|
||||
},
|
||||
},
|
||||
hits: {
|
||||
hits: [],
|
||||
total: {
|
||||
relation: 'eq',
|
||||
value: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -82,6 +82,7 @@ export type AggregateEventsOptionsBySavedObjectFilter = QueryOptionsEventsBySave
|
|||
};
|
||||
|
||||
export interface AggregateEventsBySavedObjectResult {
|
||||
hits?: estypes.SearchHitsMetadata<unknown>;
|
||||
aggregations: Record<string, estypes.AggregationsAggregate> | undefined;
|
||||
}
|
||||
|
||||
|
@ -455,12 +456,13 @@ export class ClusterClientAdapter<TDoc extends { body: AliasAny; index: string }
|
|||
};
|
||||
|
||||
try {
|
||||
const { aggregations } = await esClient.search<IValidatedEvent>({
|
||||
const { aggregations, hits } = await esClient.search<IValidatedEvent>({
|
||||
index,
|
||||
body,
|
||||
});
|
||||
return {
|
||||
aggregations,
|
||||
hits,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
|
@ -488,19 +490,20 @@ export class ClusterClientAdapter<TDoc extends { body: AliasAny; index: string }
|
|||
query,
|
||||
aggs,
|
||||
};
|
||||
|
||||
try {
|
||||
const { aggregations } = await esClient.search<IValidatedEvent>({
|
||||
const { aggregations, hits } = await esClient.search<IValidatedEvent>({
|
||||
index,
|
||||
body,
|
||||
});
|
||||
return {
|
||||
aggregations,
|
||||
hits,
|
||||
};
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
this.logger.debug(
|
||||
`querying for Event Log by for type "${type}" and auth filter failed with: ${err.message}`
|
||||
);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { useCallback } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import datemath from '@kbn/datemath';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
@ -37,7 +37,6 @@ const isGlobal = (props: UseLoadRuleEventLogsProps): props is LoadGlobalExecutio
|
|||
|
||||
export function useLoadRuleEventLogs(props: UseLoadRuleEventLogsProps) {
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const queryFn = useCallback(() => {
|
||||
if (isGlobal(props)) {
|
||||
return loadGlobalExecutionLogAggregations({
|
||||
|
@ -55,16 +54,21 @@ export function useLoadRuleEventLogs(props: UseLoadRuleEventLogsProps) {
|
|||
});
|
||||
}, [props, http]);
|
||||
|
||||
const { data, isLoading, isFetching, refetch } = useQuery({
|
||||
const { data, error, isLoading, isFetching, refetch } = useQuery({
|
||||
queryKey: ['loadRuleEventLog', props],
|
||||
queryFn,
|
||||
onError: props.onError,
|
||||
retry: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
isLoading: isLoading || isFetching,
|
||||
loadEventLogs: refetch,
|
||||
};
|
||||
const hasExceedLogs = useMemo(() => error && error.body.statusCode === 413, [error]);
|
||||
return useMemo(
|
||||
() => ({
|
||||
data,
|
||||
hasExceedLogs,
|
||||
isLoading: isLoading || isFetching,
|
||||
loadEventLogs: refetch,
|
||||
}),
|
||||
[data, hasExceedLogs, isFetching, isLoading, refetch]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,10 @@ import { loadExecutionKPIAggregations } from '../../../lib/rule_api/load_executi
|
|||
import { loadGlobalExecutionKPIAggregations } from '../../../lib/rule_api/load_global_execution_kpi_aggregations';
|
||||
import { RuleEventLogListKPI } from './rule_event_log_list_kpi';
|
||||
import { getIsExperimentalFeatureEnabled } from '../../../../common/get_experimental_features';
|
||||
import { useKibana } from '../../../../common/lib';
|
||||
import { IToasts } from '@kbn/core/public';
|
||||
|
||||
const addDangerMock = jest.fn();
|
||||
jest.mock('../../../../common/lib/kibana', () => ({
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
services: {
|
||||
|
@ -20,6 +23,7 @@ jest.mock('../../../../common/lib/kibana', () => ({
|
|||
},
|
||||
}),
|
||||
}));
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
|
||||
jest.mock('../../../lib/rule_api/load_execution_kpi_aggregations', () => ({
|
||||
loadExecutionKPIAggregations: jest.fn(),
|
||||
|
@ -53,6 +57,9 @@ const loadGlobalExecutionKPIAggregationsMock =
|
|||
describe('rule_event_log_list_kpi', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
useKibanaMock().services.notifications.toasts = {
|
||||
addDanger: addDangerMock,
|
||||
} as unknown as IToasts;
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock<any, any>).mockImplementation(() => false);
|
||||
loadExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse);
|
||||
loadGlobalExecutionKPIAggregationsMock.mockResolvedValue(mockKpiResponse);
|
||||
|
@ -226,4 +233,44 @@ describe('rule_event_log_list_kpi', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('Should call addDanger function when an the API throw an error', async () => {
|
||||
loadGlobalExecutionKPIAggregationsMock.mockRejectedValue({ body: { statusCode: 400 } });
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogListKPI
|
||||
ruleId="*"
|
||||
dateStart="now-24h"
|
||||
dateEnd="now"
|
||||
loadExecutionKPIAggregations={loadExecutionKPIAggregationsMock}
|
||||
loadGlobalExecutionKPIAggregations={loadGlobalExecutionKPIAggregationsMock}
|
||||
/>
|
||||
);
|
||||
// Let the load resolve
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(addDangerMock).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Should NOT call addDanger function when an the API throw a 413 error', async () => {
|
||||
loadGlobalExecutionKPIAggregationsMock.mockRejectedValue({ body: { statusCode: 413 } });
|
||||
const wrapper = mountWithIntl(
|
||||
<RuleEventLogListKPI
|
||||
ruleId="*"
|
||||
dateStart="now-24h"
|
||||
dateEnd="now"
|
||||
loadExecutionKPIAggregations={loadExecutionKPIAggregationsMock}
|
||||
loadGlobalExecutionKPIAggregations={loadGlobalExecutionKPIAggregationsMock}
|
||||
/>
|
||||
);
|
||||
// Let the load resolve
|
||||
await act(async () => {
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
});
|
||||
|
||||
expect(addDangerMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -109,6 +109,9 @@ export const RuleEventLogListKPI = (props: RuleEventLogListKPIProps) => {
|
|||
});
|
||||
setKpi(newKpi);
|
||||
} catch (e) {
|
||||
if (e.body.statusCode === 413) {
|
||||
return;
|
||||
}
|
||||
toasts.addDanger({
|
||||
title: API_FAILED_MESSAGE,
|
||||
text: e.body?.message ?? e,
|
||||
|
|
|
@ -95,6 +95,7 @@ describe('rule_event_log_list_table', () => {
|
|||
useLoadRuleEventLogs.mockReturnValue({
|
||||
data: mockLogResponse,
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
});
|
||||
|
@ -130,6 +131,7 @@ describe('rule_event_log_list_table', () => {
|
|||
useLoadRuleEventLogs.mockReturnValue({
|
||||
data: [],
|
||||
isLoading: true,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
|
||||
|
@ -254,6 +256,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 100,
|
||||
},
|
||||
isLoading: true,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
});
|
||||
|
@ -306,6 +309,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 0,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
render(<RuleEventLogListWithProvider ruleId={ruleMock.id} />);
|
||||
|
@ -324,6 +328,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 1,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
|
||||
|
@ -343,6 +348,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 85,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
|
||||
|
@ -368,6 +374,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 85,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
|
||||
|
@ -397,7 +404,7 @@ describe('rule_event_log_list_table', () => {
|
|||
outcomeFilter: [],
|
||||
page: 0,
|
||||
perPage: 10,
|
||||
dateStart: 'now-24h',
|
||||
dateStart: 'now-15m',
|
||||
dateEnd: 'now',
|
||||
})
|
||||
);
|
||||
|
@ -460,6 +467,7 @@ describe('rule_event_log_list_table', () => {
|
|||
total: 1100,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: false,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
});
|
||||
|
@ -485,6 +493,36 @@ describe('rule_event_log_list_table', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Show exceed document prompt', () => {
|
||||
beforeEach(() => {
|
||||
useLoadRuleEventLogs.mockReturnValue({
|
||||
data: {
|
||||
data: [],
|
||||
total: 11000,
|
||||
},
|
||||
isLoading: false,
|
||||
hasExceedLogs: true,
|
||||
loadEventLogs: mockLoadEventLog,
|
||||
});
|
||||
});
|
||||
|
||||
it('should show the exceed limit logs prompt normally', async () => {
|
||||
render(<RuleEventLogListWithProvider ruleId={ruleMock.id} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('exceedLimitLogsCallout')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide the logs table', async () => {
|
||||
render(<RuleEventLogListWithProvider ruleId={ruleMock.id} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('eventLogList')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('renders errored action badges in message rows', async () => {
|
||||
useLoadRuleEventLogs.mockReturnValue({
|
||||
data: {
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
OnTimeChangeProps,
|
||||
EuiSwitch,
|
||||
EuiDataGridColumn,
|
||||
EuiCallOut,
|
||||
} from '@elastic/eui';
|
||||
import { IExecutionLog } from '@kbn/alerting-plugin/common';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
@ -150,7 +151,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
});
|
||||
|
||||
// Date related states
|
||||
const [dateStart, setDateStart] = useState<string>('now-24h');
|
||||
const [dateStart, setDateStart] = useState<string>('now-15m');
|
||||
const [dateEnd, setDateEnd] = useState<string>('now');
|
||||
const [dateFormat] = useState(() => uiSettings?.get('dateFormat'));
|
||||
const [commonlyUsedRanges] = useState(() => {
|
||||
|
@ -197,6 +198,9 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
|
||||
const onError = useCallback(
|
||||
(e) => {
|
||||
if (e.body.statusCode === 413) {
|
||||
return;
|
||||
}
|
||||
notifications.toasts.addDanger({
|
||||
title: API_FAILED_MESSAGE,
|
||||
text: e.body?.message ?? e,
|
||||
|
@ -205,7 +209,7 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
[notifications]
|
||||
);
|
||||
|
||||
const { data, isLoading, loadEventLogs } = useLoadRuleEventLogs({
|
||||
const { data, isLoading, hasExceedLogs, loadEventLogs } = useLoadRuleEventLogs({
|
||||
id: ruleId,
|
||||
sort: formattedSort as LoadExecutionLogAggregationsProps['sort'],
|
||||
outcomeFilter: filter,
|
||||
|
@ -724,7 +728,19 @@ export const RuleEventLogListTable = <T extends RuleEventLogListOptions>(
|
|||
<EuiSpacer />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{renderList()}
|
||||
{hasExceedLogs && (
|
||||
<EuiCallOut
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.triggersActionsUI.sections.exceedLog.refineSearch.prompt"
|
||||
defaultMessage="Results are limited to 10,000 documents, refine your search to see others."
|
||||
/>
|
||||
}
|
||||
data-test-subj="exceedLimitLogsCallout"
|
||||
size="m"
|
||||
/>
|
||||
)}
|
||||
{!hasExceedLogs && renderList()}
|
||||
{isOnLastPage && (
|
||||
<RefineSearchPrompt
|
||||
documentSize={actualTotalItemCount}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue