mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[RAM][Maintenance Window] MW scoped query schema and API changes (#171597)
## Summary Partially Resolves: https://github.com/elastic/kibana/issues/164255 This pull request is part 1/3 to add scoped queries to maintenance windows. More specifically, this PR adds the new `scoped_query` field to the `maintenanceWindow` type and schema. Also adds the `scoped_query` field to `create/update` maintenance window APIs. This PR only contains the schema and API component. All changes should be backwards compatible since the `scoped_query` field is optional. So this PR can be merged without any dependencies. The 2 PRs that comes after will be: - Frontend changes - Task runner changes ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d5754ad46f
commit
92bc2a0d7c
41 changed files with 711 additions and 90 deletions
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { LicenseType } from '@kbn/licensing-plugin/server';
|
||||
import type { LicenseType } from '@kbn/licensing-plugin/server';
|
||||
|
||||
export const PLUGIN = {
|
||||
ID: 'alerting',
|
|
@ -14,6 +14,13 @@ export enum MaintenanceWindowStatus {
|
|||
Archived = 'archived',
|
||||
}
|
||||
|
||||
export const filterStateStore = {
|
||||
APP_STATE: 'appState',
|
||||
GLOBAL_STATE: 'globalState',
|
||||
} as const;
|
||||
|
||||
export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore];
|
||||
|
||||
export interface MaintenanceWindowModificationMetadata {
|
||||
createdBy: string | null;
|
||||
updatedBy: string | null;
|
||||
|
@ -26,6 +33,23 @@ export interface DateRange {
|
|||
lte: string;
|
||||
}
|
||||
|
||||
export interface ScopeQueryFilter {
|
||||
query?: Record<string, unknown>;
|
||||
meta: Record<string, unknown>;
|
||||
$state?: {
|
||||
store: FilterStateStore;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ScopedQueryAttributes {
|
||||
kql: string;
|
||||
filters: ScopeQueryFilter[];
|
||||
dsl?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the data/maintenance_window types instead
|
||||
*/
|
||||
export interface MaintenanceWindowSOProperties {
|
||||
title: string;
|
||||
enabled: boolean;
|
||||
|
@ -34,11 +58,18 @@ export interface MaintenanceWindowSOProperties {
|
|||
events: DateRange[];
|
||||
rRule: RRuleParams;
|
||||
categoryIds?: string[] | null;
|
||||
scopedQuery?: ScopedQueryAttributes | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the data/maintenance_window types instead
|
||||
*/
|
||||
export type MaintenanceWindowSOAttributes = MaintenanceWindowSOProperties &
|
||||
MaintenanceWindowModificationMetadata;
|
||||
|
||||
/**
|
||||
* @deprecated Use the application/maintenance_window types instead
|
||||
*/
|
||||
export type MaintenanceWindow = MaintenanceWindowSOAttributes & {
|
||||
status: MaintenanceWindowStatus;
|
||||
eventStartTime: string | null;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { filterStateStore } from './v1';
|
||||
export type { FilterStateStore } from './v1';
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const filterStateStore = {
|
||||
APP_STATE: 'appState',
|
||||
GLOBAL_STATE: 'globalState',
|
||||
} as const;
|
||||
|
||||
export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore];
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { filterStateStore } from './constants/latest';
|
||||
export type { FilterStateStore } from './constants/latest';
|
||||
export { alertsFilterQuerySchema } from './schemas/latest';
|
||||
|
||||
export { filterStateStore as filterStateStoreV1 } from './constants/v1';
|
||||
export type { FilterStateStore as FilterStateStoreV1 } from './constants/v1';
|
||||
export { alertsFilterQuerySchema as alertsFilterQuerySchemaV1 } from './schemas/v1';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { alertsFilterQuerySchema } from './v1';
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { filterStateStore } from '..';
|
||||
|
||||
export const alertsFilterQuerySchema = schema.object({
|
||||
kql: schema.string(),
|
||||
filters: schema.arrayOf(
|
||||
schema.object({
|
||||
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
|
||||
meta: schema.recordOf(schema.string(), schema.any()),
|
||||
$state: schema.maybe(
|
||||
schema.object({
|
||||
store: schema.oneOf([
|
||||
schema.literal(filterStateStore.APP_STATE),
|
||||
schema.literal(filterStateStore.GLOBAL_STATE),
|
||||
]),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
dsl: schema.maybe(schema.string()),
|
||||
});
|
|
@ -8,10 +8,12 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowCategoryIdsSchemaV1 } from '../../../shared';
|
||||
import { rRuleRequestSchemaV1 } from '../../../../r_rule';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
|
||||
|
||||
export const createBodySchema = schema.object({
|
||||
title: schema.string(),
|
||||
duration: schema.number(),
|
||||
r_rule: rRuleRequestSchemaV1,
|
||||
category_ids: maintenanceWindowCategoryIdsSchemaV1,
|
||||
scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)),
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowCategoryIdsSchemaV1 } from '../../../shared';
|
||||
import { rRuleRequestSchemaV1 } from '../../../../r_rule';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
|
||||
|
||||
export const updateParamsSchema = schema.object({
|
||||
id: schema.string(),
|
||||
|
@ -19,4 +20,5 @@ export const updateBodySchema = schema.object({
|
|||
duration: schema.maybe(schema.number()),
|
||||
r_rule: schema.maybe(rRuleRequestSchemaV1),
|
||||
category_ids: maintenanceWindowCategoryIdsSchemaV1,
|
||||
scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)),
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { schema } from '@kbn/config-schema';
|
|||
import { maintenanceWindowStatusV1 } from '..';
|
||||
import { maintenanceWindowCategoryIdsSchemaV1 } from '../../shared';
|
||||
import { rRuleResponseSchemaV1 } from '../../../r_rule';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../alerts_filter_query';
|
||||
|
||||
export const maintenanceWindowEventSchema = schema.object({
|
||||
gte: schema.string(),
|
||||
|
@ -36,4 +37,5 @@ export const maintenanceWindowResponseSchema = schema.object({
|
|||
schema.literal(maintenanceWindowStatusV1.ARCHIVED),
|
||||
]),
|
||||
category_ids: maintenanceWindowCategoryIdsSchemaV1,
|
||||
scoped_query: schema.maybe(schema.nullable(alertsFilterQuerySchemaV1)),
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { validateDurationV1, validateHoursV1, validateTimezoneV1 } from '../../../validation';
|
||||
import { notifyWhenSchemaV1 } from '../../../response';
|
||||
import { filterStateStore } from '../../../common/constants/v1';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../../alerts_filter_query';
|
||||
|
||||
export const actionFrequencySchema = schema.object({
|
||||
summary: schema.boolean(),
|
||||
|
@ -17,26 +17,7 @@ export const actionFrequencySchema = schema.object({
|
|||
});
|
||||
|
||||
export const actionAlertsFilterSchema = schema.object({
|
||||
query: schema.maybe(
|
||||
schema.object({
|
||||
kql: schema.string(),
|
||||
filters: schema.arrayOf(
|
||||
schema.object({
|
||||
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
|
||||
meta: schema.recordOf(schema.string(), schema.any()),
|
||||
$state: schema.maybe(
|
||||
schema.object({
|
||||
store: schema.oneOf([
|
||||
schema.literal(filterStateStore.APP_STATE),
|
||||
schema.literal(filterStateStore.GLOBAL_STATE),
|
||||
]),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
dsl: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
query: schema.maybe(alertsFilterQuerySchemaV1),
|
||||
timeframe: schema.maybe(
|
||||
schema.object({
|
||||
days: schema.arrayOf(
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { rRuleResponseSchemaV1 } from '../../../r_rule';
|
||||
import { alertsFilterQuerySchemaV1 } from '../../../alerts_filter_query';
|
||||
import {
|
||||
ruleNotifyWhen as ruleNotifyWhenV1,
|
||||
ruleExecutionStatusValues as ruleExecutionStatusValuesV1,
|
||||
ruleExecutionStatusErrorReason as ruleExecutionStatusErrorReasonV1,
|
||||
ruleExecutionStatusWarningReason as ruleExecutionStatusWarningReasonV1,
|
||||
ruleLastRunOutcomeValues as ruleLastRunOutcomeValuesV1,
|
||||
filterStateStore as filterStateStoreV1,
|
||||
} from '../../common/constants/v1';
|
||||
import { validateNotifyWhenV1 } from '../../validation';
|
||||
|
||||
|
@ -41,25 +41,7 @@ const actionFrequencySchema = schema.object({
|
|||
});
|
||||
|
||||
const actionAlertsFilterSchema = schema.object({
|
||||
query: schema.maybe(
|
||||
schema.object({
|
||||
kql: schema.string(),
|
||||
filters: schema.arrayOf(
|
||||
schema.object({
|
||||
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
|
||||
meta: schema.recordOf(schema.string(), schema.any()),
|
||||
$state: schema.maybe(
|
||||
schema.object({
|
||||
store: schema.oneOf([
|
||||
schema.literal(filterStateStoreV1.APP_STATE),
|
||||
schema.literal(filterStateStoreV1.GLOBAL_STATE),
|
||||
]),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
query: schema.maybe(alertsFilterQuerySchemaV1),
|
||||
timeframe: schema.maybe(
|
||||
schema.object({
|
||||
days: schema.arrayOf(
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const filterStateStore = {
|
||||
APP_STATE: 'appState',
|
||||
GLOBAL_STATE: 'globalState',
|
||||
} as const;
|
||||
|
||||
export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore];
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { filterStateStore } from '../constants';
|
||||
|
||||
export const alertsFilterQuerySchema = schema.object({
|
||||
kql: schema.string(),
|
||||
filters: schema.arrayOf(
|
||||
schema.object({
|
||||
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
|
||||
meta: schema.recordOf(schema.string(), schema.any()),
|
||||
$state: schema.maybe(
|
||||
schema.object({
|
||||
store: schema.oneOf([
|
||||
schema.literal(filterStateStore.APP_STATE),
|
||||
schema.literal(filterStateStore.GLOBAL_STATE),
|
||||
]),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
dsl: schema.maybe(schema.string()),
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export { alertsFilterQuerySchema } from './alerts_filter_query_schemas';
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { alertsFilterQuerySchema } from '../schemas/alerts_filter_query_schemas';
|
||||
|
||||
export type AlertsFilterQuery = TypeOf<typeof alertsFilterQuerySchema>;
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { AlertsFilterQuery } from './alerts_filter_query';
|
|
@ -13,8 +13,12 @@ export const maintenanceWindowStatus = {
|
|||
} as const;
|
||||
|
||||
export const maintenanceWindowCategoryIdTypes = {
|
||||
KIBANA: 'kibana',
|
||||
OBSERVABILITY: 'observability',
|
||||
SECURITY_SOLUTION: 'securitySolution',
|
||||
MANAGEMENT: 'management',
|
||||
} as const;
|
||||
|
||||
export const filterStateStore = {
|
||||
APP_STATE: 'appState',
|
||||
GLOBAL_STATE: 'globalState',
|
||||
} as const;
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import _ from 'lodash';
|
||||
import moment from 'moment-timezone';
|
||||
import { RRule, Weekday } from '@kbn/rrule';
|
||||
import { RRuleParams, MaintenanceWindowSOAttributes, DateRange } from '../../../../common';
|
||||
import { RRuleParams, DateRange } from '../../../../common';
|
||||
import { MaintenanceWindow } from '../types';
|
||||
|
||||
export interface GenerateMaintenanceWindowEventsParams {
|
||||
rRule: RRuleParams;
|
||||
|
@ -58,7 +59,7 @@ export const shouldRegenerateEvents = ({
|
|||
rRule,
|
||||
duration,
|
||||
}: {
|
||||
maintenanceWindow: MaintenanceWindowSOAttributes;
|
||||
maintenanceWindow: MaintenanceWindow;
|
||||
rRule?: RRuleParams;
|
||||
duration?: number;
|
||||
}): boolean => {
|
||||
|
|
|
@ -133,6 +133,124 @@ describe('MaintenanceWindowClient - create', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should create maintenance window with scoped query', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z'));
|
||||
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(),
|
||||
});
|
||||
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
attributes: mockMaintenanceWindow,
|
||||
version: '123',
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
await createMaintenanceWindow(mockContext, {
|
||||
data: {
|
||||
title: mockMaintenanceWindow.title,
|
||||
duration: mockMaintenanceWindow.duration,
|
||||
rRule: mockMaintenanceWindow.rRule as CreateMaintenanceWindowParams['data']['rRule'],
|
||||
categoryIds: ['observability', 'securitySolution'],
|
||||
scopedQuery: {
|
||||
kql: "_id: '1234'",
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'kibana.alert.action_group',
|
||||
field: 'kibana.alert.action_group',
|
||||
params: {
|
||||
query: 'test',
|
||||
},
|
||||
type: 'phrase',
|
||||
},
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'kibana.alert.action_group': 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(savedObjectsClient.create).toHaveBeenLastCalledWith(
|
||||
MAINTENANCE_WINDOW_SAVED_OBJECT_TYPE,
|
||||
expect.objectContaining({
|
||||
title: mockMaintenanceWindow.title,
|
||||
duration: mockMaintenanceWindow.duration,
|
||||
rRule: mockMaintenanceWindow.rRule,
|
||||
enabled: true,
|
||||
expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(),
|
||||
categoryIds: ['observability', 'securitySolution'],
|
||||
...updatedMetadata,
|
||||
}),
|
||||
{
|
||||
id: expect.any(String),
|
||||
}
|
||||
);
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.kql
|
||||
).toEqual(`_id: '1234'`);
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.filters[0]
|
||||
).toEqual({
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
field: 'kibana.alert.action_group',
|
||||
key: 'kibana.alert.action_group',
|
||||
negate: false,
|
||||
params: { query: 'test' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'kibana.alert.action_group': 'test' } },
|
||||
});
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.dsl
|
||||
).toMatchInlineSnapshot(
|
||||
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"_id\\":\\"'1234'\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"kibana.alert.action_group\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"`
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw if trying to create a maintenance window with invalid scoped query', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z'));
|
||||
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
expirationDate: moment(new Date()).tz('UTC').add(1, 'year').toISOString(),
|
||||
});
|
||||
|
||||
await expect(async () => {
|
||||
await createMaintenanceWindow(mockContext, {
|
||||
data: {
|
||||
title: mockMaintenanceWindow.title,
|
||||
duration: mockMaintenanceWindow.duration,
|
||||
rRule: mockMaintenanceWindow.rRule as CreateMaintenanceWindowParams['data']['rRule'],
|
||||
categoryIds: ['observability', 'securitySolution'],
|
||||
scopedQuery: {
|
||||
kql: 'invalid: ',
|
||||
filters: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
}).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Error validating create maintenance scoped query - Expected \\"(\\", \\"{\\", value, whitespace but end of input found.
|
||||
invalid:
|
||||
---------^"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should throw if trying to create a maintenance window with invalid category ids', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date('2023-02-26T00:00:00.000Z'));
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import moment from 'moment';
|
||||
import Boom from '@hapi/boom';
|
||||
import { SavedObjectsUtils } from '@kbn/core/server';
|
||||
import { buildEsQuery, Filter } from '@kbn/es-query';
|
||||
import { generateMaintenanceWindowEvents } from '../../lib/generate_maintenance_window_events';
|
||||
import type { MaintenanceWindowClientContext } from '../../../../../common';
|
||||
import type { MaintenanceWindow } from '../../types';
|
||||
|
@ -25,7 +26,7 @@ export async function createMaintenanceWindow(
|
|||
): Promise<MaintenanceWindow> {
|
||||
const { data } = params;
|
||||
const { savedObjectsClient, getModificationMetadata, logger } = context;
|
||||
const { title, duration, rRule, categoryIds } = data;
|
||||
const { title, duration, rRule, categoryIds, scopedQuery } = data;
|
||||
|
||||
try {
|
||||
createMaintenanceWindowParamsSchema.validate(params);
|
||||
|
@ -33,6 +34,25 @@ export async function createMaintenanceWindow(
|
|||
throw Boom.badRequest(`Error validating create maintenance window data - ${error.message}`);
|
||||
}
|
||||
|
||||
let scopedQueryWithGeneratedValue = scopedQuery;
|
||||
try {
|
||||
if (scopedQuery) {
|
||||
const dsl = JSON.stringify(
|
||||
buildEsQuery(
|
||||
undefined,
|
||||
[{ query: scopedQuery.kql, language: 'kuery' }],
|
||||
scopedQuery.filters as Filter[]
|
||||
)
|
||||
);
|
||||
scopedQueryWithGeneratedValue = {
|
||||
...scopedQuery,
|
||||
dsl,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating create maintenance scoped query - ${error.message}`);
|
||||
}
|
||||
|
||||
const id = SavedObjectsUtils.generateId();
|
||||
const expirationDate = moment().utc().add(1, 'year').toISOString();
|
||||
const modificationMetadata = await getModificationMetadata();
|
||||
|
@ -43,6 +63,7 @@ export async function createMaintenanceWindow(
|
|||
enabled: true,
|
||||
expirationDate,
|
||||
categoryIds,
|
||||
scopedQuery: scopedQueryWithGeneratedValue,
|
||||
rRule: rRule as MaintenanceWindow['rRule'],
|
||||
duration,
|
||||
events,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowCategoryIdsSchema } from '../../../schemas';
|
||||
import { rRuleRequestSchema } from '../../../../r_rule/schemas';
|
||||
import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas';
|
||||
|
||||
export const createMaintenanceWindowParamsSchema = schema.object({
|
||||
data: schema.object({
|
||||
|
@ -15,5 +16,6 @@ export const createMaintenanceWindowParamsSchema = schema.object({
|
|||
duration: schema.number(),
|
||||
rRule: rRuleRequestSchema,
|
||||
categoryIds: maintenanceWindowCategoryIdsSchema,
|
||||
scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowCategoryIdsSchema } from '../../../schemas';
|
||||
import { rRuleRequestSchema } from '../../../../r_rule/schemas';
|
||||
import { alertsFilterQuerySchema } from '../../../../alerts_filter_query/schemas';
|
||||
|
||||
export const updateMaintenanceWindowParamsSchema = schema.object({
|
||||
id: schema.string(),
|
||||
|
@ -17,5 +18,6 @@ export const updateMaintenanceWindowParamsSchema = schema.object({
|
|||
duration: schema.maybe(schema.number()),
|
||||
rRule: schema.maybe(rRuleRequestSchema),
|
||||
categoryIds: maintenanceWindowCategoryIdsSchema,
|
||||
scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)),
|
||||
}),
|
||||
});
|
||||
|
|
|
@ -207,6 +207,172 @@ describe('MaintenanceWindowClient - update', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should update maintenance window with scoped query', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(firstTimestamp));
|
||||
|
||||
const modifiedEvents = [
|
||||
{ gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T00:12:34.000Z' },
|
||||
{ gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-01T23:43:21.000Z' },
|
||||
];
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 5,
|
||||
} as MaintenanceWindow['rRule'],
|
||||
events: modifiedEvents,
|
||||
expirationDate: moment(new Date(firstTimestamp)).tz('UTC').add(2, 'week').toISOString(),
|
||||
});
|
||||
|
||||
savedObjectsClient.get.mockResolvedValue({
|
||||
attributes: mockMaintenanceWindow,
|
||||
version: '123',
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
savedObjectsClient.create.mockResolvedValue({
|
||||
attributes: {
|
||||
...mockMaintenanceWindow,
|
||||
...updatedAttributes,
|
||||
...updatedMetadata,
|
||||
},
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
await updateMaintenanceWindow(mockContext, {
|
||||
id: 'test-id',
|
||||
data: {
|
||||
scopedQuery: {
|
||||
kql: "_id: '1234'",
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'kibana.alert.action_group',
|
||||
field: 'kibana.alert.action_group',
|
||||
params: {
|
||||
query: 'test',
|
||||
},
|
||||
type: 'phrase',
|
||||
},
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'kibana.alert.action_group': 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.kql
|
||||
).toEqual(`_id: '1234'`);
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.filters[0]
|
||||
).toEqual({
|
||||
$state: { store: 'appState' },
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
field: 'kibana.alert.action_group',
|
||||
key: 'kibana.alert.action_group',
|
||||
negate: false,
|
||||
params: { query: 'test' },
|
||||
type: 'phrase',
|
||||
},
|
||||
query: { match_phrase: { 'kibana.alert.action_group': 'test' } },
|
||||
});
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery!.dsl
|
||||
).toMatchInlineSnapshot(
|
||||
`"{\\"bool\\":{\\"must\\":[],\\"filter\\":[{\\"bool\\":{\\"should\\":[{\\"match\\":{\\"_id\\":\\"'1234'\\"}}],\\"minimum_should_match\\":1}},{\\"match_phrase\\":{\\"kibana.alert.action_group\\":\\"test\\"}}],\\"should\\":[],\\"must_not\\":[]}}"`
|
||||
);
|
||||
});
|
||||
|
||||
it('should remove maintenance window with scoped query', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(firstTimestamp));
|
||||
|
||||
const modifiedEvents = [
|
||||
{ gte: '2023-03-26T00:00:00.000Z', lte: '2023-03-26T00:12:34.000Z' },
|
||||
{ gte: '2023-04-01T23:00:00.000Z', lte: '2023-04-01T23:43:21.000Z' },
|
||||
];
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
rRule: {
|
||||
tzid: 'CET',
|
||||
dtstart: '2023-03-26T00:00:00.000Z',
|
||||
freq: Frequency.WEEKLY,
|
||||
count: 5,
|
||||
} as MaintenanceWindow['rRule'],
|
||||
events: modifiedEvents,
|
||||
expirationDate: moment(new Date(firstTimestamp)).tz('UTC').add(2, 'week').toISOString(),
|
||||
});
|
||||
|
||||
savedObjectsClient.get.mockResolvedValue({
|
||||
attributes: mockMaintenanceWindow,
|
||||
version: '123',
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
savedObjectsClient.create.mockResolvedValue({
|
||||
attributes: {
|
||||
...mockMaintenanceWindow,
|
||||
...updatedAttributes,
|
||||
...updatedMetadata,
|
||||
},
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
await updateMaintenanceWindow(mockContext, {
|
||||
id: 'test-id',
|
||||
data: {
|
||||
scopedQuery: null,
|
||||
},
|
||||
});
|
||||
|
||||
expect(
|
||||
(savedObjectsClient.create.mock.calls[0][1] as MaintenanceWindow).scopedQuery
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw if updating a maintenance window with invalid scoped query', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(firstTimestamp));
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
expirationDate: moment(new Date(firstTimestamp)).tz('UTC').subtract(1, 'year').toISOString(),
|
||||
});
|
||||
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
attributes: mockMaintenanceWindow,
|
||||
version: '123',
|
||||
id: 'test-id',
|
||||
} as unknown as SavedObject);
|
||||
|
||||
await expect(async () => {
|
||||
await updateMaintenanceWindow(mockContext, {
|
||||
id: 'test-id',
|
||||
data: {
|
||||
scopedQuery: {
|
||||
kql: 'invalid: ',
|
||||
filters: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
}).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Error validating update maintenance scoped query - Expected \\"(\\", \\"{\\", value, whitespace but end of input found.
|
||||
invalid:
|
||||
---------^"
|
||||
`);
|
||||
});
|
||||
|
||||
it('should throw if updating a maintenance window that has expired', async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date(firstTimestamp));
|
||||
const mockMaintenanceWindow = getMockMaintenanceWindow({
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import moment from 'moment';
|
||||
import Boom from '@hapi/boom';
|
||||
import { buildEsQuery, Filter } from '@kbn/es-query';
|
||||
import type { MaintenanceWindowClientContext } from '../../../../../common';
|
||||
import type { MaintenanceWindow } from '../../types';
|
||||
import {
|
||||
|
@ -45,7 +46,7 @@ async function updateWithOCC(
|
|||
): Promise<MaintenanceWindow> {
|
||||
const { savedObjectsClient, getModificationMetadata, logger } = context;
|
||||
const { id, data } = params;
|
||||
const { title, enabled, duration, rRule, categoryIds } = data;
|
||||
const { title, enabled, duration, rRule, categoryIds, scopedQuery } = data;
|
||||
|
||||
try {
|
||||
updateMaintenanceWindowParamsSchema.validate(params);
|
||||
|
@ -53,6 +54,25 @@ async function updateWithOCC(
|
|||
throw Boom.badRequest(`Error validating update maintenance window data - ${error.message}`);
|
||||
}
|
||||
|
||||
let scopedQueryWithGeneratedValue = scopedQuery;
|
||||
try {
|
||||
if (scopedQuery) {
|
||||
const dsl = JSON.stringify(
|
||||
buildEsQuery(
|
||||
undefined,
|
||||
[{ query: scopedQuery.kql, language: 'kuery' }],
|
||||
scopedQuery.filters as Filter[]
|
||||
)
|
||||
);
|
||||
scopedQueryWithGeneratedValue = {
|
||||
...scopedQuery,
|
||||
dsl,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
throw Boom.badRequest(`Error validating update maintenance scoped query - ${error.message}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const {
|
||||
attributes,
|
||||
|
@ -88,6 +108,9 @@ async function updateWithOCC(
|
|||
...(title ? { title } : {}),
|
||||
...(rRule ? { rRule: rRule as MaintenanceWindow['rRule'] } : {}),
|
||||
...(categoryIds !== undefined ? { categoryIds } : {}),
|
||||
...(scopedQueryWithGeneratedValue !== undefined
|
||||
? { scopedQuery: scopedQueryWithGeneratedValue }
|
||||
: {}),
|
||||
...(typeof duration === 'number' ? { duration } : {}),
|
||||
...(typeof enabled === 'boolean' ? { enabled } : {}),
|
||||
expirationDate,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { schema } from '@kbn/config-schema';
|
||||
import { maintenanceWindowStatus, maintenanceWindowCategoryIdTypes } from '../constants';
|
||||
import { rRuleSchema } from '../../r_rule/schemas';
|
||||
import { alertsFilterQuerySchema } from '../../alerts_filter_query/schemas';
|
||||
|
||||
export const maintenanceWindowEventSchema = schema.object({
|
||||
gte: schema.string(),
|
||||
|
@ -47,4 +48,5 @@ export const maintenanceWindowSchema = schema.object({
|
|||
schema.literal(maintenanceWindowStatus.ARCHIVED),
|
||||
]),
|
||||
categoryIds: maintenanceWindowCategoryIdsSchema,
|
||||
scopedQuery: schema.maybe(schema.nullable(alertsFilterQuerySchema)),
|
||||
});
|
||||
|
|
|
@ -40,5 +40,6 @@ export const transformMaintenanceWindowAttributesToMaintenanceWindow = (
|
|||
eventEndTime,
|
||||
status,
|
||||
...(attributes.categoryIds !== undefined ? { categoryIds: attributes.categoryIds } : {}),
|
||||
...(attributes.scopedQuery !== undefined ? { scopedQuery: attributes.scopedQuery } : {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -25,5 +25,8 @@ export const transformMaintenanceWindowToMaintenanceWindowAttributes = (
|
|||
...(maintenanceWindow.categoryIds !== undefined
|
||||
? { categoryIds: maintenanceWindow.categoryIds }
|
||||
: {}),
|
||||
...(maintenanceWindow.scopedQuery !== undefined
|
||||
? { scopedQuery: maintenanceWindow.scopedQuery }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -489,7 +489,7 @@ async function updateRuleAttributesAndParamsInMemory<Params extends RuleParams>(
|
|||
context,
|
||||
operations,
|
||||
rule: ruleDomain,
|
||||
ruleActions,
|
||||
ruleActions: ruleActions as RuleDomain['actions'], // TODO (http-versioning) Remove this cast once we fix injectReferencesIntoActions
|
||||
ruleType,
|
||||
});
|
||||
|
||||
|
|
|
@ -7,31 +7,10 @@
|
|||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { notifyWhenSchema } from './notify_when_schema';
|
||||
import { filterStateStore } from '../constants';
|
||||
import { alertsFilterQuerySchema } from '../../alerts_filter_query/schemas';
|
||||
|
||||
export const actionParamsSchema = schema.recordOf(schema.string(), schema.maybe(schema.any()));
|
||||
|
||||
const actionAlertsFilterQueryFiltersSchema = schema.arrayOf(
|
||||
schema.object({
|
||||
query: schema.maybe(schema.recordOf(schema.string(), schema.any())),
|
||||
meta: schema.recordOf(schema.string(), schema.any()),
|
||||
$state: schema.maybe(
|
||||
schema.object({
|
||||
store: schema.oneOf([
|
||||
schema.literal(filterStateStore.APP_STATE),
|
||||
schema.literal(filterStateStore.GLOBAL_STATE),
|
||||
]),
|
||||
})
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
const actionDomainAlertsFilterQuerySchema = schema.object({
|
||||
kql: schema.string(),
|
||||
filters: actionAlertsFilterQueryFiltersSchema,
|
||||
dsl: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
const actionAlertsFilterTimeFrameSchema = schema.object({
|
||||
days: schema.arrayOf(
|
||||
schema.oneOf([
|
||||
|
@ -52,7 +31,7 @@ const actionAlertsFilterTimeFrameSchema = schema.object({
|
|||
});
|
||||
|
||||
const actionDomainAlertsFilterSchema = schema.object({
|
||||
query: schema.maybe(actionDomainAlertsFilterQuerySchema),
|
||||
query: schema.maybe(alertsFilterQuerySchema),
|
||||
timeframe: schema.maybe(actionAlertsFilterTimeFrameSchema),
|
||||
});
|
||||
|
||||
|
@ -76,17 +55,8 @@ export const actionDomainSchema = schema.object({
|
|||
useAlertDataAsTemplate: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
||||
/**
|
||||
* Sanitized (non-domain) action schema, returned by rules clients for other solutions
|
||||
*/
|
||||
const actionAlertsFilterQuerySchema = schema.object({
|
||||
kql: schema.string(),
|
||||
filters: actionAlertsFilterQueryFiltersSchema,
|
||||
dsl: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
export const actionAlertsFilterSchema = schema.object({
|
||||
query: schema.maybe(actionAlertsFilterQuerySchema),
|
||||
query: schema.maybe(alertsFilterQuerySchema),
|
||||
timeframe: schema.maybe(actionAlertsFilterTimeFrameSchema),
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const filterStateStore = {
|
||||
APP_STATE: 'appState',
|
||||
GLOBAL_STATE: 'globalState',
|
||||
} as const;
|
||||
|
||||
export type FilterStateStore = typeof filterStateStore[keyof typeof filterStateStore];
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { FilterStateStore } from '../constants';
|
||||
|
||||
export interface AlertsFilterAttributes {
|
||||
query?: Record<string, unknown>;
|
||||
meta: Record<string, unknown>;
|
||||
$state?: {
|
||||
store: FilterStateStore;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AlertsFilterQueryAttributes {
|
||||
kql: string;
|
||||
filters: AlertsFilterAttributes[];
|
||||
dsl?: string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { AlertsFilterQueryAttributes } from './alerts_filter_query_attributes';
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
|
||||
import { RRuleAttributes } from '../../r_rule/types';
|
||||
import { MaintenanceWindowCategoryIdTypes } from '../constants';
|
||||
import type { MaintenanceWindowCategoryIdTypes } from '../constants';
|
||||
import { AlertsFilterQueryAttributes } from '../../alerts_filter_query/types';
|
||||
|
||||
export interface MaintenanceWindowEventAttributes {
|
||||
gte: string;
|
||||
|
@ -25,4 +26,5 @@ export interface MaintenanceWindowAttributes {
|
|||
createdAt: string;
|
||||
updatedAt: string;
|
||||
categoryIds?: MaintenanceWindowCategoryIdTypes[] | null;
|
||||
scopedQuery?: AlertsFilterQueryAttributes | null;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import type { SavedObjectAttributes } from '@kbn/core/server';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { IsoWeekday } from '../../../../common';
|
||||
import {
|
||||
ruleNotifyWhenAttributes,
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
ruleExecutionStatusWarningReasonAttributes,
|
||||
} from '../constants';
|
||||
import { RRuleAttributes } from '../../r_rule/types';
|
||||
import { AlertsFilterQueryAttributes } from '../../alerts_filter_query/types';
|
||||
|
||||
export type RuleNotifyWhenAttributes =
|
||||
typeof ruleNotifyWhenAttributes[keyof typeof ruleNotifyWhenAttributes];
|
||||
|
@ -115,11 +115,7 @@ interface AlertsFilterTimeFrameAttributes {
|
|||
}
|
||||
|
||||
interface AlertsFilterAttributes {
|
||||
query?: {
|
||||
kql: string;
|
||||
filters: Filter[];
|
||||
dsl: string;
|
||||
};
|
||||
query?: AlertsFilterQueryAttributes;
|
||||
timeframe?: AlertsFilterTimeFrameAttributes;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import { capitalize } from 'lodash';
|
|||
import { Observable, Subscription } from 'rxjs';
|
||||
import { LicensingPluginStart } from '@kbn/licensing-plugin/server';
|
||||
import { ILicense, LicenseType } from '@kbn/licensing-plugin/common/types';
|
||||
import { PLUGIN } from '../constants/plugin';
|
||||
import { PLUGIN } from '../../common/constants/plugin';
|
||||
import { getRuleTypeFeatureUsageName } from './get_rule_type_feature_usage_name';
|
||||
import {
|
||||
RuleType,
|
||||
|
|
|
@ -16,5 +16,6 @@ export const transformCreateBody = (
|
|||
duration: createBody.duration,
|
||||
rRule: createBody.r_rule,
|
||||
categoryIds: createBody.category_ids,
|
||||
scopedQuery: createBody.scoped_query,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -11,12 +11,21 @@ import { UpdateMaintenanceWindowParams } from '../../../../../../application/mai
|
|||
export const transformUpdateBody = (
|
||||
updateBody: UpdateMaintenanceWindowRequestBodyV1
|
||||
): UpdateMaintenanceWindowParams['data'] => {
|
||||
const { title, enabled, duration, r_rule: rRule, category_ids: categoryIds } = updateBody;
|
||||
const {
|
||||
title,
|
||||
enabled,
|
||||
duration,
|
||||
r_rule: rRule,
|
||||
category_ids: categoryIds,
|
||||
scoped_query: scopedQuery,
|
||||
} = updateBody;
|
||||
|
||||
return {
|
||||
...(title !== undefined ? { title } : {}),
|
||||
...(enabled !== undefined ? { enabled } : {}),
|
||||
...(duration !== undefined ? { duration } : {}),
|
||||
...(rRule !== undefined ? { rRule } : {}),
|
||||
...(categoryIds !== undefined ? { categoryIds } : {}),
|
||||
...(scopedQuery !== undefined ? { scopedQuery } : {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -29,5 +29,8 @@ export const transformMaintenanceWindowToResponse = (
|
|||
...(maintenanceWindow.categoryIds !== undefined
|
||||
? { category_ids: maintenanceWindow.categoryIds }
|
||||
: {}),
|
||||
...(maintenanceWindow.scopedQuery !== undefined
|
||||
? { scoped_query: maintenanceWindow.scopedQuery }
|
||||
: {}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -25,6 +25,32 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider
|
|||
tzid: 'UTC',
|
||||
freq: 2, // weekly
|
||||
},
|
||||
scoped_query: {
|
||||
kql: "_id: '1234'",
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'kibana.alert.action_group',
|
||||
field: 'kibana.alert.action_group',
|
||||
params: {
|
||||
query: 'test',
|
||||
},
|
||||
type: 'phrase',
|
||||
},
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'kibana.alert.action_group': 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
afterEach(() => objectRemover.removeAll());
|
||||
|
||||
|
@ -69,6 +95,7 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider
|
|||
expect(response.body.r_rule.dtstart).to.eql(createParams.r_rule.dtstart);
|
||||
expect(response.body.events.length).to.be.greaterThan(0);
|
||||
expect(response.body.status).to.eql('running');
|
||||
expect(response.body.scoped_query.kql).to.eql("_id: '1234'");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
|
@ -102,5 +129,19 @@ export default function createMaintenanceWindowTests({ getService }: FtrProvider
|
|||
})
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should throw if creating maintenance window with invalid scoped query', async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
...createParams,
|
||||
scoped_query: {
|
||||
kql: 'invalid_kql:',
|
||||
filters: [],
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,6 +16,33 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
const supertest = getService('supertest');
|
||||
const supertestWithoutAuth = getService('supertestWithoutAuth');
|
||||
|
||||
const scopedQuery = {
|
||||
kql: "_id: '1234'",
|
||||
filters: [
|
||||
{
|
||||
meta: {
|
||||
disabled: false,
|
||||
negate: false,
|
||||
alias: null,
|
||||
key: 'kibana.alert.action_group',
|
||||
field: 'kibana.alert.action_group',
|
||||
params: {
|
||||
query: 'test',
|
||||
},
|
||||
type: 'phrase',
|
||||
},
|
||||
$state: {
|
||||
store: 'appState',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'kibana.alert.action_group': 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
describe('updateMaintenanceWindow', () => {
|
||||
const objectRemover = new ObjectRemover(supertest);
|
||||
const createParams = {
|
||||
|
@ -26,6 +53,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
tzid: 'UTC',
|
||||
freq: 2, // weekly
|
||||
},
|
||||
scoped_query: scopedQuery,
|
||||
};
|
||||
afterEach(() => objectRemover.removeAll());
|
||||
|
||||
|
@ -82,6 +110,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
expect(response.body.r_rule.dtstart).to.eql(createParams.r_rule.dtstart);
|
||||
expect(response.body.events.length).to.be.greaterThan(0);
|
||||
expect(response.body.status).to.eql('running');
|
||||
expect(response.body.scoped_query.kql).to.eql("_id: '1234'");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
|
@ -156,6 +185,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
until: moment.utc().add(1, 'week').toISOString(),
|
||||
},
|
||||
category_ids: ['management'],
|
||||
scoped_query: scopedQuery,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
|
@ -183,6 +213,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
...createParams,
|
||||
r_rule: updatedRRule,
|
||||
category_ids: null,
|
||||
scoped_query: null,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
|
@ -194,6 +225,7 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
expect(response.body.data[0].id).to.eql(createdMaintenanceWindow.id);
|
||||
expect(response.body.data[0].r_rule).to.eql(updatedRRule);
|
||||
expect(response.body.data[0].category_ids).to.eql(null);
|
||||
expect(response.body.data[0].scoped_query).to.eql(null);
|
||||
});
|
||||
|
||||
it('should throw if updating maintenance window with invalid category ids', async () => {
|
||||
|
@ -230,5 +262,46 @@ export default function updateMaintenanceWindowTests({ getService }: FtrProvider
|
|||
.send({ category_ids: ['something-else'] })
|
||||
.expect(400);
|
||||
});
|
||||
|
||||
it('should throw if updating maintenance window with invalid scoped query', async () => {
|
||||
const { body: createdMaintenanceWindow } = await supertest
|
||||
.post(`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
title: 'test-maintenance-window',
|
||||
duration: 60 * 60 * 1000, // 1 hr
|
||||
r_rule: {
|
||||
dtstart: new Date().toISOString(),
|
||||
tzid: 'UTC',
|
||||
freq: 2, // weekly
|
||||
count: 1,
|
||||
},
|
||||
scoped_query: scopedQuery,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
objectRemover.add(
|
||||
'space1',
|
||||
createdMaintenanceWindow.id,
|
||||
'rules/maintenance_window',
|
||||
'alerting',
|
||||
true
|
||||
);
|
||||
|
||||
await supertest
|
||||
.post(
|
||||
`${getUrlPrefix('space1')}/internal/alerting/rules/maintenance_window/${
|
||||
createdMaintenanceWindow.id
|
||||
}`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
scoped_query: {
|
||||
kql: 'invalid_kql:',
|
||||
filters: [],
|
||||
},
|
||||
})
|
||||
.expect(400);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue