Skip to content

Commit

Permalink
refactor: Update alerting DSL verify mechanism (#359) (#360)
Browse files Browse the repository at this point in the history
(cherry picked from commit 0f4e48a)
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
  • Loading branch information
1 parent 98fa532 commit 4e55a23
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 29 deletions.
23 changes: 16 additions & 7 deletions public/components/incontext_insight/generate_popover_body.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,20 @@ import { dataPluginMock } from '../../../../../src/plugins/data/public/mocks';

jest.mock('../../services');

jest.mock('../../utils', () => ({
createIndexPatterns: jest.fn().mockResolvedValue('index pattern'),
buildUrlQuery: jest.fn().mockResolvedValue('query'),
jest.mock('../../utils', () => {
const originUtils = jest.requireActual('../../utils');
return {
...originUtils,
createIndexPatterns: jest.fn().mockResolvedValue('index pattern'),
buildUrlQuery: jest.fn().mockResolvedValue('query'),
};
});

jest.spyOn(window, 'open').mockImplementation(() => null);

jest.mock('../../../../../src/core/public/utils', () => ({
...jest.requireActual('../../../../../src/core/public/utils'),
formatUrlWithWorkspaceId: jest.fn().mockReturnValue('formattedUrl'),
}));

const mockToasts = {
Expand Down Expand Up @@ -48,7 +59,7 @@ const mockDSL = `{
"range": {
"timestamp": {
"from": "2024-09-06T04:02:52||-1h",
"to": "2024-09-06T04:02:52",
"to": "2024-10-09T17:40:47+00:00",
"include_lower": true,
"include_upper": true,
"boost": 1
Expand Down Expand Up @@ -370,9 +381,7 @@ describe('GeneratePopoverBody', () => {
const button = getByText('Discover details');
expect(button).toBeInTheDocument();
fireEvent.click(button);
expect(coreStart.application.navigateToUrl).toHaveBeenCalledWith(
'data-explorer/discover#?query'
);
});
expect(window.open).toHaveBeenCalledWith('formattedUrl', '_blank');
});
});
50 changes: 36 additions & 14 deletions public/components/incontext_insight/generate_popover_body.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ import { SUMMARY_ASSISTANT_API } from '../../../common/constants/llm';
import shiny_sparkle from '../../assets/shiny_sparkle.svg';
import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public';
import { reportMetric } from '../../utils/report_metric';
import { buildUrlQuery, createIndexPatterns } from '../../utils';
import { buildUrlQuery, createIndexPatterns, extractTimeRangeDSL } from '../../utils';
import { AssistantPluginStartDependencies } from '../../types';
import { UI_SETTINGS } from '../../../../../src/plugins/data/public';
import { formatUrlWithWorkspaceId } from '../../../../../src/core/public/utils';

export const GeneratePopoverBody: React.FC<{
incontextInsight: IncontextInsightInput;
Expand All @@ -55,10 +56,25 @@ export const GeneratePopoverBody: React.FC<{
const getMonitorType = async () => {
const context = await incontextInsight.contextProvider?.();
const monitorType = context?.additionalInfo?.monitorType;
const dsl = context?.additionalInfo?.dsl;
// Only this two types from alerting contain DSL and index.
const shoudDisplayDiscoverButton =
const isSupportedMonitorType =
monitorType === 'query_level_monitor' || monitorType === 'bucket_level_monitor';
setDisplayDiscoverButton(shoudDisplayDiscoverButton);
let hasTimeRangeFilter = false;
if (dsl) {
let dslObject;
try {
dslObject = JSON.parse(dsl);
} catch (e) {
console.error('Invalid DSL', e);
return;
}
const filters = dslObject?.query?.bool?.filter;
// Filters contains time range filter,if no filters, return.
if (!filters?.length) return;
hasTimeRangeFilter = !!extractTimeRangeDSL(filters).timeRangeDSL;
}
setDisplayDiscoverButton(isSupportedMonitorType && hasTimeRangeFilter);
};
getMonitorType();
}, [incontextInsight, setDisplayDiscoverButton]);
Expand All @@ -83,7 +99,7 @@ export const GeneratePopoverBody: React.FC<{
defaultMessage: 'Generate summary error',
})
);
closePopover();
// closePopover();
return;
}
const contextContent = contextObj?.context || '';
Expand Down Expand Up @@ -142,7 +158,7 @@ export const GeneratePopoverBody: React.FC<{
defaultMessage: 'Generate summary error',
})
);
closePopover();
// closePopover();
});
};

Expand Down Expand Up @@ -195,12 +211,11 @@ export const GeneratePopoverBody: React.FC<{
if (!dsl || !indexName) return;
const dslObject = JSON.parse(dsl);
const filters = dslObject?.query?.bool?.filter;
if (!filters) return;
const timeDslIndex = filters?.findIndex((filter: Record<string, string>) => filter?.range);
const timeDsl = filters[timeDslIndex]?.range;
const timeFieldName = Object.keys(timeDsl)[0];
if (!timeFieldName) return;
filters?.splice(timeDslIndex, 1);
if (!filters?.length) return;
const { timeRangeDSL, newFilters, timeFieldName } = extractTimeRangeDSL(filters);
// Filter out time range DSL and use this result to build filter query.
if (!timeFieldName || !timeRangeDSL) return;
dslObject.query.bool.filter = newFilters;

if (getStartServices) {
const [coreStart, startDeps] = await getStartServices();
Expand All @@ -222,11 +237,18 @@ export const GeneratePopoverBody: React.FC<{
coreStart.savedObjects,
indexPattern,
dslObject,
timeDsl[timeFieldName],
timeRangeDSL,
context?.dataSourceId
);
// Navigate to new discover with query built to populate
coreStart.application.navigateToUrl(`data-explorer/discover#?${query}`);
// Navigate to new discover with query built to populate, use new window to avoid discover search failed.
const discoverUrl = `data-explorer/discover#?${query}`;
const currentWorkspace = coreStart.workspaces.currentWorkspace$.getValue();
const url = formatUrlWithWorkspaceId(
discoverUrl,
currentWorkspace?.id ?? '',
coreStart.http.basePath
);
window.open(url, '_blank');
}
} finally {
setDiscoverLoading(false);
Expand Down
12 changes: 12 additions & 0 deletions public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,15 @@ export type IncontextInsightType =
| 'error';

export type TabId = 'chat' | 'compose' | 'insights' | 'history' | 'trace';

export interface NestedRecord<T = string> {
[key: string]: T | NestedRecord<T>;
}

export interface DSL {
query?: {
bool?: {
filter?: unknown[];
};
};
}
54 changes: 46 additions & 8 deletions public/utils/alerting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@

import rison from 'rison-node';
import { stringify } from 'query-string';
import moment from 'moment';
import { buildCustomFilter } from '../../../../src/plugins/data/common';
import { url } from '../../../../src/plugins/opensearch_dashboards_utils/public';
import {
DataPublicPluginStart,
opensearchFilters,
IndexPattern,
Filter,
} from '../../../../src/plugins/data/public';
import { CoreStart } from '../../../../src/core/public';
import { NestedRecord, DSL } from '../types';

export const buildFilter = (indexPatternId: string, dsl: Record<string, unknown>) => {
export const buildFilter = (indexPatternId: string, dsl: DSL) => {
const filterAlias = 'Alerting-filters';
return buildCustomFilter(
indexPatternId,
Expand Down Expand Up @@ -69,16 +72,19 @@ export const buildUrlQuery = async (
dataStart: DataPublicPluginStart,
savedObjects: CoreStart['savedObjects'],
indexPattern: IndexPattern,
dsl: Record<string, unknown>,
dsl: DSL,
timeDsl: Record<'from' | 'to', string>,
dataSourceId?: string
) => {
const filter = buildFilter(indexPattern.id!, dsl);

const filterManager = dataStart.query.filterManager;
// There are some map and flatten operations to filters in filterManager, use this to keep aligned with discover.
filterManager.setAppFilters([filter]);
const filters = filterManager.getAppFilters();
let filters: Filter[] = [];
// If there is none filter after filtering timeRange filter, skip to build filter query.
if ((dsl?.query?.bool?.filter?.length ?? 0) > 0) {
const filter = buildFilter(indexPattern.id!, dsl);
const filterManager = dataStart.query.filterManager;
// There are some map and flatten operations to filters in filterManager, use this to keep aligned with discover.
filterManager.setAppFilters([filter]);
filters = filterManager.getAppFilters();
}

const refreshInterval = {
pause: true,
Expand Down Expand Up @@ -142,3 +148,35 @@ export const buildUrlQuery = async (
);
return hash;
};

const validateToTimeRange = (time: string) => {
// Alerting uses this format in to field of time range filter.
const TO_TIME_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
return moment.utc(time, TO_TIME_FORMAT, true).isValid();
};

export const extractTimeRangeDSL = (filters: NestedRecord[]) => {
let timeRangeDSL;
let timeFieldName;
const newFilters = filters.filter((filter) => {
if (filter?.range && typeof filter.range === 'object') {
for (const key of Object.keys(filter.range)) {
const rangeValue = filter.range[key];
if (typeof rangeValue === 'object' && 'to' in rangeValue) {
const toValue = rangeValue.to;
if (typeof toValue === 'string' && validateToTimeRange(toValue)) {
timeRangeDSL = filter.range[key];
timeFieldName = key;
return false;
}
}
}
}
return true;
});
return {
newFilters,
timeRangeDSL,
timeFieldName,
};
};
66 changes: 66 additions & 0 deletions public/utils/tests/alerting.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { extractTimeRangeDSL } from '../alerting';

describe('extractTimeRangeDSL', () => {
it('should only extract utc time range filter', () => {
expect(extractTimeRangeDSL([{ range: { timestamp: { to: 'now' } } }]).timeRangeDSL).toEqual(
undefined
);
});

it('should return undefined timeFiledName if no time range filter', () => {
expect(
extractTimeRangeDSL([
{
bool: {},
},
]).timeRangeDSL
).toBe(undefined);
});

it('should extract timeFiledName normally', () => {
expect(
extractTimeRangeDSL([
{
range: {
timestamp: {
from: '2024-10-09T17:40:47+00:00||-1h',
to: '2024-10-09T17:40:47+00:00',
include_lower: true,
include_upper: true,
boost: 1,
},
},
},
{
bool: {
must_not: [
{
match_phrase: {
response: {
query: '200',
slop: 0,
zero_terms_query: 'NONE',
boost: 1,
},
},
},
],
adjust_pure_negative: true,
boost: 1,
},
},
]).timeRangeDSL
).toStrictEqual({
from: '2024-10-09T17:40:47+00:00||-1h',
to: '2024-10-09T17:40:47+00:00',
include_lower: true,
include_upper: true,
boost: 1,
});
});
});

0 comments on commit 4e55a23

Please sign in to comment.