Skip to content

Commit

Permalink
[8.12] [Discover][Alerts] Fix Discover results when alert excludes ma…
Browse files Browse the repository at this point in the history
…tches 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)](#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
#148282
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=\"https://github.com/elastic/kibana/assets/1415710/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=\"https://github.com/elastic/kibana/assets/1415710/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":"#176690
Fix Discover results when alert excludes matches from previous runs
(#176690)\n\n- Closes
#148282
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=\"https://github.com/elastic/kibana/assets/1415710/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=\"https://github.com/elastic/kibana/assets/1415710/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":"#176690
Fix Discover results when alert excludes matches from previous runs
(#176690)\n\n- Closes
#148282
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=\"https://github.com/elastic/kibana/assets/1415710/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=\"https://github.com/elastic/kibana/assets/1415710/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>
  • Loading branch information
kibanamachine and jughosta committed Feb 14, 2024
1 parent 4884e48 commit e446672
Show file tree
Hide file tree
Showing 2 changed files with 164 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -55,26 +63,27 @@ const defaultParams: OnlySearchSourceRuleParams = {
};

describe('fetchSearchSourceQuery', () => {
describe('updateSearchSource', () => {
const dataViewMock = createDataView();
afterAll(() => {
jest.resetAllMocks();
});
const dataViewMock = createDataView();

const fakeNow = new Date('2020-02-09T23:15:41.941Z');
afterAll(() => {
jest.resetAllMocks();
});

beforeAll(() => {
jest.resetAllMocks();
global.Date.now = jest.fn(() => fakeNow.getTime());
});
const fakeNow = new Date('2020-02-09T23:15:41.941Z');

beforeAll(() => {
jest.resetAllMocks();
global.Date.now = jest.fn(() => fakeNow.getTime());
});

describe('updateSearchSource', () => {
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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -244,7 +273,7 @@ describe('fetchSearchSourceQuery', () => {
const searchSourceInstance = createSearchSourceMock({ index: dataViewMock });

const { dateStart, dateEnd } = getTimeRange();
const searchSource = updateSearchSource(
const { searchSource } = updateSearchSource(
searchSourceInstance,
dataViewMock,
params,
Expand Down Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -85,7 +85,8 @@ export async function fetchSearchSourceQuery({
index,
dateStart,
dateEnd,
spacePrefix
spacePrefix,
filterToExcludeHitsFromPreviousRun
);
return {
link,
Expand All @@ -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;

Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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({
Expand All @@ -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 {
Expand Down

0 comments on commit e446672

Please sign in to comment.