mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Security Solution] Add a rule management filters internal endpoint (#146826)
**Addresses:** https://github.com/elastic/kibana/issues/137428 ## Summary Adds a new internal lightweight endpoint to fetch rules related information like the number of installed prebuilt rules, the number of custom rules and etc. ## Details This PR adds a quite simple and lightweight endpoint for fetching rules related information which is - the number of installed prebuilt rules - the number of custom rules - tags UI has been updated accordingly. The result of the endpoint are mostly used in the rules table filter but not limited to. **_The added endpoint doesn't implement full aggregation for fetching rule numbers so it's planned to be done in the following PR._** ### Comparison The following screenshots from the browser's network tab demonstrate that the new endpoint is faster which is good since it's intended to be updated executed relatively often whenever the rules are updated. Prebuilt rules endpoint which was used for fetching rules related information <img width="942" alt="Screenshot 2022-12-04 at 21 50 50" src="https://user-images.githubusercontent.com/3775283/205514981-ce51dd4e-faed-4999-b770-5737e0c118d4.png"> The new endpoint  ### Checklist - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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
This commit is contained in:
parent
dd3efeaa20
commit
141078e8f5
33 changed files with 681 additions and 364 deletions
|
@ -12,7 +12,7 @@ export type RulesClientMock = jest.Mocked<Schema>;
|
|||
|
||||
const createRulesClientMock = () => {
|
||||
const mocked: RulesClientMock = {
|
||||
aggregate: jest.fn(),
|
||||
aggregate: jest.fn().mockReturnValue({ alertExecutionStatus: {}, ruleLastRunOutcome: {} }),
|
||||
create: jest.fn(),
|
||||
get: jest.fn(),
|
||||
resolve: jest.fn(),
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface AggregateOptions extends IndexType {
|
|||
id: string;
|
||||
};
|
||||
filter?: string | KueryNode;
|
||||
maxTags?: number;
|
||||
}
|
||||
|
||||
interface IndexType {
|
||||
|
@ -79,7 +80,9 @@ export interface RuleAggregation {
|
|||
|
||||
export async function aggregate(
|
||||
context: RulesClientContext,
|
||||
{ options: { fields, filter, ...options } = {} }: { options?: AggregateOptions } = {}
|
||||
{
|
||||
options: { fields, filter, maxTags = 50, ...options } = {},
|
||||
}: { options?: AggregateOptions } = {}
|
||||
): Promise<AggregateResult> {
|
||||
let authorizationTuple;
|
||||
try {
|
||||
|
@ -123,7 +126,7 @@ export async function aggregate(
|
|||
terms: { field: 'alert.attributes.muteAll' },
|
||||
},
|
||||
tags: {
|
||||
terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: 50 },
|
||||
terms: { field: 'alert.attributes.tags', order: { _key: 'asc' }, size: maxTags },
|
||||
},
|
||||
snoozed: {
|
||||
nested: {
|
||||
|
|
|
@ -310,4 +310,38 @@ describe('aggregate()', () => {
|
|||
})
|
||||
);
|
||||
});
|
||||
|
||||
describe('tags number limit', () => {
|
||||
test('sets to default (50) if it is not provided', async () => {
|
||||
const rulesClient = new RulesClient(rulesClientParams);
|
||||
|
||||
await rulesClient.aggregate();
|
||||
|
||||
expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([
|
||||
{
|
||||
aggs: {
|
||||
tags: {
|
||||
terms: { size: 50 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('sets to the provided value', async () => {
|
||||
const rulesClient = new RulesClient(rulesClientParams);
|
||||
|
||||
await rulesClient.aggregate({ options: { maxTags: 1000 } });
|
||||
|
||||
expect(unsecuredSavedObjectsClient.find.mock.calls[0]).toMatchObject([
|
||||
{
|
||||
aggs: {
|
||||
tags: {
|
||||
terms: { size: 1000 },
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* 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 { left } from 'fp-ts/lib/Either';
|
||||
import { exactCheck, foldLeftRight, getPaths } from '@kbn/securitysolution-io-ts-utils';
|
||||
|
||||
import { RuleManagementFiltersResponse } from './response_schema';
|
||||
|
||||
describe('Rule management filters response schema', () => {
|
||||
test('it should validate an empty response with defaults', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should validate an non empty response with defaults', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: 10,
|
||||
prebuilt_installed_count: 20,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: ['a', 'b', 'c'],
|
||||
},
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([]);
|
||||
expect(message.schema).toEqual(payload);
|
||||
});
|
||||
|
||||
test('it should not validate an extra invalid field added', () => {
|
||||
const payload: RuleManagementFiltersResponse & { invalid_field: string } = {
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
invalid_field: 'invalid',
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual(['invalid keys "invalid_field"']);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty response with a negative "summary.prebuilt_installed_count" number', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: -1,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "rules_summary,prebuilt_installed_count"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty response with a negative "summary.custom_count"', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: -1,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "-1" supplied to "rules_summary,custom_count"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty prepackaged response if "summary.prebuilt_installed_count" is not there', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
};
|
||||
// @ts-expect-error
|
||||
delete payload.rules_summary.prebuilt_installed_count;
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "undefined" supplied to "rules_summary,prebuilt_installed_count"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
|
||||
test('it should NOT validate an empty response with wrong "aggregated_fields.tags"', () => {
|
||||
const payload: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
// @ts-expect-error Passing an invalid value for the test
|
||||
tags: [1],
|
||||
},
|
||||
};
|
||||
const decoded = RuleManagementFiltersResponse.decode(payload);
|
||||
const checked = exactCheck(payload, decoded);
|
||||
const message = foldLeftRight(checked);
|
||||
|
||||
expect(getPaths(left(message.errors))).toEqual([
|
||||
'Invalid value "1" supplied to "aggregated_fields,tags"',
|
||||
]);
|
||||
expect(message.schema).toEqual({});
|
||||
});
|
||||
});
|
|
@ -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 * as t from 'io-ts';
|
||||
import { PositiveInteger } from '@kbn/securitysolution-io-ts-types';
|
||||
|
||||
export type RuleManagementFiltersResponse = t.TypeOf<typeof RuleManagementFiltersResponse>;
|
||||
export const RuleManagementFiltersResponse = t.exact(
|
||||
t.type({
|
||||
rules_summary: t.type({
|
||||
custom_count: PositiveInteger,
|
||||
prebuilt_installed_count: PositiveInteger,
|
||||
}),
|
||||
aggregated_fields: t.type({
|
||||
tags: t.array(t.string),
|
||||
}),
|
||||
})
|
||||
);
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { INTERNAL_DETECTION_ENGINE_URL } from '../../../constants';
|
||||
|
||||
export const RULE_MANAGEMENT_FILTERS_URL = `${INTERNAL_DETECTION_ENGINE_URL}/rules/_rule_management_filters`;
|
|
@ -31,7 +31,6 @@ import {
|
|||
createPrepackagedRules,
|
||||
importRules,
|
||||
exportRules,
|
||||
fetchTags,
|
||||
getPrePackagedRulesStatus,
|
||||
previewRule,
|
||||
findRuleExceptionReferences,
|
||||
|
@ -630,26 +629,6 @@ describe('Detections Rules API', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('fetchTags', () => {
|
||||
beforeEach(() => {
|
||||
fetchMock.mockClear();
|
||||
fetchMock.mockResolvedValue(['some', 'tags']);
|
||||
});
|
||||
|
||||
test('check parameter url when fetching tags', async () => {
|
||||
await fetchTags({ signal: abortCtrl.signal });
|
||||
expect(fetchMock).toHaveBeenCalledWith('/api/detection_engine/tags', {
|
||||
signal: abortCtrl.signal,
|
||||
method: 'GET',
|
||||
});
|
||||
});
|
||||
|
||||
test('happy path', async () => {
|
||||
const resp = await fetchTags({ signal: abortCtrl.signal });
|
||||
expect(resp).toEqual(['some', 'tags']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPrePackagedRulesStatus', () => {
|
||||
const prePackagedRulesStatus = {
|
||||
rules_custom_installed: 33,
|
||||
|
|
|
@ -11,13 +11,14 @@ import type {
|
|||
ExceptionListItemSchema,
|
||||
} from '@kbn/securitysolution-io-ts-list-types';
|
||||
|
||||
import type { RuleManagementFiltersResponse } from '../../../../common/detection_engine/rule_management/api/rules/filters/response_schema';
|
||||
import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../common/detection_engine/rule_management/api/urls';
|
||||
import type { BulkActionsDryRunErrCode } from '../../../../common/constants';
|
||||
import {
|
||||
DETECTION_ENGINE_RULES_BULK_ACTION,
|
||||
DETECTION_ENGINE_RULES_PREVIEW,
|
||||
DETECTION_ENGINE_RULES_URL,
|
||||
DETECTION_ENGINE_RULES_URL_FIND,
|
||||
DETECTION_ENGINE_TAGS_URL,
|
||||
} from '../../../../common/constants';
|
||||
|
||||
import {
|
||||
|
@ -33,7 +34,6 @@ import type {
|
|||
BulkActionDuplicatePayload,
|
||||
} from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import { BulkActionType } from '../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
|
||||
import type {
|
||||
RuleResponse,
|
||||
PreviewResponse,
|
||||
|
@ -388,17 +388,19 @@ export const exportRules = async ({
|
|||
});
|
||||
};
|
||||
|
||||
export type FetchTagsResponse = string[];
|
||||
|
||||
/**
|
||||
* Fetch all unique Tags used by Rules
|
||||
* Fetch rule filters related information like installed rules count, tags and etc
|
||||
*
|
||||
* @param signal to cancel request
|
||||
*
|
||||
* @throws An error if response is not OK
|
||||
*/
|
||||
export const fetchTags = async ({ signal }: { signal?: AbortSignal }): Promise<FetchTagsResponse> =>
|
||||
KibanaServices.get().http.fetch<FetchTagsResponse>(DETECTION_ENGINE_TAGS_URL, {
|
||||
export const fetchRuleManagementFilters = async ({
|
||||
signal,
|
||||
}: {
|
||||
signal?: AbortSignal;
|
||||
}): Promise<RuleManagementFiltersResponse> =>
|
||||
KibanaServices.get().http.fetch<RuleManagementFiltersResponse>(RULE_MANAGEMENT_FILTERS_URL, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
|
|
|
@ -10,11 +10,11 @@ import type { IHttpFetchError } from '@kbn/core/public';
|
|||
import { BulkActionType } from '../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import type { BulkActionErrorResponse, BulkActionResponse, PerformBulkActionProps } from '../api';
|
||||
import { performBulkAction } from '../api';
|
||||
import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants';
|
||||
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query';
|
||||
import { useInvalidateFindRulesQuery, useUpdateRulesCache } from './use_find_rules_query';
|
||||
import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query';
|
||||
import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query';
|
||||
import { DETECTION_ENGINE_RULES_BULK_ACTION } from '../../../../../common/constants';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query';
|
||||
|
||||
export const BULK_ACTION_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_BULK_ACTION];
|
||||
|
||||
|
@ -27,7 +27,7 @@ export const useBulkActionMutation = (
|
|||
) => {
|
||||
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
|
||||
const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery();
|
||||
const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery();
|
||||
const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery();
|
||||
const invalidateFetchPrebuiltRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery();
|
||||
const updateRulesCache = useUpdateRulesCache();
|
||||
|
||||
|
@ -66,12 +66,12 @@ export const useBulkActionMutation = (
|
|||
case BulkActionType.delete:
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchRuleByIdQuery();
|
||||
invalidateFetchTagsQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
invalidateFetchPrebuiltRulesStatusQuery();
|
||||
break;
|
||||
case BulkActionType.duplicate:
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchPrebuiltRulesStatusQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
break;
|
||||
case BulkActionType.edit:
|
||||
if (updatedRules) {
|
||||
|
@ -82,7 +82,7 @@ export const useBulkActionMutation = (
|
|||
invalidateFindRulesQuery();
|
||||
}
|
||||
invalidateFetchRuleByIdQuery();
|
||||
invalidateFetchTagsQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,12 +6,12 @@
|
|||
*/
|
||||
import type { UseMutationOptions } from '@tanstack/react-query';
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { PREBUILT_RULES_URL } from '../../../../../common/detection_engine/prebuilt_rules/api/urls';
|
||||
import type { CreatePrepackagedRulesResponse } from '../api';
|
||||
import { createPrepackagedRules } from '../api';
|
||||
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query';
|
||||
import { useInvalidateFindRulesQuery } from './use_find_rules_query';
|
||||
import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query';
|
||||
import { PREBUILT_RULES_URL } from '../../../../../common/detection_engine/prebuilt_rules/api/urls';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query';
|
||||
|
||||
export const CREATE_PREBUILT_RULES_MUTATION_KEY = ['PUT', PREBUILT_RULES_URL];
|
||||
|
||||
|
@ -20,7 +20,7 @@ export const useCreatePrebuiltRulesMutation = (
|
|||
) => {
|
||||
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
|
||||
const invalidatePrePackagedRulesStatus = useInvalidateFetchPrebuiltRulesStatusQuery();
|
||||
const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery();
|
||||
const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery();
|
||||
|
||||
return useMutation(() => createPrepackagedRules(), {
|
||||
...options,
|
||||
|
@ -30,7 +30,7 @@ export const useCreatePrebuiltRulesMutation = (
|
|||
// the number of rules might change after the installation
|
||||
invalidatePrePackagedRulesStatus();
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchTagsQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
|
|
|
@ -13,8 +13,7 @@ import type {
|
|||
} from '../../../../../common/detection_engine/rule_schema';
|
||||
import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms';
|
||||
import { createRule } from '../api';
|
||||
import { useInvalidateFetchPrebuiltRulesStatusQuery } from './use_fetch_prebuilt_rules_status_query';
|
||||
import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query';
|
||||
import { useInvalidateFindRulesQuery } from './use_find_rules_query';
|
||||
|
||||
export const CREATE_RULE_MUTATION_KEY = ['POST', DETECTION_ENGINE_RULES_URL];
|
||||
|
@ -23,8 +22,7 @@ export const useCreateRuleMutation = (
|
|||
options?: UseMutationOptions<RuleResponse, Error, RuleCreateProps>
|
||||
) => {
|
||||
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
|
||||
const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery();
|
||||
const invalidateFetchPrePackagedRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery();
|
||||
const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery();
|
||||
|
||||
return useMutation<RuleResponse, Error, RuleCreateProps>(
|
||||
(rule: RuleCreateProps) => createRule({ rule: transformOutput(rule) }),
|
||||
|
@ -32,9 +30,8 @@ export const useCreateRuleMutation = (
|
|||
...options,
|
||||
mutationKey: CREATE_RULE_MUTATION_KEY,
|
||||
onSettled: (...args) => {
|
||||
invalidateFetchPrePackagedRulesStatusQuery();
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchTagsQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 { useCallback } from 'react';
|
||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import type { RuleManagementFiltersResponse } from '../../../../../common/detection_engine/rule_management/api/rules/filters/response_schema';
|
||||
import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../common/detection_engine/rule_management/api/urls';
|
||||
import { fetchRuleManagementFilters } from '../api';
|
||||
import { DEFAULT_QUERY_OPTIONS } from './constants';
|
||||
|
||||
export const RULE_MANAGEMENT_FILTERS_QUERY_KEY = ['GET', RULE_MANAGEMENT_FILTERS_URL];
|
||||
|
||||
export const useFetchRuleManagementFiltersQuery = (
|
||||
options?: UseQueryOptions<RuleManagementFiltersResponse>
|
||||
) => {
|
||||
return useQuery<RuleManagementFiltersResponse>(
|
||||
RULE_MANAGEMENT_FILTERS_QUERY_KEY,
|
||||
async ({ signal }) => {
|
||||
const response = await fetchRuleManagementFilters({ signal });
|
||||
return response;
|
||||
},
|
||||
{
|
||||
...DEFAULT_QUERY_OPTIONS,
|
||||
...options,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* We should use this hook to invalidate the rule management filters cache. For
|
||||
* example, rule mutations that affect rule set size, like creation or deletion,
|
||||
* should lead to cache invalidation.
|
||||
*
|
||||
* @returns A rules cache invalidation callback
|
||||
*/
|
||||
export const useInvalidateFetchRuleManagementFiltersQuery = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useCallback(() => {
|
||||
queryClient.invalidateQueries(RULE_MANAGEMENT_FILTERS_QUERY_KEY, {
|
||||
refetchType: 'active',
|
||||
});
|
||||
}, [queryClient]);
|
||||
};
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* 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 { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback } from 'react';
|
||||
import { DETECTION_ENGINE_TAGS_URL } from '../../../../../common/constants';
|
||||
import type { FetchTagsResponse } from '../api';
|
||||
import { fetchTags } from '../api';
|
||||
import { DEFAULT_QUERY_OPTIONS } from './constants';
|
||||
|
||||
const TAGS_QUERY_KEY = ['GET', DETECTION_ENGINE_TAGS_URL];
|
||||
|
||||
/**
|
||||
* Hook for using the list of Tags from the Detection Engine API
|
||||
*
|
||||
*/
|
||||
export const useFetchTagsQuery = (options?: UseQueryOptions<FetchTagsResponse>) => {
|
||||
return useQuery<FetchTagsResponse>(
|
||||
TAGS_QUERY_KEY,
|
||||
async ({ signal }) => {
|
||||
return fetchTags({ signal });
|
||||
},
|
||||
{
|
||||
...DEFAULT_QUERY_OPTIONS,
|
||||
...options,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const useInvalidateFetchTagsQuery = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useCallback(() => {
|
||||
queryClient.invalidateQueries(TAGS_QUERY_KEY, {
|
||||
refetchType: 'active',
|
||||
});
|
||||
}, [queryClient]);
|
||||
};
|
|
@ -11,11 +11,11 @@ import type {
|
|||
RuleUpdateProps,
|
||||
} from '../../../../../common/detection_engine/rule_schema';
|
||||
import { transformOutput } from '../../../../detections/containers/detection_engine/rules/transforms';
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
import { updateRule } from '../api';
|
||||
import { useInvalidateFindRulesQuery } from './use_find_rules_query';
|
||||
import { useInvalidateFetchTagsQuery } from './use_fetch_tags_query';
|
||||
import { useInvalidateFetchRuleByIdQuery } from './use_fetch_rule_by_id_query';
|
||||
import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from './use_fetch_rule_management_filters_query';
|
||||
|
||||
export const UPDATE_RULE_MUTATION_KEY = ['PUT', DETECTION_ENGINE_RULES_URL];
|
||||
|
||||
|
@ -23,7 +23,7 @@ export const useUpdateRuleMutation = (
|
|||
options?: UseMutationOptions<RuleResponse, Error, RuleUpdateProps>
|
||||
) => {
|
||||
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
|
||||
const invalidateFetchTagsQuery = useInvalidateFetchTagsQuery();
|
||||
const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery();
|
||||
const invalidateFetchRuleByIdQuery = useInvalidateFetchRuleByIdQuery();
|
||||
|
||||
return useMutation<RuleResponse, Error, RuleUpdateProps>(
|
||||
|
@ -34,7 +34,7 @@ export const useUpdateRuleMutation = (
|
|||
onSettled: (...args) => {
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchRuleByIdQuery();
|
||||
invalidateFetchTagsQuery();
|
||||
invalidateFetchRuleManagementFilters();
|
||||
|
||||
if (options?.onSettled) {
|
||||
options.onSettled(...args);
|
||||
|
|
|
@ -14,6 +14,13 @@ export const RULE_AND_TIMELINE_FETCH_FAILURE = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const RULE_MANAGEMENT_FILTERS_FETCH_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.containers.detectionEngine.ruleManagementFiltersFetchFailure',
|
||||
{
|
||||
defaultMessage: 'Failed to fetch rule filters',
|
||||
}
|
||||
);
|
||||
|
||||
export const RULE_ADD_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.containers.detectionEngine.addRuleFailDescription',
|
||||
{
|
||||
|
@ -48,10 +55,3 @@ export const TIMELINE_PREPACKAGED_SUCCESS = i18n.translate(
|
|||
defaultMessage: 'Installed pre-packaged timeline templates from elastic',
|
||||
}
|
||||
);
|
||||
|
||||
export const TAG_FETCH_FAILURE = i18n.translate(
|
||||
'xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription',
|
||||
{
|
||||
defaultMessage: 'Failed to fetch Tags',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -4,21 +4,16 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
|
||||
import { useFetchTagsQuery } from '../api/hooks/use_fetch_tags_query';
|
||||
import { useFetchRuleManagementFiltersQuery } from '../api/hooks/use_fetch_rule_management_filters_query';
|
||||
import * as i18n from './translations';
|
||||
|
||||
/**
|
||||
* Hook for using the list of Tags from the Detection Engine API
|
||||
*
|
||||
*/
|
||||
export const useTags = () => {
|
||||
export const useRuleManagementFilters = () => {
|
||||
const { addError } = useAppToasts();
|
||||
|
||||
return useFetchTagsQuery({
|
||||
return useFetchRuleManagementFiltersQuery({
|
||||
onError: (err) => {
|
||||
addError(err, { title: i18n.TAG_FETCH_FAILURE });
|
||||
addError(err, { title: i18n.RULE_MANAGEMENT_FILTERS_FETCH_FAILURE });
|
||||
},
|
||||
});
|
||||
};
|
|
@ -9,6 +9,7 @@ import { EuiCallOut, EuiFormRow } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useRuleManagementFilters } from '../../../../../rule_management/logic/use_rule_management_filters';
|
||||
import type { BulkActionEditPayload } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import { BulkActionEditType } from '../../../../../../../common/detection_engine/rule_management/api/rules/bulk_actions/request_schema';
|
||||
import * as i18n from '../../../../../../detections/pages/detection_engine/rules/translations';
|
||||
|
@ -25,7 +26,6 @@ import {
|
|||
} from '../../../../../../shared_imports';
|
||||
|
||||
import { BulkEditFormWrapper } from './bulk_edit_form_wrapper';
|
||||
import { useTags } from '../../../../../rule_management/logic/use_tags';
|
||||
|
||||
type TagsEditActions =
|
||||
| BulkActionEditType.add_tags
|
||||
|
@ -78,13 +78,16 @@ interface TagsFormProps {
|
|||
}
|
||||
|
||||
const TagsFormComponent = ({ editAction, rulesCount, onClose, onConfirm }: TagsFormProps) => {
|
||||
const { data: tags = [] } = useTags();
|
||||
const { data: ruleManagementFilters } = useRuleManagementFilters();
|
||||
const { form } = useForm({
|
||||
defaultValue: initialFormData,
|
||||
schema,
|
||||
});
|
||||
const [{ overwrite }] = useFormData({ form, watch: ['overwrite'] });
|
||||
const sortedTags = useMemo(() => caseInsensitiveSort(tags), [tags]);
|
||||
const sortedTags = useMemo(
|
||||
() => caseInsensitiveSort(ruleManagementFilters?.aggregated_fields.tags ?? []),
|
||||
[ruleManagementFilters]
|
||||
);
|
||||
|
||||
const { tagsLabel, tagsHelpText, formTitle } = getFormConfig(editAction);
|
||||
|
||||
|
|
|
@ -9,13 +9,12 @@ import { EuiFilterButton, EuiFilterGroup, EuiFlexGroup, EuiFlexItem } from '@ela
|
|||
import { isEqual } from 'lodash/fp';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useRuleManagementFilters } from '../../../../rule_management/logic/use_rule_management_filters';
|
||||
import { RULES_TABLE_ACTIONS } from '../../../../../common/lib/apm/user_actions';
|
||||
import { useStartTransaction } from '../../../../../common/lib/apm/use_start_transaction';
|
||||
import { usePrePackagedRulesStatus } from '../../../../rule_management/logic/use_pre_packaged_rules_status';
|
||||
import * as i18n from '../../../../../detections/pages/detection_engine/rules/translations';
|
||||
import { useRulesTableContext } from '../rules_table/rules_table_context';
|
||||
import { TagsFilterPopover } from './tags_filter_popover';
|
||||
import { useTags } from '../../../../rule_management/logic/use_tags';
|
||||
import { RuleSearchField } from './rule_search_field';
|
||||
|
||||
const FilterWrapper = styled(EuiFlexGroup)`
|
||||
|
@ -32,10 +31,10 @@ const RulesTableFiltersComponent = () => {
|
|||
state: { filterOptions },
|
||||
actions: { setFilterOptions },
|
||||
} = useRulesTableContext();
|
||||
const { data: allTags = [] } = useTags();
|
||||
const { data: prePackagedRulesStatus } = usePrePackagedRulesStatus();
|
||||
const rulesCustomInstalled = prePackagedRulesStatus?.rules_custom_installed;
|
||||
const rulesInstalled = prePackagedRulesStatus?.rules_installed;
|
||||
const { data: ruleManagementFields } = useRuleManagementFilters();
|
||||
const allTags = ruleManagementFields?.aggregated_fields.tags ?? [];
|
||||
const rulesCustomCount = ruleManagementFields?.rules_summary.custom_count;
|
||||
const rulesPrebuiltInstalledCount = ruleManagementFields?.rules_summary.prebuilt_installed_count;
|
||||
|
||||
const { showCustomRules, showElasticRules, tags: selectedTags } = filterOptions;
|
||||
|
||||
|
@ -90,7 +89,7 @@ const RulesTableFiltersComponent = () => {
|
|||
withNext
|
||||
>
|
||||
{i18n.ELASTIC_RULES}
|
||||
{rulesInstalled != null ? ` (${rulesInstalled})` : ''}
|
||||
{rulesPrebuiltInstalledCount != null ? ` (${rulesPrebuiltInstalledCount ?? ''})` : ''}
|
||||
</EuiFilterButton>
|
||||
<EuiFilterButton
|
||||
hasActiveFilters={showCustomRules}
|
||||
|
@ -98,7 +97,7 @@ const RulesTableFiltersComponent = () => {
|
|||
data-test-subj="showCustomRulesFilterButton"
|
||||
>
|
||||
{i18n.CUSTOM_RULES}
|
||||
{rulesCustomInstalled != null ? ` (${rulesCustomInstalled})` : ''}
|
||||
{rulesCustomCount != null ? ` (${rulesCustomCount})` : ''}
|
||||
</EuiFilterButton>
|
||||
</EuiFilterGroup>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -18,7 +18,6 @@ import { useBoolState } from '../../../../common/hooks/use_bool_state';
|
|||
import { useValueChanged } from '../../../../common/hooks/use_value_changed';
|
||||
import { PrePackagedRulesPrompt } from '../../../../detections/components/rules/pre_packaged_rules/load_empty_prompt';
|
||||
import type { Rule, RulesSortingFields } from '../../../rule_management/logic';
|
||||
import { usePrePackagedRulesStatus } from '../../../rule_management/logic/use_pre_packaged_rules_status';
|
||||
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
import type { EuiBasicTableOnChange } from '../../../../detections/pages/detection_engine/rules/types';
|
||||
import { BulkActionDryRunConfirmation } from './bulk_actions/bulk_action_dry_run_confirmation';
|
||||
|
@ -39,6 +38,7 @@ import { useBulkDuplicateExceptionsConfirmation } from './bulk_actions/use_bulk_
|
|||
import { BulkActionDuplicateExceptionsConfirmation } from './bulk_actions/bulk_duplicate_exceptions_confirmation';
|
||||
import { useStartMlJobs } from '../../../rule_management/logic/use_start_ml_jobs';
|
||||
import { RULES_TABLE_PAGE_SIZE_OPTIONS } from './constants';
|
||||
import { useRuleManagementFilters } from '../../../rule_management/logic/use_rule_management_filters';
|
||||
|
||||
const INITIAL_SORT_FIELD = 'enabled';
|
||||
|
||||
|
@ -65,8 +65,7 @@ export const RulesTables = React.memo<RulesTableProps>(({ selectedTab }) => {
|
|||
|
||||
const tableRef = useRef<EuiBasicTable>(null);
|
||||
const rulesTableContext = useRulesTableContext();
|
||||
const { data: prePackagedRulesStatus, isLoading: isPrepackagedStatusLoading } =
|
||||
usePrePackagedRulesStatus();
|
||||
const { data: ruleManagementFilters } = useRuleManagementFilters();
|
||||
|
||||
const {
|
||||
state: {
|
||||
|
@ -214,11 +213,10 @@ export const RulesTables = React.memo<RulesTableProps>(({ selectedTab }) => {
|
|||
}, [rules, isAllSelected, setIsAllSelected, setSelectedRuleIds]);
|
||||
|
||||
const isTableEmpty =
|
||||
!isPrepackagedStatusLoading &&
|
||||
prePackagedRulesStatus?.rules_custom_installed === 0 &&
|
||||
prePackagedRulesStatus.rules_installed === 0;
|
||||
ruleManagementFilters?.rules_summary.custom_count === 0 &&
|
||||
ruleManagementFilters?.rules_summary.prebuilt_installed_count === 0;
|
||||
|
||||
const shouldShowRulesTable = !isPrepackagedStatusLoading && !isLoading && !isTableEmpty;
|
||||
const shouldShowRulesTable = !isLoading && !isTableEmpty;
|
||||
|
||||
const tableProps =
|
||||
selectedTab === AllRulesTabs.rules
|
||||
|
|
|
@ -32,7 +32,6 @@ import { useListsConfig } from '../../../../detections/containers/detection_engi
|
|||
import { redirectToDetections } from '../../../../detections/pages/detection_engine/rules/helpers';
|
||||
|
||||
import { useInvalidateFindRulesQuery } from '../../../rule_management/api/hooks/use_find_rules_query';
|
||||
import { useInvalidateFetchPrebuiltRulesStatusQuery } from '../../../rule_management/api/hooks/use_fetch_prebuilt_rules_status_query';
|
||||
import { importRules } from '../../../rule_management/logic';
|
||||
import { usePrePackagedRulesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_rules_installation_status';
|
||||
import { usePrePackagedTimelinesInstallationStatus } from '../../../rule_management/logic/use_pre_packaged_timelines_installation_status';
|
||||
|
@ -42,17 +41,18 @@ import { RulesTableContextProvider } from '../../components/rules_table/rules_ta
|
|||
|
||||
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
import { RulesPageTourComponent } from '../../components/rules_table/alternative_tour/tour';
|
||||
import { useInvalidateFetchRuleManagementFiltersQuery } from '../../../rule_management/api/hooks/use_fetch_rule_management_filters_query';
|
||||
|
||||
const RulesPageComponent: React.FC = () => {
|
||||
const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState();
|
||||
const [isValueListFlyoutVisible, showValueListFlyout, hideValueListFlyout] = useBoolState();
|
||||
const { navigateToApp } = useKibana().services.application;
|
||||
const invalidateFindRulesQuery = useInvalidateFindRulesQuery();
|
||||
const invalidateFetchPrebuiltRulesStatusQuery = useInvalidateFetchPrebuiltRulesStatusQuery();
|
||||
const invalidateFetchRuleManagementFilters = useInvalidateFetchRuleManagementFiltersQuery();
|
||||
const invalidateRules = useCallback(() => {
|
||||
invalidateFindRulesQuery();
|
||||
invalidateFetchPrebuiltRulesStatusQuery();
|
||||
}, [invalidateFindRulesQuery, invalidateFetchPrebuiltRulesStatusQuery]);
|
||||
invalidateFetchRuleManagementFilters();
|
||||
}, [invalidateFindRulesQuery, invalidateFetchRuleManagementFilters]);
|
||||
|
||||
const [
|
||||
{
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
DETECTION_ENGINE_RULES_BULK_CREATE,
|
||||
DETECTION_ENGINE_RULES_URL_FIND,
|
||||
} from '../../../../../common/constants';
|
||||
import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../common/detection_engine/rule_management/api/urls';
|
||||
|
||||
import {
|
||||
PREBUILT_RULES_STATUS_URL,
|
||||
|
@ -33,6 +34,7 @@ import {
|
|||
getPerformBulkActionSchemaMock,
|
||||
getPerformBulkActionEditSchemaMock,
|
||||
} from '../../../../../common/detection_engine/rule_management/mocks';
|
||||
|
||||
import { getCreateRulesSchemaMock } from '../../../../../common/detection_engine/rule_schema/mocks';
|
||||
import type { QuerySignalsSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/query_signals_index_schema';
|
||||
import type { SetSignalsStatusSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/set_signal_status_schema';
|
||||
|
@ -189,6 +191,12 @@ export const getPrepackagedRulesStatusRequest = () =>
|
|||
path: PREBUILT_RULES_STATUS_URL,
|
||||
});
|
||||
|
||||
export const getRuleManagementFiltersRequest = () =>
|
||||
requestMock.create({
|
||||
method: 'get',
|
||||
path: RULE_MANAGEMENT_FILTERS_URL,
|
||||
});
|
||||
|
||||
export interface FindHit<T = RuleAlertType> {
|
||||
page: number;
|
||||
perPage: number;
|
||||
|
|
|
@ -20,6 +20,7 @@ import { deleteRuleRoute } from './rules/delete_rule/route';
|
|||
import { exportRulesRoute } from './rules/export_rules/route';
|
||||
import { findRulesRoute } from './rules/find_rules/route';
|
||||
import { importRulesRoute } from './rules/import_rules/route';
|
||||
import { getRuleManagementFilters } from './rules/filters/route';
|
||||
import { patchRuleRoute } from './rules/patch_rule/route';
|
||||
import { readRuleRoute } from './rules/read_rule/route';
|
||||
import { updateRuleRoute } from './rules/update_rule/route';
|
||||
|
@ -56,4 +57,7 @@ export const registerRuleManagementRoutes = (
|
|||
|
||||
// Rule tags
|
||||
readTagsRoute(router);
|
||||
|
||||
// Rules filters
|
||||
getRuleManagementFilters(router);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 { getRuleManagementFilters } from './route';
|
||||
|
||||
import {
|
||||
getEmptyFindResult,
|
||||
getFindResultWithSingleHit,
|
||||
getRuleManagementFiltersRequest,
|
||||
} from '../../../../routes/__mocks__/request_responses';
|
||||
import { requestContextMock, serverMock } from '../../../../routes/__mocks__';
|
||||
|
||||
describe('Rule management filters route', () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
server = serverMock.create();
|
||||
({ clients, context } = requestContextMock.createTools());
|
||||
|
||||
clients.rulesClient.find.mockResolvedValue(getEmptyFindResult());
|
||||
|
||||
getRuleManagementFilters(server.router);
|
||||
});
|
||||
|
||||
describe('status codes', () => {
|
||||
test('returns 200', async () => {
|
||||
const response = await server.inject(
|
||||
getRuleManagementFiltersRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(200);
|
||||
});
|
||||
|
||||
test('catch error when finding rules throws error', async () => {
|
||||
clients.rulesClient.find.mockImplementation(async () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
const response = await server.inject(
|
||||
getRuleManagementFiltersRequest(),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({
|
||||
message: 'Test error',
|
||||
status_code: 500,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('responses', () => {
|
||||
test('1 rule installed, 1 custom rule and 3 tags', async () => {
|
||||
clients.rulesClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
||||
clients.rulesClient.aggregate.mockResolvedValue({
|
||||
alertExecutionStatus: {},
|
||||
ruleLastRunOutcome: {},
|
||||
ruleTags: ['a', 'b', 'c'],
|
||||
});
|
||||
const request = getRuleManagementFiltersRequest();
|
||||
const response = await server.inject(request, requestContextMock.convertContext(context));
|
||||
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({
|
||||
rules_summary: {
|
||||
custom_count: 1,
|
||||
prebuilt_installed_count: 1,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: ['a', 'b', 'c'],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { validate } from '@kbn/securitysolution-io-ts-utils';
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import { RuleManagementFiltersResponse } from '../../../../../../../common/detection_engine/rule_management/api/rules/filters/response_schema';
|
||||
import { RULE_MANAGEMENT_FILTERS_URL } from '../../../../../../../common/detection_engine/rule_management/api/urls';
|
||||
import { buildSiemResponse } from '../../../../routes/utils';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../../../types';
|
||||
import { findRules } from '../../../logic/search/find_rules';
|
||||
import { readTags } from '../../tags/read_tags/read_tags';
|
||||
|
||||
interface RulesCount {
|
||||
prebuilt: number;
|
||||
custom: number;
|
||||
}
|
||||
|
||||
const DEFAULT_FIND_RULES_COUNT_PARAMS = {
|
||||
perPage: 0,
|
||||
page: 1,
|
||||
sortField: undefined,
|
||||
sortOrder: undefined,
|
||||
fields: undefined,
|
||||
};
|
||||
|
||||
async function fetchRulesCount(rulesClient: RulesClient): Promise<RulesCount> {
|
||||
const [prebuiltRules, customRules] = await Promise.all([
|
||||
findRules({
|
||||
...DEFAULT_FIND_RULES_COUNT_PARAMS,
|
||||
rulesClient,
|
||||
filter: 'alert.attributes.params.immutable: true',
|
||||
}),
|
||||
findRules({
|
||||
...DEFAULT_FIND_RULES_COUNT_PARAMS,
|
||||
rulesClient,
|
||||
filter: 'alert.attributes.params.immutable: false',
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
prebuilt: prebuiltRules.total,
|
||||
custom: customRules.total,
|
||||
};
|
||||
}
|
||||
|
||||
export const getRuleManagementFilters = (router: SecuritySolutionPluginRouter) => {
|
||||
router.get(
|
||||
{
|
||||
path: RULE_MANAGEMENT_FILTERS_URL,
|
||||
validate: false,
|
||||
options: {
|
||||
tags: ['access:securitySolution'],
|
||||
},
|
||||
},
|
||||
async (context, _, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const ctx = await context.resolve(['alerting']);
|
||||
const rulesClient = ctx.alerting.getRulesClient();
|
||||
|
||||
try {
|
||||
const [{ prebuilt: prebuiltRulesCount, custom: customRulesCount }, tags] =
|
||||
await Promise.all([fetchRulesCount(rulesClient), readTags({ rulesClient })]);
|
||||
const responseBody: RuleManagementFiltersResponse = {
|
||||
rules_summary: {
|
||||
custom_count: customRulesCount,
|
||||
prebuilt_installed_count: prebuiltRulesCount,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags,
|
||||
},
|
||||
};
|
||||
const [validatedBody, validationError] = validate(
|
||||
responseBody,
|
||||
RuleManagementFiltersResponse
|
||||
);
|
||||
|
||||
if (validationError != null) {
|
||||
return siemResponse.error({ statusCode: 500, body: validationError });
|
||||
} else {
|
||||
return response.ok({ body: validatedBody ?? {} });
|
||||
}
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -6,12 +6,7 @@
|
|||
*/
|
||||
|
||||
import { rulesClientMock } from '@kbn/alerting-plugin/server/mocks';
|
||||
import {
|
||||
getRuleMock,
|
||||
getFindResultWithMultiHits,
|
||||
} from '../../../../routes/__mocks__/request_responses';
|
||||
import { getQueryRuleParams } from '../../../../rule_schema/mocks';
|
||||
import { readTags, convertTagsToSet, convertToTags, isTags } from './read_tags';
|
||||
import { readTags } from './read_tags';
|
||||
|
||||
describe('read_tags', () => {
|
||||
afterEach(() => {
|
||||
|
@ -19,182 +14,16 @@ describe('read_tags', () => {
|
|||
});
|
||||
|
||||
describe('readTags', () => {
|
||||
test('it should return the intersection of tags to where none are repeating', async () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 2', 'tag 3'];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
result2.tags = ['tag 1', 'tag 2', 'tag 3', 'tag 4'];
|
||||
|
||||
test('it should return tags from the aggregation', async () => {
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
|
||||
rulesClient.aggregate.mockResolvedValue({
|
||||
alertExecutionStatus: {},
|
||||
ruleLastRunOutcome: {},
|
||||
ruleTags: ['tag 1', 'tag 2', 'tag 3', 'tag 4'],
|
||||
});
|
||||
|
||||
const tags = await readTags({ rulesClient });
|
||||
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
|
||||
});
|
||||
|
||||
test('it should return the intersection of tags to where some are repeating values', async () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
|
||||
|
||||
const tags = await readTags({ rulesClient });
|
||||
expect(tags).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
|
||||
});
|
||||
|
||||
test('it should work with no tags defined between two results', async () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = [];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
result2.tags = [];
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1, result2] }));
|
||||
|
||||
const tags = await readTags({ rulesClient });
|
||||
expect(tags).toEqual([]);
|
||||
});
|
||||
|
||||
test('it should work with a single tag which has repeating values in it', async () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 1', 'tag 1', 'tag 2'];
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
|
||||
|
||||
const tags = await readTags({ rulesClient });
|
||||
expect(tags).toEqual(['tag 1', 'tag 2']);
|
||||
});
|
||||
|
||||
test('it should work with a single tag which has empty tags', async () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = [];
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.find.mockResolvedValue(getFindResultWithMultiHits({ data: [result1] }));
|
||||
|
||||
const tags = await readTags({ rulesClient });
|
||||
expect(tags).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertTagsToSet', () => {
|
||||
test('it should convert the intersection of two tag systems without duplicates', () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
|
||||
|
||||
const findResult = getFindResultWithMultiHits({ data: [result1, result2] });
|
||||
const set = convertTagsToSet(findResult.data);
|
||||
expect(Array.from(set)).toEqual(['tag 1', 'tag 2', 'tag 3', 'tag 4']);
|
||||
});
|
||||
|
||||
test('it should with with an empty array', () => {
|
||||
const set = convertTagsToSet([]);
|
||||
expect(Array.from(set)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertToTags', () => {
|
||||
test('it should convert the two tag systems together with duplicates', () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
result2.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
|
||||
|
||||
const findResult = getFindResultWithMultiHits({ data: [result1, result2] });
|
||||
const tags = convertToTags(findResult.data);
|
||||
expect(tags).toEqual([
|
||||
'tag 1',
|
||||
'tag 2',
|
||||
'tag 2',
|
||||
'tag 3',
|
||||
'tag 1',
|
||||
'tag 2',
|
||||
'tag 2',
|
||||
'tag 3',
|
||||
'tag 4',
|
||||
]);
|
||||
});
|
||||
|
||||
test('it should filter out anything that is not a tag', () => {
|
||||
const result1 = getRuleMock(getQueryRuleParams());
|
||||
result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result1.params.ruleId = 'rule-1';
|
||||
result1.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3'];
|
||||
|
||||
const result2 = getRuleMock(getQueryRuleParams());
|
||||
result2.id = '99979e67-19a7-455f-b452-8eded6135716';
|
||||
result2.params.ruleId = 'rule-2';
|
||||
// @ts-expect-error
|
||||
delete result2.tags;
|
||||
|
||||
const result3 = getRuleMock(getQueryRuleParams());
|
||||
result3.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d';
|
||||
result3.params.ruleId = 'rule-2';
|
||||
result3.tags = ['tag 1', 'tag 2', 'tag 2', 'tag 3', 'tag 4'];
|
||||
|
||||
const findResult = getFindResultWithMultiHits({ data: [result1, result2, result3] });
|
||||
const tags = convertToTags(findResult.data);
|
||||
expect(tags).toEqual([
|
||||
'tag 1',
|
||||
'tag 2',
|
||||
'tag 2',
|
||||
'tag 3',
|
||||
'tag 1',
|
||||
'tag 2',
|
||||
'tag 2',
|
||||
'tag 3',
|
||||
'tag 4',
|
||||
]);
|
||||
});
|
||||
|
||||
test('it should with with an empty array', () => {
|
||||
const tags = convertToTags([]);
|
||||
expect(tags).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isTags', () => {
|
||||
test('it should return true if the object has a tags on it', () => {
|
||||
expect(isTags({ tags: [] })).toBe(true);
|
||||
});
|
||||
|
||||
test('it should return false if the object does not have a tags on it', () => {
|
||||
expect(isTags({})).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,65 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { has } from 'lodash/fp';
|
||||
import type { RulesClient } from '@kbn/alerting-plugin/server';
|
||||
import { findRules } from '../../../logic/search/find_rules';
|
||||
import { enrichFilterWithRuleTypeMapping } from '../../../logic/search/enrich_filter_with_rule_type_mappings';
|
||||
|
||||
export interface TagType {
|
||||
id: string;
|
||||
tags: string[];
|
||||
}
|
||||
// This is a contrived max limit on the number of tags. In fact it can exceed this number and will be truncated to the hardcoded number.
|
||||
const EXPECTED_MAX_TAGS = 65536;
|
||||
|
||||
export const isTags = (obj: object): obj is TagType => {
|
||||
return has('tags', obj);
|
||||
};
|
||||
|
||||
export const convertToTags = (tagObjects: object[]): string[] => {
|
||||
const tags = tagObjects.reduce<string[]>((acc, tagObj) => {
|
||||
if (isTags(tagObj)) {
|
||||
return [...acc, ...tagObj.tags];
|
||||
} else {
|
||||
return acc;
|
||||
}
|
||||
}, []);
|
||||
return tags;
|
||||
};
|
||||
|
||||
export const convertTagsToSet = (tagObjects: object[]): Set<string> => {
|
||||
return new Set(convertToTags(tagObjects));
|
||||
};
|
||||
|
||||
// Note: This is doing an in-memory aggregation of the tags by calling each of the alerting
|
||||
// records in batches of this const setting and uses the fields to try to get the least
|
||||
// amount of data per record back. If saved objects at some point supports aggregations
|
||||
// then this should be replaced with a an aggregation call.
|
||||
// Ref: https://www.elastic.co/guide/en/kibana/master/saved-objects-api.html
|
||||
export const readTags = async ({
|
||||
rulesClient,
|
||||
}: {
|
||||
rulesClient: RulesClient;
|
||||
perPage?: number;
|
||||
}): Promise<string[]> => {
|
||||
// Get just one record so we can get the total count
|
||||
const firstTags = await findRules({
|
||||
rulesClient,
|
||||
fields: ['tags'],
|
||||
perPage: 1,
|
||||
page: 1,
|
||||
sortField: 'createdAt',
|
||||
sortOrder: 'desc',
|
||||
filter: undefined,
|
||||
const res = await rulesClient.aggregate({
|
||||
options: {
|
||||
fields: ['tags'],
|
||||
filter: enrichFilterWithRuleTypeMapping(undefined),
|
||||
maxTags: EXPECTED_MAX_TAGS,
|
||||
},
|
||||
});
|
||||
// Get all the rules to aggregate over all the tags of the rules
|
||||
const rules = await findRules({
|
||||
rulesClient,
|
||||
fields: ['tags'],
|
||||
perPage: firstTags.total,
|
||||
sortField: 'createdAt',
|
||||
sortOrder: 'desc',
|
||||
page: 1,
|
||||
filter: undefined,
|
||||
});
|
||||
const tagSet = convertTagsToSet(rules.data);
|
||||
return Array.from(tagSet);
|
||||
|
||||
return res.ruleTags ?? [];
|
||||
};
|
||||
|
|
|
@ -27772,7 +27772,6 @@
|
|||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Installation effectuée des règles prépackagées à partir d'Elastic",
|
||||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "Installation effectuée des modèles de chronologies prépackagées à partir d'Elastic",
|
||||
"xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "Impossible de récupérer les règles et les chronologies",
|
||||
"xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "Impossible de récupérer les balises",
|
||||
"xpack.securitySolution.contextMenuItemByRouter.viewDetails": "Afficher les détails",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudDropdownOption": "Charges de travail cloud (serveurs Linux ou environnements Kubernetes)",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersAllEvents": "Tous les événements",
|
||||
|
|
|
@ -27745,7 +27745,6 @@
|
|||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "Elastic から事前にパッケージ化されているルールをインストールしました",
|
||||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "Elasticから事前にパッケージ化されているタイムラインテンプレートをインストールしました",
|
||||
"xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "ルールとタイムラインを取得できませんでした",
|
||||
"xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "タグを取得できませんでした",
|
||||
"xpack.securitySolution.contextMenuItemByRouter.viewDetails": "詳細を表示",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudDropdownOption": "クラウドワークロード(LinuxサーバーまたはKubernetes環境)",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersAllEvents": "すべてのイベント",
|
||||
|
|
|
@ -27778,7 +27778,6 @@
|
|||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedRuleSuccesDescription": "已安装 Elastic 的预打包规则",
|
||||
"xpack.securitySolution.containers.detectionEngine.createPrePackagedTimelineSuccesDescription": "安装 Elastic 预先打包的时间线模板",
|
||||
"xpack.securitySolution.containers.detectionEngine.rulesAndTimelines": "无法提取规则和时间线",
|
||||
"xpack.securitySolution.containers.detectionEngine.tagFetchFailDescription": "无法提取标签",
|
||||
"xpack.securitySolution.contextMenuItemByRouter.viewDetails": "查看详情",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudDropdownOption": "云工作负载(Linux 服务器或 Kubernetes 环境)",
|
||||
"xpack.securitySolution.createPackagePolicy.stepConfigure.cloudEventFiltersAllEvents": "所有事件",
|
||||
|
|
|
@ -137,6 +137,35 @@ export default function createAggregateTests({ getService }: FtrProviderContext)
|
|||
});
|
||||
});
|
||||
|
||||
describe('tags limit', () => {
|
||||
it('should be 50 be default', async () => {
|
||||
const numOfAlerts = 3;
|
||||
const numOfTagsPerAlert = 30;
|
||||
|
||||
await Promise.all(
|
||||
[...Array(numOfAlerts)].map(async (_, alertIndex) => {
|
||||
const okAlertId = await createTestAlert(
|
||||
{
|
||||
rule_type_id: 'test.noop',
|
||||
schedule: { interval: '1s' },
|
||||
tags: [...Array(numOfTagsPerAlert)].map(
|
||||
(__, i) => `tag-${i + numOfTagsPerAlert * alertIndex}`
|
||||
),
|
||||
},
|
||||
'ok'
|
||||
);
|
||||
objectRemover.add(Spaces.space1.id, okAlertId, 'rule', 'alerting');
|
||||
})
|
||||
);
|
||||
|
||||
const response = await supertest.get(
|
||||
`${getUrlPrefix(Spaces.space1.id)}/internal/alerting/rules/_aggregate`
|
||||
);
|
||||
|
||||
expect(response.body.rule_tags.length).to.eql(50);
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should aggregate alert status totals', async () => {
|
||||
const NumOkAlerts = 4;
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 expect from '@kbn/expect';
|
||||
|
||||
import { DETECTION_ENGINE_RULES_URL } from '@kbn/security-solution-plugin/common/constants';
|
||||
import { RULE_MANAGEMENT_FILTERS_URL } from '@kbn/security-solution-plugin/common/detection_engine/rule_management/api/urls';
|
||||
import { PREBUILT_RULES_URL } from '@kbn/security-solution-plugin/common/detection_engine/prebuilt_rules';
|
||||
import { FtrProviderContext } from '../../common/ftr_provider_context';
|
||||
import { deleteAllAlerts, getSimpleRule } from '../../utils';
|
||||
import { createPrebuiltRuleAssetSavedObjects } from '../../utils/create_prebuilt_rule_saved_objects';
|
||||
import { deleteAllPrebuiltRules } from '../../utils/delete_all_prebuilt_rules';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default ({ getService }: FtrProviderContext): void => {
|
||||
const supertest = getService('supertest');
|
||||
const es = getService('es');
|
||||
const log = getService('log');
|
||||
|
||||
describe('get_rule_management_filters', () => {
|
||||
beforeEach(async () => {
|
||||
await deleteAllAlerts(supertest, log);
|
||||
});
|
||||
|
||||
it('should return the correct result when there are no rules', async () => {
|
||||
const { body } = await supertest
|
||||
.get(RULE_MANAGEMENT_FILTERS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body).to.eql({
|
||||
rules_summary: {
|
||||
custom_count: 0,
|
||||
prebuilt_installed_count: 0,
|
||||
},
|
||||
aggregated_fields: {
|
||||
tags: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there is a custom rule', () => {
|
||||
beforeEach(async () => {
|
||||
const rule = getSimpleRule();
|
||||
rule.tags = ['tag-a'];
|
||||
|
||||
await supertest
|
||||
.post(DETECTION_ENGINE_RULES_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(rule)
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
it('should return the correct number of custom rules', async () => {
|
||||
const { body } = await supertest
|
||||
.get(RULE_MANAGEMENT_FILTERS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.rules_summary.custom_count).to.eql(1);
|
||||
expect(body.rules_summary.prebuilt_installed_count).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return correct tags', async () => {
|
||||
const { body } = await supertest
|
||||
.get(RULE_MANAGEMENT_FILTERS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.aggregated_fields.tags).to.eql(['tag-a']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when there are installed prebuilt rules', () => {
|
||||
beforeEach(async () => {
|
||||
await createPrebuiltRuleAssetSavedObjects(es);
|
||||
await supertest.put(PREBUILT_RULES_URL).set('kbn-xsrf', 'true').send().expect(200);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteAllPrebuiltRules(es);
|
||||
});
|
||||
|
||||
it('should return the correct number of installed prepacked rules after pre-packaged rules have been installed', async () => {
|
||||
const { body } = await supertest
|
||||
.get(RULE_MANAGEMENT_FILTERS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.rules_summary.prebuilt_installed_count).to.eql(3);
|
||||
expect(body.rules_summary.custom_count).to.eql(0);
|
||||
});
|
||||
|
||||
it('should return installed prebuilt rules tags', async () => {
|
||||
const { body } = await supertest
|
||||
.get(RULE_MANAGEMENT_FILTERS_URL)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200);
|
||||
|
||||
expect(body.aggregated_fields.tags).to.eql(['test-tag-1', 'test-tag-2', 'test-tag-3']);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
|
@ -31,5 +31,6 @@ export default ({ loadTestFile }: FtrProviderContext): void => {
|
|||
loadTestFile(require.resolve('./find_rules'));
|
||||
loadTestFile(require.resolve('./find_rule_exception_references'));
|
||||
loadTestFile(require.resolve('./get_prepackaged_rules_status'));
|
||||
loadTestFile(require.resolve('./get_rule_management_filters'));
|
||||
});
|
||||
};
|
||||
|
|
|
@ -21,6 +21,7 @@ export const SAMPLE_PREBUILT_RULES = [
|
|||
'security-rule': {
|
||||
...getPrebuiltRuleWithExceptionsMock(),
|
||||
rule_id: ELASTIC_SECURITY_RULE_ID,
|
||||
tags: ['test-tag-1'],
|
||||
enabled: true,
|
||||
},
|
||||
type: 'security-rule',
|
||||
|
@ -33,6 +34,7 @@ export const SAMPLE_PREBUILT_RULES = [
|
|||
'security-rule': {
|
||||
...getPrebuiltRuleMock(),
|
||||
rule_id: '000047bb-b27a-47ec-8b62-ef1a5d2c9e19',
|
||||
tags: ['test-tag-2'],
|
||||
},
|
||||
type: 'security-rule',
|
||||
references: [],
|
||||
|
@ -44,6 +46,7 @@ export const SAMPLE_PREBUILT_RULES = [
|
|||
'security-rule': {
|
||||
...getPrebuiltRuleMock(),
|
||||
rule_id: '00140285-b827-4aee-aa09-8113f58a08f3',
|
||||
tags: ['test-tag-3'],
|
||||
},
|
||||
type: 'security-rule',
|
||||
references: [],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue