mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[8.12] [Discover][Alerts] Fix Discover results when alert excludes matches from previous runs (#176690) (#176931)
# Backport This will backport the following commits from `main` to `8.12`: - [[Discover][Alerts] Fix Discover results when alert excludes matches from previous runs (#176690)](https://github.com/elastic/kibana/pull/176690) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Julia Rechkunova","email":"julia.rechkunova@elastic.co"},"sourceCommit":{"committedDate":"2024-02-14T16:44:40Z","message":"[Discover][Alerts] Fix Discover results when alert excludes matches from previous runs (#176690)\n\n- Closes https://github.com/elastic/kibana/issues/148282\r\n\r\n## Summary\r\n\r\nIn case if user creates a rule and enables \"Exclude matches from\r\nprevious runs\", Discover link will now include a time filter to filter\r\nprevious results out.\r\n\r\n<img width=\"500\" alt=\"Screenshot 2024-02-12 at 14 02 18\"\r\nsrc=\"89ae9bb1
-5fe7-4366-a3db-6ed3b8ae7545\">\r\n\r\nFor testing:\r\n- Open Discover with an index which has documents before and after\r\ncurrent time (e.g. a freshly installed Kibana Sample Data Logs)\r\n- Create a new rule \r\n - Enable/disable \"Exclude matches from previous runs\" switch\r\n - Define an index connector with a link\r\n```\r\n {\r\n \"rule_id\": \"\",\r\n \"rule_name\": \"\",\r\n \"alert_id\": \"\",\r\n \"context_message\": \"\",\r\n \"link\": \"\"\r\n}\r\n```\r\n- Now navigate to Discover, create a data view for the connector index\r\n- Copy locator links from the appearing alerts and open Discover with\r\nthem in another tab\r\n\r\n<img width=\"300\" alt=\"Screenshot 2024-02-12 at 15 19 24\"\r\nsrc=\"0e5c3718
-b16a-4360-a213-490479f85088\">\r\n\r\n\r\nIf \"Exclude matches from previous runs\" was enabled, then an additional\r\nfilter will show up on Discover page for the locator link.\r\n\r\nCheck that Discover total count is the same as the one mentioned in\r\n`context_message` field.","sha":"7e3a9f8fbe7c5513e4a4b74939593779d9ba4b24","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Feature:Alerting","Team:DataDiscovery","backport:prev-minor","v8.13.0"],"title":"[Discover][Alerts] Fix Discover results when alert excludes matches from previous runs","number":176690,"url":"https://github.com/elastic/kibana/pull/176690","mergeCommit":{"message":"[Discover][Alerts] Fix Discover results when alert excludes matches from previous runs (#176690)\n\n- Closes https://github.com/elastic/kibana/issues/148282\r\n\r\n## Summary\r\n\r\nIn case if user creates a rule and enables \"Exclude matches from\r\nprevious runs\", Discover link will now include a time filter to filter\r\nprevious results out.\r\n\r\n<img width=\"500\" alt=\"Screenshot 2024-02-12 at 14 02 18\"\r\nsrc=\"89ae9bb1
-5fe7-4366-a3db-6ed3b8ae7545\">\r\n\r\nFor testing:\r\n- Open Discover with an index which has documents before and after\r\ncurrent time (e.g. a freshly installed Kibana Sample Data Logs)\r\n- Create a new rule \r\n - Enable/disable \"Exclude matches from previous runs\" switch\r\n - Define an index connector with a link\r\n```\r\n {\r\n \"rule_id\": \"\",\r\n \"rule_name\": \"\",\r\n \"alert_id\": \"\",\r\n \"context_message\": \"\",\r\n \"link\": \"\"\r\n}\r\n```\r\n- Now navigate to Discover, create a data view for the connector index\r\n- Copy locator links from the appearing alerts and open Discover with\r\nthem in another tab\r\n\r\n<img width=\"300\" alt=\"Screenshot 2024-02-12 at 15 19 24\"\r\nsrc=\"0e5c3718
-b16a-4360-a213-490479f85088\">\r\n\r\n\r\nIf \"Exclude matches from previous runs\" was enabled, then an additional\r\nfilter will show up on Discover page for the locator link.\r\n\r\nCheck that Discover total count is the same as the one mentioned in\r\n`context_message` field.","sha":"7e3a9f8fbe7c5513e4a4b74939593779d9ba4b24"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.13.0","branchLabelMappingKey":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/176690","number":176690,"mergeCommit":{"message":"[Discover][Alerts] Fix Discover results when alert excludes matches from previous runs (#176690)\n\n- Closes https://github.com/elastic/kibana/issues/148282\r\n\r\n## Summary\r\n\r\nIn case if user creates a rule and enables \"Exclude matches from\r\nprevious runs\", Discover link will now include a time filter to filter\r\nprevious results out.\r\n\r\n<img width=\"500\" alt=\"Screenshot 2024-02-12 at 14 02 18\"\r\nsrc=\"89ae9bb1
-5fe7-4366-a3db-6ed3b8ae7545\">\r\n\r\nFor testing:\r\n- Open Discover with an index which has documents before and after\r\ncurrent time (e.g. a freshly installed Kibana Sample Data Logs)\r\n- Create a new rule \r\n - Enable/disable \"Exclude matches from previous runs\" switch\r\n - Define an index connector with a link\r\n```\r\n {\r\n \"rule_id\": \"\",\r\n \"rule_name\": \"\",\r\n \"alert_id\": \"\",\r\n \"context_message\": \"\",\r\n \"link\": \"\"\r\n}\r\n```\r\n- Now navigate to Discover, create a data view for the connector index\r\n- Copy locator links from the appearing alerts and open Discover with\r\nthem in another tab\r\n\r\n<img width=\"300\" alt=\"Screenshot 2024-02-12 at 15 19 24\"\r\nsrc=\"0e5c3718
-b16a-4360-a213-490479f85088\">\r\n\r\n\r\nIf \"Exclude matches from previous runs\" was enabled, then an additional\r\nfilter will show up on Discover page for the locator link.\r\n\r\nCheck that Discover total count is the same as the one mentioned in\r\n`context_message` field.","sha":"7e3a9f8fbe7c5513e4a4b74939593779d9ba4b24"}}]}] BACKPORT--> Co-authored-by: Julia Rechkunova <julia.rechkunova@elastic.co>
This commit is contained in:
parent
4884e48e60
commit
e446672754
2 changed files with 166 additions and 30 deletions
|
@ -7,14 +7,22 @@
|
|||
|
||||
import { OnlySearchSourceRuleParams } from '../types';
|
||||
import { createSearchSourceMock } from '@kbn/data-plugin/common/search/search_source/mocks';
|
||||
import { updateSearchSource, getSmallerDataViewSpec } from './fetch_search_source_query';
|
||||
import {
|
||||
updateSearchSource,
|
||||
generateLink,
|
||||
updateFilterReferences,
|
||||
getSmallerDataViewSpec,
|
||||
} from './fetch_search_source_query';
|
||||
import {
|
||||
createStubDataView,
|
||||
stubbedSavedObjectIndexPattern,
|
||||
} from '@kbn/data-views-plugin/common/data_view.stub';
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { DataView, DataViewSpec } from '@kbn/data-views-plugin/common';
|
||||
import { fieldFormatsMock } from '@kbn/field-formats-plugin/common/mocks';
|
||||
import { Comparator } from '../../../../common/comparator_types';
|
||||
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
|
||||
import { DiscoverAppLocatorParams } from '@kbn/discover-plugin/common';
|
||||
import { LocatorPublic } from '@kbn/share-plugin/common';
|
||||
|
||||
const createDataView = () => {
|
||||
const id = 'test-id';
|
||||
|
@ -55,26 +63,27 @@ const defaultParams: OnlySearchSourceRuleParams = {
|
|||
};
|
||||
|
||||
describe('fetchSearchSourceQuery', () => {
|
||||
const dataViewMock = createDataView();
|
||||
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const fakeNow = new Date('2020-02-09T23:15:41.941Z');
|
||||
|
||||
beforeAll(() => {
|
||||
jest.resetAllMocks();
|
||||
global.Date.now = jest.fn(() => fakeNow.getTime());
|
||||
});
|
||||
|
||||
describe('updateSearchSource', () => {
|
||||
const dataViewMock = createDataView();
|
||||
afterAll(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
const fakeNow = new Date('2020-02-09T23:15:41.941Z');
|
||||
|
||||
beforeAll(() => {
|
||||
jest.resetAllMocks();
|
||||
global.Date.now = jest.fn(() => fakeNow.getTime());
|
||||
});
|
||||
|
||||
it('without latest timestamp', async () => {
|
||||
const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] };
|
||||
|
||||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource, filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
|
@ -83,6 +92,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
dateEnd
|
||||
);
|
||||
const searchRequest = searchSource.getSearchRequestBody();
|
||||
expect(filterToExcludeHitsFromPreviousRun).toBe(null);
|
||||
expect(searchRequest.size).toMatchInlineSnapshot(`100`);
|
||||
expect(searchRequest.query).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -113,7 +123,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource, filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
|
@ -122,6 +132,23 @@ describe('fetchSearchSourceQuery', () => {
|
|||
dateEnd
|
||||
);
|
||||
const searchRequest = searchSource.getSearchRequestBody();
|
||||
expect(filterToExcludeHitsFromPreviousRun).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"field": "time",
|
||||
"index": "test-id",
|
||||
"params": Object {},
|
||||
},
|
||||
"query": Object {
|
||||
"range": Object {
|
||||
"time": Object {
|
||||
"format": "strict_date_optional_time",
|
||||
"gt": "2020-02-09T23:12:41.941Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(searchRequest.size).toMatchInlineSnapshot(`100`);
|
||||
expect(searchRequest.query).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -160,7 +187,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource, filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
|
@ -169,6 +196,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
dateEnd
|
||||
);
|
||||
const searchRequest = searchSource.getSearchRequestBody();
|
||||
expect(filterToExcludeHitsFromPreviousRun).toBe(null);
|
||||
expect(searchRequest.size).toMatchInlineSnapshot(`100`);
|
||||
expect(searchRequest.query).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -199,7 +227,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource, filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
|
@ -208,6 +236,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
dateEnd
|
||||
);
|
||||
const searchRequest = searchSource.getSearchRequestBody();
|
||||
expect(filterToExcludeHitsFromPreviousRun).toBe(null);
|
||||
expect(searchRequest.size).toMatchInlineSnapshot(`100`);
|
||||
expect(searchRequest.query).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -244,7 +273,7 @@ describe('fetchSearchSourceQuery', () => {
|
|||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
|
@ -307,6 +336,95 @@ describe('fetchSearchSourceQuery', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('generateLink', () => {
|
||||
it('should include additional time filter', async () => {
|
||||
const params = { ...defaultParams, thresholdComparator: Comparator.GT_OR_EQ, threshold: [3] };
|
||||
|
||||
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });
|
||||
|
||||
const { dateStart, dateEnd } = getTimeRange();
|
||||
const { filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
searchSourceInstance,
|
||||
dataViewMock,
|
||||
params,
|
||||
'2020-02-09T23:12:41.941Z',
|
||||
dateStart,
|
||||
dateEnd
|
||||
);
|
||||
|
||||
expect(filterToExcludeHitsFromPreviousRun).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"meta": Object {
|
||||
"field": "time",
|
||||
"index": "test-id",
|
||||
"params": Object {},
|
||||
},
|
||||
"query": Object {
|
||||
"range": Object {
|
||||
"time": Object {
|
||||
"format": "strict_date_optional_time",
|
||||
"gt": "2020-02-09T23:12:41.941Z",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const locatorMock = {
|
||||
getRedirectUrl: jest.fn(() => '/app/r?l=DISCOVER_APP_LOCATOR'),
|
||||
} as unknown as LocatorPublic<DiscoverAppLocatorParams>;
|
||||
|
||||
const dataViews = {
|
||||
...dataViewPluginMocks.createStartContract(),
|
||||
create: async (spec: DataViewSpec) =>
|
||||
new DataView({ spec, fieldFormats: fieldFormatsMock }),
|
||||
};
|
||||
|
||||
const linkWithoutExcludedRuns = await generateLink(
|
||||
searchSourceInstance,
|
||||
locatorMock,
|
||||
dataViews,
|
||||
dataViewMock,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
'test1',
|
||||
null
|
||||
);
|
||||
|
||||
expect(linkWithoutExcludedRuns).toBe('test1/app/r?l=DISCOVER_APP_LOCATOR');
|
||||
expect(locatorMock.getRedirectUrl).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filters: [],
|
||||
})
|
||||
);
|
||||
|
||||
const linkWithExcludedRuns = await generateLink(
|
||||
searchSourceInstance,
|
||||
locatorMock,
|
||||
dataViews,
|
||||
dataViewMock,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
'test2',
|
||||
filterToExcludeHitsFromPreviousRun
|
||||
);
|
||||
|
||||
expect(linkWithExcludedRuns).toBe('test2/app/r?l=DISCOVER_APP_LOCATOR');
|
||||
expect(locatorMock.getRedirectUrl).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
filters: expect.arrayContaining(
|
||||
updateFilterReferences(
|
||||
[filterToExcludeHitsFromPreviousRun!],
|
||||
dataViewMock.id!,
|
||||
undefined
|
||||
)
|
||||
),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSmallerDataViewSpec', () => {
|
||||
it('should remove "count"s but keep other props like "customLabel"', async () => {
|
||||
const fieldsMap = {
|
||||
|
|
|
@ -60,7 +60,7 @@ export async function fetchSearchSourceQuery({
|
|||
const initialSearchSource = await searchSourceClient.create(params.searchConfiguration);
|
||||
|
||||
const index = initialSearchSource.getField('index') as DataView;
|
||||
const searchSource = updateSearchSource(
|
||||
const { searchSource, filterToExcludeHitsFromPreviousRun } = updateSearchSource(
|
||||
initialSearchSource,
|
||||
index,
|
||||
params,
|
||||
|
@ -85,7 +85,8 @@ export async function fetchSearchSourceQuery({
|
|||
index,
|
||||
dateStart,
|
||||
dateEnd,
|
||||
spacePrefix
|
||||
spacePrefix,
|
||||
filterToExcludeHitsFromPreviousRun
|
||||
);
|
||||
return {
|
||||
link,
|
||||
|
@ -104,7 +105,7 @@ export function updateSearchSource(
|
|||
dateStart: string,
|
||||
dateEnd: string,
|
||||
alertLimit?: number
|
||||
) {
|
||||
): { searchSource: ISearchSource; filterToExcludeHitsFromPreviousRun: Filter | null } {
|
||||
const isGroupAgg = isGroupAggregation(params.termField);
|
||||
const timeFieldName = params.timeField || index.timeFieldName;
|
||||
|
||||
|
@ -123,16 +124,17 @@ export function updateSearchSource(
|
|||
),
|
||||
];
|
||||
|
||||
let filterToExcludeHitsFromPreviousRun = null;
|
||||
if (params.excludeHitsFromPreviousRun) {
|
||||
if (latestTimestamp && latestTimestamp > dateStart) {
|
||||
// add additional filter for documents with a timestamp greater then
|
||||
// add additional filter for documents with a timestamp greater than
|
||||
// the timestamp of the previous run, so that those documents are not counted twice
|
||||
const addTimeRangeField = buildRangeFilter(
|
||||
filterToExcludeHitsFromPreviousRun = buildRangeFilter(
|
||||
field!,
|
||||
{ gt: latestTimestamp, format: 'strict_date_optional_time' },
|
||||
index
|
||||
);
|
||||
filters.push(addTimeRangeField);
|
||||
filters.push(filterToExcludeHitsFromPreviousRun);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,19 +166,31 @@ export function updateSearchSource(
|
|||
...(isGroupAgg ? { topHitsSize: params.size } : {}),
|
||||
})
|
||||
);
|
||||
return searchSourceChild;
|
||||
return {
|
||||
searchSource: searchSourceChild,
|
||||
filterToExcludeHitsFromPreviousRun,
|
||||
};
|
||||
}
|
||||
|
||||
async function generateLink(
|
||||
export async function generateLink(
|
||||
searchSource: ISearchSource,
|
||||
discoverLocator: LocatorPublic<DiscoverAppLocatorParams>,
|
||||
dataViews: DataViewsContract,
|
||||
dataViewToUpdate: DataView,
|
||||
dateStart: string,
|
||||
dateEnd: string,
|
||||
spacePrefix: string
|
||||
spacePrefix: string,
|
||||
filterToExcludeHitsFromPreviousRun: Filter | null
|
||||
) {
|
||||
const prevFilters = searchSource.getField('filter') as Filter[];
|
||||
const prevFilters = [...((searchSource.getField('filter') as Filter[]) || [])];
|
||||
|
||||
if (filterToExcludeHitsFromPreviousRun) {
|
||||
// Using the same additional filter as in the alert check above.
|
||||
// We cannot simply pass `latestTimestamp` to `timeRange.from` Discover locator params
|
||||
// as that would include `latestTimestamp` itself in the Discover results which would be wrong.
|
||||
// Results should be after `latestTimestamp` and within `dateStart` and `dateEnd`.
|
||||
prevFilters.push(filterToExcludeHitsFromPreviousRun);
|
||||
}
|
||||
|
||||
// make new adhoc data view
|
||||
const newDataView = await dataViews.create({
|
||||
|
@ -202,7 +216,11 @@ async function generateLink(
|
|||
return start + spacePrefix + '/app' + end;
|
||||
}
|
||||
|
||||
function updateFilterReferences(filters: Filter[], fromDataView: string, toDataView: string) {
|
||||
export function updateFilterReferences(
|
||||
filters: Filter[],
|
||||
fromDataView: string,
|
||||
toDataView: string | undefined
|
||||
) {
|
||||
return (filters || []).map((filter) => {
|
||||
if (filter.meta.index === fromDataView) {
|
||||
return {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue