mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps][Rules] Create Rules APIs package (#214187)](https://github.com/elastic/kibana/pull/214187) <!--- Backport version: 9.6.6 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Umberto Pepato","email":"umbopepato@users.noreply.github.com"},"sourceCommit":{"committedDate":"2025-03-26T09:01:51Z","message":"[ResponseOps][Rules] Create Rules APIs package (#214187)\n\n## Summary\n\n- Creates a `@kbn/response-ops-rules-apis` package, following the\nproposed structure for ResponseOps Management Experiences package.\n- Moves relevant rules API fetchers and react-query hooks to the new\npackage.\n- Adds an internal variant of the `/api/alerting/rule_types` endpoint\n(`/internal/alerting/_rule_types`), that returns the same value as the\npublic one + the newly added internal [`solution`\nfield](https://github.com/elastic/kibana/issues/212017), that we don't\nwant to expose publicly.\n\n## Verification steps\n\n1. Create rules that fire alerts\n2. Verify the usages of the moved/changed hooks, with limited privileges\nas well (i.e. only `Rules Settings` but not `Stack Rules`):\n2.1. Stack management and Observability rules, rule details and alerts\npages\n2.2. Rules tab in the Connector editor flyout\n2.3. Alerts table row actions (••• icon)\n2.4. Tags filter in the rules list page\n3. Using the DevTools, compare the response of the public and internal\n`rule_types` endpoins:\n ```\n GET kbn:/api/alerting/rule_types\n GET kbn:/internal/alerting/_rule_types\n ```\nChecking that the `solution` field is present only in the internal one\n\n## References \n\nCloses #213059 \n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"7aac590af4e245049f3865ea23ed88a179a80a28","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","Team:obs-ux-management","backport:version","v9.1.0","v8.19.0"],"title":"[ResponseOps][Rules] Create Rules APIs package","number":214187,"url":"https://github.com/elastic/kibana/pull/214187","mergeCommit":{"message":"[ResponseOps][Rules] Create Rules APIs package (#214187)\n\n## Summary\n\n- Creates a `@kbn/response-ops-rules-apis` package, following the\nproposed structure for ResponseOps Management Experiences package.\n- Moves relevant rules API fetchers and react-query hooks to the new\npackage.\n- Adds an internal variant of the `/api/alerting/rule_types` endpoint\n(`/internal/alerting/_rule_types`), that returns the same value as the\npublic one + the newly added internal [`solution`\nfield](https://github.com/elastic/kibana/issues/212017), that we don't\nwant to expose publicly.\n\n## Verification steps\n\n1. Create rules that fire alerts\n2. Verify the usages of the moved/changed hooks, with limited privileges\nas well (i.e. only `Rules Settings` but not `Stack Rules`):\n2.1. Stack management and Observability rules, rule details and alerts\npages\n2.2. Rules tab in the Connector editor flyout\n2.3. Alerts table row actions (••• icon)\n2.4. Tags filter in the rules list page\n3. Using the DevTools, compare the response of the public and internal\n`rule_types` endpoins:\n ```\n GET kbn:/api/alerting/rule_types\n GET kbn:/internal/alerting/_rule_types\n ```\nChecking that the `solution` field is present only in the internal one\n\n## References \n\nCloses #213059 \n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"7aac590af4e245049f3865ea23ed88a179a80a28"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/214187","number":214187,"mergeCommit":{"message":"[ResponseOps][Rules] Create Rules APIs package (#214187)\n\n## Summary\n\n- Creates a `@kbn/response-ops-rules-apis` package, following the\nproposed structure for ResponseOps Management Experiences package.\n- Moves relevant rules API fetchers and react-query hooks to the new\npackage.\n- Adds an internal variant of the `/api/alerting/rule_types` endpoint\n(`/internal/alerting/_rule_types`), that returns the same value as the\npublic one + the newly added internal [`solution`\nfield](https://github.com/elastic/kibana/issues/212017), that we don't\nwant to expose publicly.\n\n## Verification steps\n\n1. Create rules that fire alerts\n2. Verify the usages of the moved/changed hooks, with limited privileges\nas well (i.e. only `Rules Settings` but not `Stack Rules`):\n2.1. Stack management and Observability rules, rule details and alerts\npages\n2.2. Rules tab in the Connector editor flyout\n2.3. Alerts table row actions (••• icon)\n2.4. Tags filter in the rules list page\n3. Using the DevTools, compare the response of the public and internal\n`rule_types` endpoins:\n ```\n GET kbn:/api/alerting/rule_types\n GET kbn:/internal/alerting/_rule_types\n ```\nChecking that the `solution` field is present only in the internal one\n\n## References \n\nCloses #213059 \n\n### Checklist\n\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n\n---------\n\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>","sha":"7aac590af4e245049f3865ea23ed88a179a80a28"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Alex Szabo <alex.szabo@elastic.co>
This commit is contained in:
parent
cd9ef106d5
commit
3fddba179f
136 changed files with 2559 additions and 975 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -765,6 +765,7 @@ src/platform/packages/shared/response-ops/alerts-fields-browser @elastic/respons
|
|||
src/platform/packages/shared/response-ops/alerts-table @elastic/response-ops
|
||||
src/platform/packages/shared/response-ops/rule_form @elastic/response-ops
|
||||
src/platform/packages/shared/response-ops/rule_params @elastic/response-ops
|
||||
src/platform/packages/shared/response-ops/rules-apis @elastic/response-ops
|
||||
examples/response_stream @elastic/ml-ui
|
||||
src/platform/packages/shared/kbn-rison @elastic/kibana-operations
|
||||
x-pack/platform/packages/private/rollup @elastic/kibana-management
|
||||
|
|
|
@ -777,6 +777,7 @@
|
|||
"@kbn/response-ops-alerts-table": "link:src/platform/packages/shared/response-ops/alerts-table",
|
||||
"@kbn/response-ops-rule-form": "link:src/platform/packages/shared/response-ops/rule_form",
|
||||
"@kbn/response-ops-rule-params": "link:src/platform/packages/shared/response-ops/rule_params",
|
||||
"@kbn/response-ops-rules-apis": "link:src/platform/packages/shared/response-ops/rules-apis",
|
||||
"@kbn/response-stream-plugin": "link:examples/response_stream",
|
||||
"@kbn/rison": "link:src/platform/packages/shared/kbn-rison",
|
||||
"@kbn/rollup": "link:x-pack/platform/packages/private/rollup",
|
||||
|
|
|
@ -15,6 +15,7 @@ import type {
|
|||
import type { Filter } from '@kbn/es-query';
|
||||
import type { RuleNotifyWhenType, RRuleParams } from '.';
|
||||
|
||||
export type RuleTypeSolution = 'observability' | 'security' | 'stack';
|
||||
export type RuleTypeParams = Record<string, unknown>;
|
||||
export type RuleActionParams = SavedObjectAttributes;
|
||||
export type RuleActionParam = SavedObjectAttribute;
|
||||
|
|
|
@ -290,5 +290,6 @@ function getAlertType(actionVariables: ActionVariables): RuleType {
|
|||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
category: 'my-category',
|
||||
isExportable: true,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -11,5 +11,5 @@ export * from './use_alerts_data_view';
|
|||
export * from './use_get_alerts_group_aggregations_query';
|
||||
export * from './use_health_check';
|
||||
export * from './use_load_alerting_framework_health';
|
||||
export * from './use_load_rule_types_query';
|
||||
export * from './use_get_rule_types_permissions';
|
||||
export * from './use_load_ui_health';
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { notificationServiceMock } from '@kbn/core/public/mocks';
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
import { useGetRuleTypesPermissions } from './use_get_rule_types_permissions';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { testQueryClientConfig } from '../test_utils/test_query_client_config';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
const { toasts } = notificationServiceMock.createStartContract();
|
||||
|
||||
jest.mock('@kbn/response-ops-rules-apis/apis/get_rule_types');
|
||||
const { getRuleTypes } = jest.requireMock('@kbn/response-ops-rules-apis/apis/get_rule_types');
|
||||
getRuleTypes.mockResolvedValue([
|
||||
{
|
||||
id: 'rule-type-1',
|
||||
authorizedConsumers: {},
|
||||
},
|
||||
{
|
||||
id: 'rule-type-2',
|
||||
authorizedConsumers: {},
|
||||
},
|
||||
]);
|
||||
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
const Wrapper = ({ children }: PropsWithChildren) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('useGetRuleTypesPermissions', () => {
|
||||
afterEach(() => {
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
it('should not filter the rule types if `filteredRuleTypes` and `registeredRuleTypes` are not defined', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(2);
|
||||
expect(result.current.authorizedRuleTypes.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should filter the rule types according to `filteredRuleTypes`', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
filteredRuleTypes: ['rule-type-1'],
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(1);
|
||||
expect(result.current.authorizedRuleTypes.length).toBe(1);
|
||||
expect(result.current.ruleTypesState.data.keys().next().value).toBe('rule-type-1');
|
||||
});
|
||||
|
||||
it('should filter out rule types not present in `registeredRuleTypes`', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
registeredRuleTypes: [{ id: 'rule-type-1', description: '' }],
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(1);
|
||||
expect(result.current.authorizedRuleTypes.length).toBe(1);
|
||||
expect(result.current.ruleTypesState.data.keys().next().value).toBe('rule-type-1');
|
||||
});
|
||||
|
||||
it('should return the correct authz flags when no rule types are accessible', async () => {
|
||||
getRuleTypes.mockResolvedValueOnce([]);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(0);
|
||||
expect(result.current.hasAnyAuthorizedRuleType).toBe(false);
|
||||
expect(result.current.authorizedToReadAnyRules).toBe(false);
|
||||
expect(result.current.authorizedToCreateAnyRules).toBe(false);
|
||||
});
|
||||
|
||||
it('should return the correct authz flags for read-only rule types', async () => {
|
||||
getRuleTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: 'rule-type-1',
|
||||
authorizedConsumers: { alerts: { read: true, all: false } },
|
||||
},
|
||||
]);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(1);
|
||||
expect(result.current.hasAnyAuthorizedRuleType).toBe(true);
|
||||
expect(result.current.authorizedToReadAnyRules).toBe(true);
|
||||
expect(result.current.authorizedToCreateAnyRules).toBe(false);
|
||||
});
|
||||
|
||||
it('should return the correct authz flags for read+write rule types', async () => {
|
||||
getRuleTypes.mockResolvedValueOnce([
|
||||
{
|
||||
id: 'rule-type-1',
|
||||
authorizedConsumers: { alerts: { read: true, all: true } },
|
||||
},
|
||||
]);
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
enabled: true,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
await waitFor(() => expect(result.current.isSuccess).toBeTruthy());
|
||||
expect(result.current.ruleTypesState.data.size).toBe(1);
|
||||
expect(result.current.hasAnyAuthorizedRuleType).toBe(true);
|
||||
expect(result.current.authorizedToReadAnyRules).toBe(true);
|
||||
expect(result.current.authorizedToCreateAnyRules).toBe(true);
|
||||
});
|
||||
});
|
|
@ -9,19 +9,19 @@
|
|||
|
||||
import { useMemo } from 'react';
|
||||
import { keyBy } from 'lodash';
|
||||
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { ToastsStart } from '@kbn/core-notifications-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { RuleType } from '@kbn/triggers-actions-ui-types';
|
||||
import {
|
||||
RuleTypeIndexWithDescriptions,
|
||||
RuleTypeWithDescription,
|
||||
} from '@kbn/triggers-actions-ui-types';
|
||||
import { fetchRuleTypes } from '../apis/fetch_rule_types';
|
||||
import { useGetRuleTypesQuery } from '@kbn/response-ops-rules-apis/hooks/use_get_rule_types_query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { ALERTS_FEATURE_ID } from '../constants';
|
||||
|
||||
export interface UseRuleTypesProps {
|
||||
export interface UseGetRuleTypesPermissionsParams {
|
||||
http: HttpStart;
|
||||
toasts: ToastsStart;
|
||||
filteredRuleTypes?: string[];
|
||||
|
@ -37,7 +37,7 @@ const getFilteredIndex = ({
|
|||
}: {
|
||||
data: Array<RuleType<string, string>>;
|
||||
filteredRuleTypes?: string[];
|
||||
registeredRuleTypes: UseRuleTypesProps['registeredRuleTypes'];
|
||||
registeredRuleTypes: UseGetRuleTypesPermissionsParams['registeredRuleTypes'];
|
||||
}) => {
|
||||
const index: RuleTypeIndexWithDescriptions = new Map();
|
||||
const registeredRuleTypesDictionary = registeredRuleTypes ? keyBy(registeredRuleTypes, 'id') : {};
|
||||
|
@ -63,19 +63,15 @@ const getFilteredIndex = ({
|
|||
return filteredIndex;
|
||||
};
|
||||
|
||||
export const useLoadRuleTypesQuery = ({
|
||||
export const useGetRuleTypesPermissions = ({
|
||||
http,
|
||||
toasts,
|
||||
filteredRuleTypes,
|
||||
registeredRuleTypes,
|
||||
context,
|
||||
enabled = true,
|
||||
}: UseRuleTypesProps) => {
|
||||
const queryFn = () => {
|
||||
return fetchRuleTypes({ http });
|
||||
};
|
||||
|
||||
const onErrorFn = (error: Error) => {
|
||||
}: UseGetRuleTypesPermissionsParams) => {
|
||||
const onErrorFn = (error: unknown) => {
|
||||
if (error) {
|
||||
toasts.addDanger(
|
||||
i18n.translate('alertsUIShared.hooks.useLoadRuleTypesQuery.unableToLoadRuleTypesMessage', {
|
||||
|
@ -84,17 +80,15 @@ export const useLoadRuleTypesQuery = ({
|
|||
);
|
||||
}
|
||||
};
|
||||
const { data, isSuccess, isFetching, isInitialLoading, isLoading, error } = useQuery({
|
||||
queryKey: ['loadRuleTypes'],
|
||||
queryFn,
|
||||
onError: onErrorFn,
|
||||
refetchOnWindowFocus: false,
|
||||
// Leveraging TanStack Query's caching system to avoid duplicated requests as
|
||||
// other state-sharing solutions turned out to be overly complex and less readable
|
||||
staleTime: 60 * 1000,
|
||||
enabled,
|
||||
context,
|
||||
});
|
||||
|
||||
const { data, isSuccess, isFetching, isInitialLoading, isLoading, error } = useGetRuleTypesQuery(
|
||||
{ http },
|
||||
{
|
||||
onError: onErrorFn,
|
||||
enabled,
|
||||
context,
|
||||
}
|
||||
);
|
||||
|
||||
const filteredIndex = useMemo(
|
||||
() =>
|
|
@ -35,5 +35,6 @@
|
|||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/shared-ux-table-persist",
|
||||
"@kbn/presentation-publishing",
|
||||
"@kbn/response-ops-rules-apis",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ export interface RuleType<
|
|||
| 'defaultScheduleInterval'
|
||||
| 'doesSetRecoveryContext'
|
||||
| 'category'
|
||||
| 'isExportable'
|
||||
> {
|
||||
actionVariables: ActionVariables;
|
||||
authorizedConsumers: Record<string, { read: boolean; all: boolean }>;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { getMutedAlertsInstancesByRule } from './get_muted_alerts_instances_by_rule';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { muteAlertInstance } from './mute_alert_instance';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import { BASE_ALERTING_API_PATH } from '../constants';
|
||||
|
||||
export interface MuteAlertInstanceParams {
|
||||
id: string;
|
||||
instanceId: string;
|
||||
http: HttpSetup;
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
export const muteAlertInstance = ({ id, instanceId, http }: MuteAlertInstanceParams) => {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { unmuteAlertInstance } from './unmute_alert_instance';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import { BASE_ALERTING_API_PATH } from '../constants';
|
||||
|
||||
export interface UnmuteAlertInstanceParams {
|
||||
id: string;
|
||||
instanceId: string;
|
||||
http: HttpSetup;
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
export const unmuteAlertInstance = ({ id, instanceId, http }: UnmuteAlertInstanceParams) => {
|
||||
|
|
|
@ -8,15 +8,4 @@
|
|||
*/
|
||||
|
||||
export const BASE_ALERTING_API_PATH = '/api/alerting';
|
||||
|
||||
export const queryKeys = {
|
||||
root: 'alerts',
|
||||
mutedAlerts: (ruleIds: string[]) =>
|
||||
[queryKeys.root, 'mutedInstanceIdsForRuleIds', ruleIds] as const,
|
||||
};
|
||||
|
||||
export const mutationKeys = {
|
||||
root: 'alerts',
|
||||
muteAlertInstance: () => [mutationKeys.root, 'muteAlertInstance'] as const,
|
||||
unmuteAlertInstance: () => [mutationKeys.root, 'unmuteAlertInstance'] as const,
|
||||
};
|
||||
export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting';
|
||||
|
|
|
@ -13,7 +13,7 @@ import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/al
|
|||
import { QueryOptionsOverrides } from '@kbn/alerts-ui-shared/src/common/types/tanstack_query_utility_types';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import { queryKeys } from '../constants';
|
||||
import { queryKeys } from '../query_keys';
|
||||
import { MutedAlerts, ServerError } from '../types';
|
||||
import {
|
||||
getMutedAlertsInstancesByRule,
|
||||
|
@ -38,13 +38,15 @@ export interface UseGetMutedAlertsQueryParams {
|
|||
notifications: NotificationsStart;
|
||||
}
|
||||
|
||||
export const getKey = queryKeys.getMutedAlerts;
|
||||
|
||||
export const useGetMutedAlertsQuery = (
|
||||
{ ruleIds, http, notifications: { toasts } }: UseGetMutedAlertsQueryParams,
|
||||
{ enabled }: QueryOptionsOverrides<typeof getMutedAlerts> = {}
|
||||
) => {
|
||||
return useQuery({
|
||||
context: AlertsQueryContext,
|
||||
queryKey: queryKeys.mutedAlerts(ruleIds),
|
||||
queryKey: getKey(ruleIds),
|
||||
queryFn: ({ signal }) => getMutedAlerts({ http, signal, ruleIds }),
|
||||
onError: (error: ServerError) => {
|
||||
if (error.name !== 'AbortError') {
|
||||
|
|
|
@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import { mutationKeys } from '../constants';
|
||||
import { mutationKeys } from '../mutation_keys';
|
||||
import type { ServerError, ToggleAlertParams } from '../types';
|
||||
import { muteAlertInstance } from '../apis/mute_alert_instance';
|
||||
|
||||
|
@ -25,6 +25,8 @@ export interface UseMuteAlertInstanceParams {
|
|||
notifications: NotificationsStart;
|
||||
}
|
||||
|
||||
export const getKey = mutationKeys.muteAlertInstance;
|
||||
|
||||
export const useMuteAlertInstance = ({
|
||||
http,
|
||||
notifications: { toasts },
|
||||
|
@ -33,7 +35,7 @@ export const useMuteAlertInstance = ({
|
|||
({ ruleId, alertInstanceId }: ToggleAlertParams) =>
|
||||
muteAlertInstance({ http, id: ruleId, instanceId: alertInstanceId }),
|
||||
{
|
||||
mutationKey: mutationKeys.muteAlertInstance(),
|
||||
mutationKey: getKey(),
|
||||
context: AlertsQueryContext,
|
||||
onSuccess() {
|
||||
toasts.addSuccess(
|
||||
|
|
|
@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
|
|||
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { NotificationsStart } from '@kbn/core-notifications-browser';
|
||||
import { mutationKeys } from '../constants';
|
||||
import { mutationKeys } from '../mutation_keys';
|
||||
import type { ServerError, ToggleAlertParams } from '../types';
|
||||
import { unmuteAlertInstance } from '../apis/unmute_alert_instance';
|
||||
|
||||
|
@ -25,6 +25,8 @@ export interface UseUnmuteAlertInstanceParams {
|
|||
notifications: NotificationsStart;
|
||||
}
|
||||
|
||||
export const getKey = mutationKeys.unmuteAlertInstance;
|
||||
|
||||
export const useUnmuteAlertInstance = ({
|
||||
http,
|
||||
notifications: { toasts },
|
||||
|
@ -33,7 +35,7 @@ export const useUnmuteAlertInstance = ({
|
|||
({ ruleId, alertInstanceId }: ToggleAlertParams) =>
|
||||
unmuteAlertInstance({ http, id: ruleId, instanceId: alertInstanceId }),
|
||||
{
|
||||
mutationKey: mutationKeys.unmuteAlertInstance(),
|
||||
mutationKey: getKey(),
|
||||
context: AlertsQueryContext,
|
||||
onSuccess() {
|
||||
toasts.addSuccess(
|
||||
|
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const mutationKeys = {
|
||||
root: 'alerts',
|
||||
muteAlertInstance: () => [mutationKeys.root, 'muteAlertInstance'] as const,
|
||||
unmuteAlertInstance: () => [mutationKeys.root, 'unmuteAlertInstance'] as const,
|
||||
};
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const queryKeys = {
|
||||
root: 'alerts',
|
||||
getMutedAlerts: (ruleIds: string[]) =>
|
||||
[queryKeys.root, 'mutedInstanceIdsForRuleIds', ruleIds] as const,
|
||||
};
|
|
@ -15,7 +15,6 @@
|
|||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core",
|
||||
"@kbn/core-http-browser",
|
||||
"@kbn/i18n",
|
||||
"@kbn/alerts-ui-shared",
|
||||
|
|
|
@ -43,7 +43,7 @@ import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/al
|
|||
import deepEqual from 'fast-deep-equal';
|
||||
import { Alert } from '@kbn/alerting-types';
|
||||
import { useGetMutedAlertsQuery } from '@kbn/response-ops-alerts-apis/hooks/use_get_muted_alerts_query';
|
||||
import { queryKeys as alertsQueryKeys } from '@kbn/response-ops-alerts-apis/constants';
|
||||
import { queryKeys as alertsQueryKeys } from '@kbn/response-ops-alerts-apis/query_keys';
|
||||
import { ErrorFallback } from './error_fallback';
|
||||
import { defaultAlertsTableColumns } from '../configuration';
|
||||
import { Storage } from '../utils/storage';
|
||||
|
@ -357,7 +357,7 @@ const AlertsTableContent = typedForwardRef(
|
|||
refetchAlerts();
|
||||
}
|
||||
queryClient.invalidateQueries(queryKeys.casesBulkGet(caseIds));
|
||||
queryClient.invalidateQueries(alertsQueryKeys.mutedAlerts(ruleIds));
|
||||
queryClient.invalidateQueries(alertsQueryKeys.getMutedAlerts(ruleIds));
|
||||
queryClient.invalidateQueries(queryKeys.maintenanceWindowsBulkGet(maintenanceWindowIds));
|
||||
}, [caseIds, maintenanceWindowIds, queryClient, queryParams.pageIndex, refetchAlerts, ruleIds]);
|
||||
|
||||
|
|
|
@ -16,9 +16,8 @@ import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
|
|||
import { createPartialObjectMock } from '../utils/test';
|
||||
import { AlertsTableContextProvider } from '../contexts/alerts_table_context';
|
||||
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_load_rule_types_query', () => ({
|
||||
useLoadRuleTypesQuery: jest.fn(),
|
||||
}));
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions');
|
||||
|
||||
jest.mock('./view_rule_details_alert_action', () => {
|
||||
return {
|
||||
ViewRuleDetailsAlertAction: () => (
|
||||
|
@ -44,8 +43,8 @@ jest.mock('./mark_as_untracked_alert_action', () => {
|
|||
};
|
||||
});
|
||||
|
||||
const { useLoadRuleTypesQuery } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_load_rule_types_query'
|
||||
const { useGetRuleTypesPermissions } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions'
|
||||
);
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
@ -70,7 +69,7 @@ const TestComponent = (_props: AlertActionsProps) => (
|
|||
|
||||
describe('DefaultAlertActions', () => {
|
||||
it('should show "Mute" and "Marked as untracked" option', async () => {
|
||||
useLoadRuleTypesQuery.mockReturnValue({ authorizedToCreateAnyRules: true });
|
||||
useGetRuleTypesPermissions.mockReturnValue({ authorizedToCreateAnyRules: true });
|
||||
|
||||
render(<TestComponent {...props} />);
|
||||
|
||||
|
@ -79,7 +78,7 @@ describe('DefaultAlertActions', () => {
|
|||
});
|
||||
|
||||
it('should hide "Mute" and "Marked as untracked" option', async () => {
|
||||
useLoadRuleTypesQuery.mockReturnValue({ authorizedToCreateAnyRules: false });
|
||||
useGetRuleTypesPermissions.mockReturnValue({ authorizedToCreateAnyRules: false });
|
||||
|
||||
render(<TestComponent {...props} />);
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { useLoadRuleTypesQuery } from '@kbn/alerts-ui-shared/src/common/hooks';
|
||||
import { useGetRuleTypesPermissions } from '@kbn/alerts-ui-shared/src/common/hooks';
|
||||
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
|
||||
import { ViewRuleDetailsAlertAction } from './view_rule_details_alert_action';
|
||||
import type { AdditionalContext, AlertActionsProps } from '../types';
|
||||
|
@ -29,7 +29,7 @@ export const DefaultAlertActions = <AC extends AdditionalContext = AdditionalCon
|
|||
notifications: { toasts },
|
||||
},
|
||||
} = useAlertsTableContext();
|
||||
const { authorizedToCreateAnyRules } = useLoadRuleTypesQuery({
|
||||
const { authorizedToCreateAnyRules } = useGetRuleTypesPermissions({
|
||||
filteredRuleTypes: [],
|
||||
http,
|
||||
toasts,
|
||||
|
|
|
@ -29,6 +29,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
default_schedule_interval: defaultScheduleInterval,
|
||||
has_alerts_mappings: hasAlertsMappings,
|
||||
has_fields_for_a_a_d: hasFieldsForAAD,
|
||||
is_exportable: isExportable,
|
||||
...rest
|
||||
}: AsApiContract<RuleType>) => ({
|
||||
enabledInLicense,
|
||||
|
@ -43,6 +44,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
defaultScheduleInterval,
|
||||
hasAlertsMappings,
|
||||
hasFieldsForAAD,
|
||||
isExportable,
|
||||
...rest,
|
||||
});
|
||||
|
||||
|
|
|
@ -29,8 +29,8 @@ jest.mock('../common/hooks/use_resolve_rule', () => ({
|
|||
useResolveRule: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_load_rule_types_query', () => ({
|
||||
useLoadRuleTypesQuery: jest.fn(),
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions', () => ({
|
||||
useGetRuleTypesPermissions: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../common/hooks/use_load_connectors', () => ({
|
||||
|
@ -59,8 +59,8 @@ const { useLoadConnectorTypes } = jest.requireMock('../common/hooks/use_load_con
|
|||
const { useLoadRuleTypeAadTemplateField } = jest.requireMock(
|
||||
'../common/hooks/use_load_rule_type_aad_template_fields'
|
||||
);
|
||||
const { useLoadRuleTypesQuery } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_load_rule_types_query'
|
||||
const { useGetRuleTypesPermissions } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions'
|
||||
);
|
||||
const { useFetchFlappingSettings } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_fetch_flapping_settings'
|
||||
|
@ -158,7 +158,7 @@ const ruleTypeIndex = new Map();
|
|||
|
||||
ruleTypeIndex.set('.index-threshold', indexThresholdRuleType);
|
||||
|
||||
useLoadRuleTypesQuery.mockReturnValue({
|
||||
useGetRuleTypesPermissions.mockReturnValue({
|
||||
ruleTypesState: {
|
||||
isLoading: false,
|
||||
isInitialLoading: false,
|
||||
|
@ -278,7 +278,7 @@ describe('useLoadDependencies', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('should call useLoadRuleTypesQuery with fitlered rule types', async () => {
|
||||
test('should call useGetRuleTypesPermissions with filtered rule types', async () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
return useLoadDependencies({
|
||||
|
@ -302,7 +302,7 @@ describe('useLoadDependencies', () => {
|
|||
return expect(result.current.isInitialLoading).toEqual(false);
|
||||
});
|
||||
|
||||
expect(useLoadRuleTypesQuery).toBeCalledWith({
|
||||
expect(useGetRuleTypesPermissions).toBeCalledWith({
|
||||
http: httpMock,
|
||||
toasts: toastsMock,
|
||||
filteredRuleTypes: ['test-rule-type'],
|
||||
|
|
|
@ -14,7 +14,7 @@ import type { RuleCreationValidConsumer } from '@kbn/rule-data-utils';
|
|||
import { useMemo } from 'react';
|
||||
import {
|
||||
useHealthCheck,
|
||||
useLoadRuleTypesQuery,
|
||||
useGetRuleTypesPermissions,
|
||||
useFetchFlappingSettings,
|
||||
} from '@kbn/alerts-ui-shared';
|
||||
import type { FieldsMetadataPublicStart } from '@kbn/fields-metadata-plugin/public';
|
||||
|
@ -81,7 +81,7 @@ export const useLoadDependencies = (props: UseLoadDependencies) => {
|
|||
isLoading: isLoadingRuleTypes,
|
||||
isInitialLoad: isInitialLoadingRuleTypes,
|
||||
},
|
||||
} = useLoadRuleTypesQuery({
|
||||
} = useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
filteredRuleTypes,
|
||||
|
|
|
@ -27,6 +27,7 @@ const mockRuleType: (
|
|||
actionGroups: [],
|
||||
defaultActionGroupId: 'default',
|
||||
category: 'my-category',
|
||||
isExportable: true,
|
||||
});
|
||||
|
||||
const pickRuleTypeName = (ruleType: RuleTypeWithDescription) => ({ name: ruleType.name });
|
||||
|
|
|
@ -11,7 +11,7 @@ import { countBy } from 'lodash';
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { ToastsStart } from '@kbn/core-notifications-browser';
|
||||
import { useLoadRuleTypesQuery } from '@kbn/alerts-ui-shared';
|
||||
import { useGetRuleTypesPermissions } from '@kbn/alerts-ui-shared';
|
||||
import { RuleTypeModal, type RuleTypeModalProps } from './rule_type_modal';
|
||||
import { filterAndCountRuleTypes } from './helpers/filter_and_count_rule_types';
|
||||
|
||||
|
@ -38,7 +38,7 @@ export const RuleTypeModalComponent: React.FC<RuleTypeModalComponentProps> = ({
|
|||
|
||||
const {
|
||||
ruleTypesState: { data: ruleTypeIndex, isLoading: ruleTypesLoading },
|
||||
} = useLoadRuleTypesQuery({
|
||||
} = useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
filteredRuleTypes,
|
||||
|
|
|
@ -32,6 +32,7 @@ const ruleTypes: RuleTypeWithDescription[] = [
|
|||
},
|
||||
defaultActionGroupId: '1',
|
||||
category: 'my-category-1',
|
||||
isExportable: true,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -51,6 +52,7 @@ const ruleTypes: RuleTypeWithDescription[] = [
|
|||
},
|
||||
defaultActionGroupId: '2',
|
||||
category: 'my-category-2',
|
||||
isExportable: true,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
@ -70,6 +72,7 @@ const ruleTypes: RuleTypeWithDescription[] = [
|
|||
},
|
||||
defaultActionGroupId: '3',
|
||||
category: 'my-category-3',
|
||||
isExportable: true,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ describe('getInitialMultiConsumer', () => {
|
|||
},
|
||||
enabledInLicense: true,
|
||||
category: 'test',
|
||||
isExportable: true,
|
||||
} as RuleTypeWithDescription;
|
||||
|
||||
const ruleTypes = [
|
||||
|
@ -100,6 +101,7 @@ describe('getInitialMultiConsumer', () => {
|
|||
name: 'Index threshold',
|
||||
category: 'management',
|
||||
producer: 'stackAlerts',
|
||||
isExportable: true,
|
||||
},
|
||||
] as RuleTypeWithDescription[];
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# @kbn/response-ops-rules-apis
|
||||
|
||||
Client-side Rules HTTP API fetchers and React Query wrappers.
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { getInternalRuleTypes } from './get_internal_rule_types';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
const ruleTypeResponse = {
|
||||
id: '1',
|
||||
name: 'name',
|
||||
action_groups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
action_variables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
alerts: [],
|
||||
authorized_consumers: {},
|
||||
category: 'test',
|
||||
default_action_group_id: 'default',
|
||||
default_schedule_interval: '10m',
|
||||
does_set_recovery_context: false,
|
||||
enabled_in_license: true,
|
||||
has_alerts_mappings: true,
|
||||
has_fields_for_a_a_d: false,
|
||||
is_exportable: true,
|
||||
minimum_license_required: 'basic',
|
||||
producer: 'test',
|
||||
solution: 'stack',
|
||||
recovery_action_group: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
rule_task_timeout: '10m',
|
||||
};
|
||||
|
||||
const expectedRuleType = {
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
alerts: [],
|
||||
authorizedConsumers: {},
|
||||
category: 'test',
|
||||
defaultActionGroupId: 'default',
|
||||
defaultScheduleInterval: '10m',
|
||||
doesSetRecoveryContext: false,
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: true,
|
||||
hasFieldsForAAD: false,
|
||||
isExportable: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: 'test',
|
||||
solution: 'stack',
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
ruleTaskTimeout: '10m',
|
||||
};
|
||||
|
||||
describe('getInternalRuleTypes', () => {
|
||||
it('should call the internal rule types API and transform the response case', async () => {
|
||||
http.get.mockResolvedValueOnce([ruleTypeResponse]);
|
||||
|
||||
const result = await getInternalRuleTypes({
|
||||
http,
|
||||
});
|
||||
|
||||
expect(result).toEqual([expectedRuleType]);
|
||||
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/internal/alerting/_rule_types",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { AsApiContract, RewriteRequestCase } from '@kbn/actions-types';
|
||||
import type { RuleType } from '@kbn/triggers-actions-ui-types';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { RuleTypeSolution } from '@kbn/alerting-types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../constants';
|
||||
|
||||
export interface InternalRuleType extends RuleType<string, string> {
|
||||
solution: RuleTypeSolution;
|
||||
}
|
||||
|
||||
const rewriteResponse = (results: Array<AsApiContract<InternalRuleType>>): InternalRuleType[] => {
|
||||
return results.map((item) => rewriteRuleType(item));
|
||||
};
|
||||
|
||||
const rewriteRuleType: RewriteRequestCase<InternalRuleType> = ({
|
||||
enabled_in_license: enabledInLicense,
|
||||
recovery_action_group: recoveryActionGroup,
|
||||
action_groups: actionGroups,
|
||||
default_action_group_id: defaultActionGroupId,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
action_variables: actionVariables,
|
||||
authorized_consumers: authorizedConsumers,
|
||||
rule_task_timeout: ruleTaskTimeout,
|
||||
does_set_recovery_context: doesSetRecoveryContext,
|
||||
default_schedule_interval: defaultScheduleInterval,
|
||||
has_alerts_mappings: hasAlertsMappings,
|
||||
has_fields_for_a_a_d: hasFieldsForAAD,
|
||||
is_exportable: isExportable,
|
||||
...rest
|
||||
}: AsApiContract<InternalRuleType>) => ({
|
||||
enabledInLicense,
|
||||
recoveryActionGroup,
|
||||
actionGroups,
|
||||
defaultActionGroupId,
|
||||
minimumLicenseRequired,
|
||||
actionVariables,
|
||||
authorizedConsumers,
|
||||
ruleTaskTimeout,
|
||||
doesSetRecoveryContext,
|
||||
defaultScheduleInterval,
|
||||
hasAlertsMappings,
|
||||
hasFieldsForAAD,
|
||||
isExportable,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export async function getInternalRuleTypes({ http }: { http: HttpStart }) {
|
||||
const res = await http.get<Array<AsApiContract<InternalRuleType>>>(
|
||||
`${INTERNAL_BASE_ALERTING_API_PATH}/_rule_types`
|
||||
);
|
||||
return rewriteResponse(res);
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { getRuleTags } from './get_rule_tags';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
describe('getRuleTags', () => {
|
||||
it('should call the getTags API', async () => {
|
||||
const resolvedValue = {
|
||||
data: ['a', 'b', 'c'],
|
||||
total: 3,
|
||||
page: 2,
|
||||
per_page: 30,
|
||||
};
|
||||
http.get.mockResolvedValueOnce(resolvedValue);
|
||||
|
||||
const result = await getRuleTags({
|
||||
http,
|
||||
search: 'test',
|
||||
page: 2,
|
||||
perPage: 30,
|
||||
ruleTypeIds: ['test-rule-type'],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
data: ['a', 'b', 'c'],
|
||||
page: 2,
|
||||
perPage: 30,
|
||||
total: 3,
|
||||
});
|
||||
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/internal/alerting/rules/_tags",
|
||||
Object {
|
||||
"query": Object {
|
||||
"page": 2,
|
||||
"per_page": 30,
|
||||
"rule_type_ids": Array [
|
||||
"test-rule-type",
|
||||
],
|
||||
"search": "test",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { AsApiContract, RewriteRequestCase } from '@kbn/actions-types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../constants';
|
||||
|
||||
export interface GetRuleTagsParams {
|
||||
// Params
|
||||
search?: string;
|
||||
ruleTypeIds?: string[];
|
||||
perPage?: number;
|
||||
page: number;
|
||||
|
||||
// Services
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
export interface GetRuleTagsResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
data: string[];
|
||||
}
|
||||
|
||||
export const rewriteTagsBodyRes: RewriteRequestCase<GetRuleTagsResponse> = ({
|
||||
per_page: perPage,
|
||||
...rest
|
||||
}) => ({
|
||||
perPage,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export async function getRuleTags({
|
||||
http,
|
||||
search,
|
||||
ruleTypeIds,
|
||||
perPage,
|
||||
page,
|
||||
}: GetRuleTagsParams): Promise<GetRuleTagsResponse> {
|
||||
const res = await http.get<AsApiContract<GetRuleTagsResponse>>(
|
||||
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_tags`,
|
||||
{
|
||||
query: {
|
||||
search,
|
||||
per_page: perPage,
|
||||
page,
|
||||
rule_type_ids: ruleTypeIds,
|
||||
},
|
||||
}
|
||||
);
|
||||
return rewriteTagsBodyRes(res);
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { getRuleTypes } from './get_rule_types';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
const ruleTypeResponse = {
|
||||
id: '1',
|
||||
name: 'name',
|
||||
action_groups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
action_variables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
alerts: [],
|
||||
authorized_consumers: {},
|
||||
category: 'test',
|
||||
default_action_group_id: 'default',
|
||||
default_schedule_interval: '10m',
|
||||
does_set_recovery_context: false,
|
||||
enabled_in_license: true,
|
||||
has_alerts_mappings: true,
|
||||
has_fields_for_a_a_d: false,
|
||||
is_exportable: true,
|
||||
minimum_license_required: 'basic',
|
||||
producer: 'test',
|
||||
recovery_action_group: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
rule_task_timeout: '10m',
|
||||
};
|
||||
|
||||
const expectedRuleType = {
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
alerts: [],
|
||||
authorizedConsumers: {},
|
||||
category: 'test',
|
||||
defaultActionGroupId: 'default',
|
||||
defaultScheduleInterval: '10m',
|
||||
doesSetRecoveryContext: false,
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: true,
|
||||
hasFieldsForAAD: false,
|
||||
isExportable: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
producer: 'test',
|
||||
recoveryActionGroup: {
|
||||
id: 'recovered',
|
||||
name: 'Recovered',
|
||||
},
|
||||
ruleTaskTimeout: '10m',
|
||||
};
|
||||
|
||||
describe('getRuleTypes', () => {
|
||||
it('should call the rule types API and transform the response case', async () => {
|
||||
http.get.mockResolvedValueOnce([ruleTypeResponse]);
|
||||
|
||||
const result = await getRuleTypes({
|
||||
http,
|
||||
});
|
||||
|
||||
expect(result).toEqual([expectedRuleType]);
|
||||
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/api/alerting/rule_types",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -7,16 +7,16 @@
|
|||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { AsApiContract, RewriteRequestCase } from '@kbn/actions-types';
|
||||
import type { RuleType } from '@kbn/triggers-actions-ui-types';
|
||||
import { BASE_ALERTING_API_PATH } from '../constants';
|
||||
|
||||
const rewriteResponseRes = (results: Array<AsApiContract<RuleType>>): RuleType[] => {
|
||||
return results.map((item) => rewriteBodyReq(item));
|
||||
const rewriteResponse = (results: Array<AsApiContract<RuleType>>): RuleType[] => {
|
||||
return results.map((item) => rewriteRuleType(item));
|
||||
};
|
||||
|
||||
const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
||||
const rewriteRuleType: RewriteRequestCase<RuleType> = ({
|
||||
enabled_in_license: enabledInLicense,
|
||||
recovery_action_group: recoveryActionGroup,
|
||||
action_groups: actionGroups,
|
||||
|
@ -29,6 +29,7 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
default_schedule_interval: defaultScheduleInterval,
|
||||
has_alerts_mappings: hasAlertsMappings,
|
||||
has_fields_for_a_a_d: hasFieldsForAAD,
|
||||
is_exportable: isExportable,
|
||||
...rest
|
||||
}: AsApiContract<RuleType>) => ({
|
||||
enabledInLicense,
|
||||
|
@ -43,12 +44,13 @@ const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
|||
defaultScheduleInterval,
|
||||
hasAlertsMappings,
|
||||
hasFieldsForAAD,
|
||||
isExportable,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export async function fetchRuleTypes({ http }: { http: HttpSetup }): Promise<RuleType[]> {
|
||||
export async function getRuleTypes({ http }: { http: HttpStart }): Promise<RuleType[]> {
|
||||
const res = await http.get<Array<AsApiContract<RuleType<string, string>>>>(
|
||||
`${BASE_ALERTING_API_PATH}/rule_types`
|
||||
);
|
||||
return rewriteResponseRes(res);
|
||||
return rewriteResponse(res);
|
||||
}
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const BASE_ALERTING_API_PATH = '/api/alerting';
|
||||
export const INTERNAL_BASE_ALERTING_API_PATH = '/internal/alerting';
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { waitFor, renderHook } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useGetInternalRuleTypesQuery } from './use_get_internal_rule_types_query';
|
||||
import { InternalRuleType, getInternalRuleTypes } from '../apis/get_internal_rule_types';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { testQueryClientConfig } from '../test_utils';
|
||||
|
||||
const mockInternalRuleTypes = [
|
||||
{ id: 'a' },
|
||||
{ id: 'b' },
|
||||
{ id: 'c' },
|
||||
] as unknown as InternalRuleType[];
|
||||
|
||||
jest.mock('../apis/get_internal_rule_types');
|
||||
const mockGetInternalRuleTypes = jest.mocked(getInternalRuleTypes);
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
|
||||
export const Wrapper = ({ children }: PropsWithChildren) => {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
};
|
||||
|
||||
describe('useGetInternalRuleTypesQuery', () => {
|
||||
beforeEach(() => {
|
||||
mockGetInternalRuleTypes.mockResolvedValue(mockInternalRuleTypes);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
queryClient.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the getInternalRuleTypes API', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetInternalRuleTypesQuery({
|
||||
http,
|
||||
}),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
expect(mockGetInternalRuleTypes).toHaveBeenCalled();
|
||||
expect(result.current.data).toEqual(mockInternalRuleTypes);
|
||||
});
|
||||
});
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getInternalRuleTypes } from '../apis/get_internal_rule_types';
|
||||
import { queryKeys } from '../query_keys';
|
||||
|
||||
export const getKey = queryKeys.getInternalRuleTypes;
|
||||
|
||||
export const useGetInternalRuleTypesQuery = ({ http }: { http: HttpStart }) => {
|
||||
return useQuery({
|
||||
queryKey: getKey(),
|
||||
queryFn: () => getInternalRuleTypes({ http }),
|
||||
});
|
||||
};
|
|
@ -1,44 +1,38 @@
|
|||
/*
|
||||
* 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.
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
import { waitFor, renderHook } from '@testing-library/react';
|
||||
import { useLoadTagsQuery } from './use_load_tags_query';
|
||||
import { useGetRuleTagsQuery } from './use_get_rule_tags_query';
|
||||
import { getRuleTags } from '../apis/get_rule_tags';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { IToasts } from '@kbn/core-notifications-browser';
|
||||
import { testQueryClientConfig } from '../test_utils';
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
|
||||
const MOCK_TAGS = ['a', 'b', 'c'];
|
||||
|
||||
jest.mock('../../common/lib/kibana');
|
||||
jest.mock('../lib/rule_api/aggregate', () => ({
|
||||
loadRuleTags: jest.fn(),
|
||||
}));
|
||||
jest.mock('../apis/get_rule_tags');
|
||||
const mockGetRuleTags = jest.mocked(getRuleTags);
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||
const { loadRuleTags } = jest.requireMock('../lib/rule_api/aggregate');
|
||||
const http = httpServiceMock.createStartContract();
|
||||
const notifications = notificationServiceMock.createStartContract();
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
cacheTime: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
|
||||
describe('useLoadTagsQuery', () => {
|
||||
export const Wrapper = ({ children }: PropsWithChildren) => {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
};
|
||||
|
||||
describe('useGetRuleTagsQuery', () => {
|
||||
beforeEach(() => {
|
||||
useKibanaMock().services.notifications.toasts = {
|
||||
addDanger: jest.fn(),
|
||||
} as unknown as IToasts;
|
||||
loadRuleTags.mockResolvedValue({
|
||||
mockGetRuleTags.mockResolvedValue({
|
||||
data: MOCK_TAGS,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
|
@ -51,23 +45,25 @@ describe('useLoadTagsQuery', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call loadRuleTags API and handle result', async () => {
|
||||
it('should call the getRuleTags API and collect the tags into one array', async () => {
|
||||
const { rerender, result } = renderHook(
|
||||
() =>
|
||||
useLoadTagsQuery({
|
||||
useGetRuleTagsQuery({
|
||||
http,
|
||||
toasts: notifications.toasts,
|
||||
enabled: true,
|
||||
search: 'test',
|
||||
perPage: 50,
|
||||
page: 1,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
rerender();
|
||||
await waitFor(() => {
|
||||
expect(loadRuleTags).toHaveBeenLastCalledWith(
|
||||
expect(mockGetRuleTags).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
search: 'test',
|
||||
perPage: 50,
|
||||
|
@ -81,7 +77,7 @@ describe('useLoadTagsQuery', () => {
|
|||
});
|
||||
|
||||
it('should support pagination', async () => {
|
||||
loadRuleTags.mockResolvedValue({
|
||||
mockGetRuleTags.mockResolvedValue({
|
||||
data: ['a', 'b', 'c', 'd', 'e'],
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
|
@ -89,19 +85,21 @@ describe('useLoadTagsQuery', () => {
|
|||
});
|
||||
const { rerender, result } = renderHook(
|
||||
() =>
|
||||
useLoadTagsQuery({
|
||||
useGetRuleTagsQuery({
|
||||
http,
|
||||
toasts: notifications.toasts,
|
||||
enabled: true,
|
||||
perPage: 5,
|
||||
page: 1,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
rerender();
|
||||
await waitFor(() => {
|
||||
expect(loadRuleTags).toHaveBeenLastCalledWith(
|
||||
expect(mockGetRuleTags).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
perPage: 5,
|
||||
page: 1,
|
||||
|
@ -112,7 +110,7 @@ describe('useLoadTagsQuery', () => {
|
|||
expect(result.current.hasNextPage).toEqual(true);
|
||||
});
|
||||
|
||||
loadRuleTags.mockResolvedValue({
|
||||
mockGetRuleTags.mockResolvedValue({
|
||||
data: ['a', 'b', 'c', 'd', 'e'],
|
||||
page: 2,
|
||||
perPage: 5,
|
||||
|
@ -121,7 +119,7 @@ describe('useLoadTagsQuery', () => {
|
|||
|
||||
result.current.fetchNextPage();
|
||||
|
||||
expect(loadRuleTags).toHaveBeenLastCalledWith(
|
||||
expect(mockGetRuleTags).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
perPage: 5,
|
||||
page: 2,
|
||||
|
@ -133,7 +131,7 @@ describe('useLoadTagsQuery', () => {
|
|||
});
|
||||
|
||||
it('should support pagination when there are no tags', async () => {
|
||||
loadRuleTags.mockResolvedValue({
|
||||
mockGetRuleTags.mockResolvedValue({
|
||||
data: [],
|
||||
page: 1,
|
||||
perPage: 5,
|
||||
|
@ -142,19 +140,21 @@ describe('useLoadTagsQuery', () => {
|
|||
|
||||
const { rerender, result } = renderHook(
|
||||
() =>
|
||||
useLoadTagsQuery({
|
||||
useGetRuleTagsQuery({
|
||||
http,
|
||||
toasts: notifications.toasts,
|
||||
enabled: true,
|
||||
perPage: 5,
|
||||
page: 1,
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
rerender();
|
||||
await waitFor(() => {
|
||||
expect(loadRuleTags).toHaveBeenLastCalledWith(
|
||||
expect(mockGetRuleTags).toHaveBeenLastCalledWith(
|
||||
expect.objectContaining({
|
||||
perPage: 5,
|
||||
page: 1,
|
||||
|
@ -167,14 +167,20 @@ describe('useLoadTagsQuery', () => {
|
|||
});
|
||||
|
||||
it('should call onError if API fails', async () => {
|
||||
loadRuleTags.mockRejectedValue('');
|
||||
mockGetRuleTags.mockRejectedValue('');
|
||||
|
||||
const { result } = renderHook(() => useLoadTagsQuery({ enabled: true }), { wrapper });
|
||||
|
||||
expect(loadRuleTags).toBeCalled();
|
||||
expect(result.current.tags).toEqual([]);
|
||||
await waitFor(() =>
|
||||
expect(useKibanaMock().services.notifications.toasts.addDanger).toBeCalled()
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTagsQuery({
|
||||
http,
|
||||
toasts: notifications.toasts,
|
||||
enabled: true,
|
||||
}),
|
||||
{ wrapper: Wrapper }
|
||||
);
|
||||
|
||||
expect(mockGetRuleTags).toBeCalled();
|
||||
expect(result.current.tags).toEqual([]);
|
||||
await waitFor(() => expect(notifications.toasts.addDanger).toBeCalled());
|
||||
});
|
||||
});
|
|
@ -1,58 +1,58 @@
|
|||
/*
|
||||
* 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.
|
||||
* or more contributor license agreements. Licensed under the "Elastic License
|
||||
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { loadRuleTags } from '../lib/rule_api/aggregate';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import type { LoadRuleTagsProps } from '../lib/rule_api';
|
||||
import type { GetRuleTagsResponse } from '../lib/rule_api/aggregate_helpers';
|
||||
import type { ToastsStart } from '@kbn/core-notifications-browser';
|
||||
import type { SetOptional } from 'type-fest';
|
||||
import type { GetRuleTagsParams, GetRuleTagsResponse } from '../apis/get_rule_tags';
|
||||
import { getRuleTags } from '../apis/get_rule_tags';
|
||||
import { queryKeys } from '../query_keys';
|
||||
|
||||
interface UseLoadTagsQueryProps {
|
||||
enabled: boolean;
|
||||
interface UseGetRuleTagsQueryParams extends SetOptional<GetRuleTagsParams, 'page'> {
|
||||
// Params
|
||||
refresh?: Date;
|
||||
search?: string;
|
||||
perPage?: number;
|
||||
page?: number;
|
||||
enabled: boolean;
|
||||
|
||||
// Services
|
||||
toasts: ToastsStart;
|
||||
}
|
||||
|
||||
const EMPTY_TAGS: string[] = [];
|
||||
|
||||
export const getKey = queryKeys.getRuleTags;
|
||||
|
||||
// React query will refetch all prev pages when the cache keys change:
|
||||
// https://github.com/TanStack/query/discussions/3576
|
||||
export function useLoadTagsQuery(props: UseLoadTagsQueryProps) {
|
||||
const { enabled, refresh, search, perPage, page = 1 } = props;
|
||||
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const queryFn = ({ pageParam }: { pageParam?: LoadRuleTagsProps }) => {
|
||||
if (pageParam) {
|
||||
return loadRuleTags({
|
||||
http,
|
||||
perPage: pageParam.perPage,
|
||||
page: pageParam.page,
|
||||
search,
|
||||
});
|
||||
}
|
||||
return loadRuleTags({
|
||||
export function useGetRuleTagsQuery({
|
||||
enabled,
|
||||
refresh,
|
||||
search,
|
||||
ruleTypeIds,
|
||||
perPage,
|
||||
page = 1,
|
||||
http,
|
||||
toasts,
|
||||
}: UseGetRuleTagsQueryParams) {
|
||||
const queryFn = ({ pageParam }: { pageParam?: GetRuleTagsParams }) =>
|
||||
getRuleTags({
|
||||
http,
|
||||
perPage,
|
||||
page,
|
||||
perPage: pageParam?.perPage ?? perPage,
|
||||
page: pageParam?.page ?? page,
|
||||
search,
|
||||
ruleTypeIds,
|
||||
});
|
||||
};
|
||||
|
||||
const onErrorFn = () => {
|
||||
toasts.addDanger(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTags', {
|
||||
i18n.translate('responseOpsRulesApis.unableToLoadRuleTags', {
|
||||
defaultMessage: 'Unable to load rule tags',
|
||||
})
|
||||
);
|
||||
|
@ -71,15 +71,13 @@ export function useLoadTagsQuery(props: UseLoadTagsQueryProps) {
|
|||
|
||||
const { refetch, data, fetchNextPage, isLoading, isFetching, hasNextPage, isFetchingNextPage } =
|
||||
useInfiniteQuery({
|
||||
queryKey: [
|
||||
'loadRuleTags',
|
||||
queryKey: getKey({
|
||||
ruleTypeIds,
|
||||
search,
|
||||
perPage,
|
||||
page,
|
||||
{
|
||||
refresh: refresh?.toISOString(),
|
||||
},
|
||||
],
|
||||
refresh,
|
||||
}),
|
||||
queryFn,
|
||||
onError: onErrorFn,
|
||||
enabled,
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import React, { PropsWithChildren } from 'react';
|
||||
import { waitFor, renderHook } from '@testing-library/react';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { useGetRuleTypesQuery } from './use_get_rule_types_query';
|
||||
import type { RuleType } from '@kbn/triggers-actions-ui-types';
|
||||
import { getRuleTypes } from '../apis/get_rule_types';
|
||||
import { httpServiceMock } from '@kbn/core-http-browser-mocks';
|
||||
import { testQueryClientConfig } from '../test_utils';
|
||||
|
||||
const mockRuleTypes = [{ id: 'a' }, { id: 'b' }, { id: 'c' }] as unknown as RuleType[];
|
||||
|
||||
jest.mock('../apis/get_rule_types');
|
||||
const mockGetRuleTypes = jest.mocked(getRuleTypes);
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
|
||||
export const Wrapper = ({ children }: PropsWithChildren) => {
|
||||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
|
||||
};
|
||||
|
||||
describe('useGetRuleTypesQuery', () => {
|
||||
beforeEach(() => {
|
||||
mockGetRuleTypes.mockResolvedValue(mockRuleTypes);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
queryClient.clear();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should call the getRuleTypes API', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useGetRuleTypesQuery(
|
||||
{
|
||||
http,
|
||||
},
|
||||
{ enabled: true }
|
||||
),
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.isLoading).toBe(false));
|
||||
expect(mockGetRuleTypes).toHaveBeenCalled();
|
||||
expect(result.current.data).toEqual(mockRuleTypes);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
import type { HttpStart } from '@kbn/core-http-browser';
|
||||
import type { UseQueryOptions } from '@tanstack/react-query';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { getRuleTypes } from '../apis/get_rule_types';
|
||||
import { queryKeys } from '../query_keys';
|
||||
|
||||
export interface GetRuleTypesQueryParams {
|
||||
http: HttpStart;
|
||||
}
|
||||
|
||||
export const getKey = queryKeys.getRuleTypes;
|
||||
|
||||
export const useGetRuleTypesQuery = (
|
||||
{ http }: GetRuleTypesQueryParams,
|
||||
{
|
||||
onError,
|
||||
enabled,
|
||||
context,
|
||||
}: Pick<UseQueryOptions<typeof getRuleTypes>, 'onError' | 'enabled' | 'context'>
|
||||
) => {
|
||||
return useQuery({
|
||||
queryKey: getKey(),
|
||||
queryFn: () => getRuleTypes({ http }),
|
||||
refetchOnWindowFocus: false,
|
||||
// Leveraging TanStack Query's caching system to avoid duplicated requests as
|
||||
// other state-sharing solutions turned out to be overly complex and less readable
|
||||
staleTime: 60 * 1000,
|
||||
onError,
|
||||
enabled,
|
||||
context,
|
||||
});
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../../../../../..',
|
||||
roots: ['<rootDir>/src/platform/packages/shared/response-ops/rules-apis'],
|
||||
setupFilesAfterEnv: [
|
||||
'<rootDir>/src/platform/packages/shared/response-ops/rules-apis/setup_tests.ts',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "shared-browser",
|
||||
"id": "@kbn/response-ops-rules-apis",
|
||||
"owner": "@elastic/response-ops",
|
||||
"group": "platform",
|
||||
"visibility": "shared"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const mutationKeys = {
|
||||
root: 'rules',
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/response-ops-rules-apis",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0"
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
|
||||
export const queryKeys = {
|
||||
root: 'rules',
|
||||
getRuleTags: ({
|
||||
ruleTypeIds,
|
||||
search,
|
||||
perPage,
|
||||
page,
|
||||
refresh,
|
||||
}: {
|
||||
ruleTypeIds?: string[];
|
||||
search?: string;
|
||||
perPage?: number;
|
||||
page: number;
|
||||
refresh?: Date;
|
||||
}) =>
|
||||
[
|
||||
queryKeys.root,
|
||||
'getRuleTags',
|
||||
ruleTypeIds,
|
||||
search,
|
||||
perPage,
|
||||
page,
|
||||
{
|
||||
refresh: refresh?.toISOString(),
|
||||
},
|
||||
] as const,
|
||||
getRuleTypes: () => [queryKeys.root, 'getRuleTypes'] as const,
|
||||
getInternalRuleTypes: () => [queryKeys.root, 'getInternalRuleTypes'] as const,
|
||||
};
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import '@testing-library/jest-dom';
|
||||
import 'jest-styled-components';
|
|
@ -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", the "GNU Affero General Public License v3.0 only", and the "Server Side
|
||||
* Public License v 1"; you may not use this file except in compliance with, at
|
||||
* your election, the "Elastic License 2.0", the "GNU Affero General Public
|
||||
* License v3.0 only", or the "Server Side Public License, v 1".
|
||||
*/
|
||||
/* eslint-disable no-console */
|
||||
|
||||
export const testQueryClientConfig = {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
logger: {
|
||||
log: console.log,
|
||||
warn: console.warn,
|
||||
error: () => {},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"extends": "../../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core-http-browser-mocks",
|
||||
"@kbn/core-http-browser",
|
||||
"@kbn/actions-types",
|
||||
"@kbn/i18n",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-notifications-browser-mocks",
|
||||
"@kbn/triggers-actions-ui-types",
|
||||
"@kbn/alerting-types",
|
||||
]
|
||||
}
|
|
@ -1524,6 +1524,8 @@
|
|||
"@kbn/response-ops-rule-form/*": ["src/platform/packages/shared/response-ops/rule_form/*"],
|
||||
"@kbn/response-ops-rule-params": ["src/platform/packages/shared/response-ops/rule_params"],
|
||||
"@kbn/response-ops-rule-params/*": ["src/platform/packages/shared/response-ops/rule_params/*"],
|
||||
"@kbn/response-ops-rules-apis": ["src/platform/packages/shared/response-ops/rules-apis"],
|
||||
"@kbn/response-ops-rules-apis/*": ["src/platform/packages/shared/response-ops/rules-apis/*"],
|
||||
"@kbn/response-stream-plugin": ["examples/response_stream"],
|
||||
"@kbn/response-stream-plugin/*": ["examples/response_stream/*"],
|
||||
"@kbn/rison": ["src/platform/packages/shared/kbn-rison"],
|
||||
|
|
|
@ -35,13 +35,6 @@ const { loadActionTypes } = jest.requireMock(
|
|||
'@kbn/triggers-actions-ui-plugin/public/application/lib/action_connector_api'
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'@kbn/triggers-actions-ui-plugin/public/application/hooks/use_load_rule_types_query',
|
||||
() => ({
|
||||
useLoadRuleTypesQuery: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public/ui_settings/use_ui_setting', () => ({
|
||||
useUiSetting: jest.fn().mockImplementation((_, defaultValue) => defaultValue),
|
||||
}));
|
||||
|
|
|
@ -50307,8 +50307,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadConnectorTypesMessage": "Impossible de charger les types de connecteurs",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRulesMessage": "Impossible de charger les règles",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleStatusInfoMessage": "Impossible de charger les infos de statut de règles",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTags": "Impossible de charger les balises de règle",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTypesMessage": "Impossible de charger les types de règles",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToRunRuleSoon": "Impossible de programmer l'exécution de votre règle",
|
||||
"xpack.triggersActionsUI.sections.rulesList.viewBannerButtonLabel": "Afficher {totalStatusesError, plural, one {la règle} other {les règles}} comportant des erreurs",
|
||||
"xpack.triggersActionsUI.sections.rulesList.weeksLabel": "semaines",
|
||||
|
|
|
@ -50267,8 +50267,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadConnectorTypesMessage": "コネクタータイプを読み込めません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRulesMessage": "ルールを読み込めません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleStatusInfoMessage": "ルールステータス情報を読み込めません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTags": "ルールタグを読み込めません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTypesMessage": "ルールタイプを読み込めません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToRunRuleSoon": "ルールの実行をスケジュールできません",
|
||||
"xpack.triggersActionsUI.sections.rulesList.viewBannerButtonLabel": "エラーがある{totalStatusesError, plural, other {個のルール}}を表示",
|
||||
"xpack.triggersActionsUI.sections.rulesList.weeksLabel": "週",
|
||||
|
|
|
@ -50350,8 +50350,6 @@
|
|||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadConnectorTypesMessage": "无法加载连接器类型",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRulesMessage": "无法加载规则",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleStatusInfoMessage": "无法加载规则状态信息",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTags": "无法加载规则标签",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTypesMessage": "无法加载规则类型",
|
||||
"xpack.triggersActionsUI.sections.rulesList.unableToRunRuleSoon": "无法计划您的要运行的规则",
|
||||
"xpack.triggersActionsUI.sections.rulesList.viewBannerButtonLabel": "显示包含错误的{totalStatusesError, plural, other {规则}}",
|
||||
"xpack.triggersActionsUI.sections.rulesList.weeksLabel": "周",
|
||||
|
|
191
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/list_types/external/schemas/v1.ts
vendored
Normal file
191
x-pack/platform/plugins/shared/alerting/common/routes/rule/apis/list_types/external/schemas/v1.ts
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* 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';
|
||||
|
||||
const actionVariableSchema = schema.object({
|
||||
name: schema.string(),
|
||||
description: schema.string(),
|
||||
usesPublicBaseUrl: schema.maybe(schema.boolean()),
|
||||
});
|
||||
|
||||
const actionGroupSchema = schema.object(
|
||||
{
|
||||
id: schema.string(),
|
||||
name: schema.string(),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
description:
|
||||
'An action group to use when an alert goes from an active state to an inactive one.',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const typesRulesSchema = schema.object({
|
||||
action_groups: schema.maybe(
|
||||
schema.arrayOf(actionGroupSchema, {
|
||||
meta: {
|
||||
description:
|
||||
"An explicit list of groups for which the rule type can schedule actions, each with the action group's unique ID and human readable name. Rule actions validation uses this configuration to ensure that groups are valid.",
|
||||
},
|
||||
})
|
||||
),
|
||||
action_variables: schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
context: schema.maybe(schema.arrayOf(actionVariableSchema)),
|
||||
state: schema.maybe(schema.arrayOf(actionVariableSchema)),
|
||||
params: schema.maybe(schema.arrayOf(actionVariableSchema)),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
description:
|
||||
'A list of action variables that the rule type makes available via context and state in action parameter templates, and a short human readable description. When you create a rule in Kibana, it uses this information to prompt you for these variables in action parameter editors.',
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
alerts: schema.maybe(
|
||||
schema.object(
|
||||
{
|
||||
context: schema.string({
|
||||
meta: {
|
||||
description: 'The namespace for this rule type.',
|
||||
},
|
||||
}),
|
||||
mappings: schema.maybe(
|
||||
schema.object({
|
||||
dynamic: schema.maybe(
|
||||
schema.oneOf([schema.literal(false), schema.literal('strict')], {
|
||||
meta: {
|
||||
description: 'Indicates whether new fields are added dynamically.',
|
||||
},
|
||||
})
|
||||
),
|
||||
fieldMap: schema.recordOf(schema.string(), schema.any(), {
|
||||
meta: {
|
||||
description:
|
||||
'Mapping information for each field supported in alerts as data documents for this rule type. For more information about mapping parameters, refer to the Elasticsearch documentation.',
|
||||
},
|
||||
}),
|
||||
shouldWrite: schema.maybe(
|
||||
schema.boolean({
|
||||
meta: {
|
||||
description: 'Indicates whether the rule should write out alerts as data.',
|
||||
},
|
||||
})
|
||||
),
|
||||
useEcs: schema.maybe(
|
||||
schema.boolean({
|
||||
meta: {
|
||||
description:
|
||||
'Indicates whether to include the ECS component template for the alerts.',
|
||||
},
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
description: 'Details for writing alerts as data documents for this rule type.',
|
||||
},
|
||||
}
|
||||
)
|
||||
),
|
||||
authorized_consumers: schema.recordOf(
|
||||
schema.string(),
|
||||
schema.object({ read: schema.boolean(), all: schema.boolean() }),
|
||||
{
|
||||
meta: {
|
||||
description: 'The list of the plugins IDs that have access to the rule type.',
|
||||
},
|
||||
}
|
||||
),
|
||||
category: schema.string({
|
||||
meta: {
|
||||
description:
|
||||
'The rule category, which is used by features such as category-specific maintenance windows.',
|
||||
},
|
||||
}),
|
||||
default_action_group_id: schema.string({
|
||||
meta: {
|
||||
description: 'The default identifier for the rule type group.',
|
||||
},
|
||||
}),
|
||||
default_schedule_interval: schema.maybe(schema.string()),
|
||||
does_set_recovery_context: schema.maybe(
|
||||
schema.boolean({
|
||||
meta: {
|
||||
description: 'Indicates whether the rule passes context variables to its recovery action.',
|
||||
},
|
||||
})
|
||||
),
|
||||
enabled_in_license: schema.boolean({
|
||||
meta: {
|
||||
description:
|
||||
'Indicates whether the rule type is enabled or disabled based on the subscription.',
|
||||
},
|
||||
}),
|
||||
fieldsForAAD: schema.maybe(schema.arrayOf(schema.string())),
|
||||
has_alerts_mappings: schema.boolean({
|
||||
meta: {
|
||||
description: 'Indicates whether the rule type has custom mappings for the alert data.',
|
||||
},
|
||||
}),
|
||||
has_fields_for_a_a_d: schema.boolean({
|
||||
meta: {
|
||||
description:
|
||||
'Indicates whether the rule type has fields for alert as data for the alert data. ',
|
||||
},
|
||||
}),
|
||||
id: schema.string({
|
||||
meta: {
|
||||
description: 'The unique identifier for the rule type.',
|
||||
},
|
||||
}),
|
||||
is_exportable: schema.boolean({
|
||||
meta: {
|
||||
description:
|
||||
'Indicates whether the rule type is exportable in Stack Management > Saved Objects.',
|
||||
},
|
||||
}),
|
||||
minimum_license_required: schema.oneOf(
|
||||
[
|
||||
schema.literal('basic'),
|
||||
schema.literal('gold'),
|
||||
schema.literal('platinum'),
|
||||
schema.literal('standard'),
|
||||
schema.literal('enterprise'),
|
||||
schema.literal('trial'),
|
||||
],
|
||||
{
|
||||
meta: {
|
||||
description: 'The subscriptions required to use the rule type.',
|
||||
},
|
||||
}
|
||||
),
|
||||
name: schema.string({
|
||||
meta: {
|
||||
description: 'The descriptive name of the rule type.',
|
||||
},
|
||||
}),
|
||||
producer: schema.string({
|
||||
meta: {
|
||||
description: 'An identifier for the application that produces this rule type.',
|
||||
},
|
||||
}),
|
||||
recovery_action_group: actionGroupSchema,
|
||||
rule_task_timeout: schema.maybe(schema.string()),
|
||||
});
|
||||
|
||||
export const typesRulesResponseBodySchema = schema.arrayOf(typesRulesSchema);
|
||||
|
||||
export const typesRulesResponseSchema = schema.object({
|
||||
body: typesRulesResponseBodySchema,
|
||||
});
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 {
|
||||
getRuleTypesInternalResponseSchema,
|
||||
getRuleTypesInternalResponseBodySchema,
|
||||
} from './schemas/latest';
|
||||
export type {
|
||||
GetRuleTypesInternalResponse,
|
||||
GetRuleTypesInternalResponseBody,
|
||||
} from './types/latest';
|
||||
|
||||
export {
|
||||
getRuleTypesInternalResponseSchema as getRuleTypesInternalResponseSchemaV1,
|
||||
getRuleTypesInternalResponseBodySchema as getRuleTypesInternalResponseBodySchemaV1,
|
||||
} from './schemas/v1';
|
||||
export type {
|
||||
GetRuleTypesInternalResponse as GetRuleTypesInternalV1,
|
||||
GetRuleTypesInternalResponseBody as GetRuleTypesInternalResponseBodyV1,
|
||||
} from './types/v1';
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { typesRulesSchema } from '../../external/schemas/v1';
|
||||
|
||||
export const getRuleTypesInternalResponseBodySchema = schema.arrayOf(
|
||||
typesRulesSchema.extends({
|
||||
solution: schema.oneOf(
|
||||
[schema.literal('stack'), schema.literal('observability'), schema.literal('security')],
|
||||
{
|
||||
meta: {
|
||||
description: 'An identifier for the solution that owns this rule type.',
|
||||
},
|
||||
}
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
export const getRuleTypesInternalResponseSchema = schema.object({
|
||||
body: getRuleTypesInternalResponseBodySchema,
|
||||
});
|
|
@ -5,4 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { ruleTypesRoute } from './rule_types';
|
||||
export * from './v1';
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { TypeOf } from '@kbn/config-schema';
|
||||
|
||||
import type {
|
||||
getRuleTypesInternalResponseSchemaV1,
|
||||
getRuleTypesInternalResponseBodySchemaV1,
|
||||
} from '..';
|
||||
|
||||
export type GetRuleTypesInternalResponse = TypeOf<typeof getRuleTypesInternalResponseSchemaV1>;
|
||||
export type GetRuleTypesInternalResponseBody = TypeOf<
|
||||
typeof getRuleTypesInternalResponseBodySchemaV1
|
||||
>;
|
|
@ -11,7 +11,7 @@ export const ruleTagsRequestQuerySchema = schema.object({
|
|||
page: schema.number({ defaultValue: 1, min: 1 }),
|
||||
per_page: schema.maybe(schema.number({ defaultValue: DEFAULT_TAGS_PER_PAGEV1, min: 1 })),
|
||||
search: schema.maybe(schema.string()),
|
||||
rule_type_ids: schema.maybe(schema.arrayOf(schema.string())),
|
||||
rule_type_ids: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])),
|
||||
});
|
||||
|
||||
export const ruleTagsFormattedResponseSchema = schema.object({
|
||||
|
|
|
@ -31,7 +31,8 @@ import { getRuleExecutionKPIRoute } from './get_rule_execution_kpi';
|
|||
import { getRuleStateRoute } from './get_rule_state';
|
||||
import { healthRoute } from './framework/apis/health';
|
||||
import { resolveRuleRoute } from './rule/apis/resolve';
|
||||
import { ruleTypesRoute } from './rule/apis/list_types/rule_types';
|
||||
import { getRuleTypesRoute } from './rule/apis/list_types/external/get_rule_types_route';
|
||||
import { getRuleTypesInternalRoute } from './rule/apis/list_types/internal/get_rule_types_internal_route';
|
||||
import { muteAllRuleRoute } from './rule/apis/mute_all/mute_all_rule';
|
||||
import { muteAlertRoute } from './rule/apis/mute_alert/mute_alert';
|
||||
import { unmuteAllRuleRoute } from './rule/apis/unmute_all';
|
||||
|
@ -78,6 +79,7 @@ import { findGapsRoute } from './gaps/apis/find/find_gaps_route';
|
|||
import { fillGapByIdRoute } from './gaps/apis/fill/fill_gap_by_id_route';
|
||||
import { getRuleIdsWithGapsRoute } from './gaps/apis/get_rule_ids_with_gaps/get_rule_ids_with_gaps_route';
|
||||
import { getGapsSummaryByRuleIdsRoute } from './gaps/apis/get_gaps_summary_by_rule_ids/get_gaps_summary_by_rule_ids_route';
|
||||
|
||||
export interface RouteOptions {
|
||||
router: IRouter<AlertingRequestHandlerContext>;
|
||||
licenseState: ILicenseState;
|
||||
|
@ -118,7 +120,8 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
getRuleExecutionLogRoute(router, licenseState);
|
||||
getRuleExecutionKPIRoute(router, licenseState);
|
||||
getRuleStateRoute(router, licenseState);
|
||||
ruleTypesRoute(router, licenseState);
|
||||
getRuleTypesRoute(router, licenseState);
|
||||
getRuleTypesInternalRoute(router, licenseState);
|
||||
muteAllRuleRoute(router, licenseState, usageCounter);
|
||||
unmuteAllRuleRoute(router, licenseState);
|
||||
updateRuleApiKeyRoute(router, licenseState);
|
||||
|
|
|
@ -5,19 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ruleTypesRoute } from './rule_types';
|
||||
import { getRuleTypesRoute } from './get_rule_types_route';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../rules_client.mock';
|
||||
import { RecoveredActionGroup } from '../../../../../common';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../authorization';
|
||||
import type { AsApiContract } from '../../../lib';
|
||||
import { licenseStateMock } from '../../../../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../../../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../../rules_client.mock';
|
||||
import { RecoveredActionGroup } from '../../../../../../common';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../../authorization';
|
||||
import type { AsApiContract } from '../../../../lib';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../../../lib/license_api_access', () => ({
|
||||
jest.mock('../../../../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
|
@ -30,7 +30,7 @@ describe('ruleTypesRoute', () => {
|
|||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
ruleTypesRoute(router, licenseState);
|
||||
getRuleTypesRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -150,7 +150,7 @@ describe('ruleTypesRoute', () => {
|
|||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
ruleTypesRoute(router, licenseState);
|
||||
getRuleTypesRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
|
@ -208,7 +208,7 @@ describe('ruleTypesRoute', () => {
|
|||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
ruleTypesRoute(router, licenseState);
|
||||
getRuleTypesRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
|
@ -6,16 +6,16 @@
|
|||
*/
|
||||
|
||||
import type { IRouter } from '@kbn/core/server';
|
||||
import type { TypesRulesResponseBodyV1 } from '../../../../../common/routes/rule/apis/list_types';
|
||||
import { typesRulesResponseSchemaV1 } from '../../../../../common/routes/rule/apis/list_types';
|
||||
import type { ILicenseState } from '../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../types';
|
||||
import { BASE_ALERTING_API_PATH } from '../../../../types';
|
||||
import type { TypesRulesResponseBodyV1 } from '../../../../../../common/routes/rule/apis/list_types/external';
|
||||
import { typesRulesResponseSchemaV1 } from '../../../../../../common/routes/rule/apis/list_types/external';
|
||||
import type { ILicenseState } from '../../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../../types';
|
||||
import { BASE_ALERTING_API_PATH } from '../../../../../types';
|
||||
import { transformRuleTypesResponseV1 } from './transforms';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../constants';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../../constants';
|
||||
|
||||
export const ruleTypesRoute = (
|
||||
export const getRuleTypesRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
8
x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/list_types/external/index.ts
vendored
Normal file
8
x-pack/platform/plugins/shared/alerting/server/routes/rule/apis/list_types/external/index.ts
vendored
Normal file
|
@ -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 { getRuleTypesRoute } from './get_rule_types_route';
|
|
@ -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 * from './v1';
|
|
@ -6,8 +6,8 @@
|
|||
*/
|
||||
|
||||
import { isBoolean } from 'lodash/fp';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../../../authorization';
|
||||
import type { TypesRulesResponseBodyV1 } from '../../../../../../../common/routes/rule/apis/list_types';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../../../../authorization';
|
||||
import type { TypesRulesResponseBodyV1 } from '../../../../../../../../common/routes/rule/apis/list_types/external';
|
||||
|
||||
export const transformRuleTypesResponse = (
|
||||
ruleTypes: RegistryAlertTypeWithAuth[]
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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 { getRuleTypesInternalRoute } from './get_rule_types_internal_route';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../../../../../lib/license_state.mock';
|
||||
import { verifyApiAccess } from '../../../../../lib/license_api_access';
|
||||
import { mockHandlerArguments } from '../../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../../rules_client.mock';
|
||||
import { RecoveredActionGroup } from '../../../../../../common';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../../authorization';
|
||||
import type { AsApiContract } from '../../../../lib';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
|
||||
jest.mock('../../../../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('internalRuleTypesRoute', () => {
|
||||
it('lists rule types with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getRuleTypesInternalRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/_rule_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
ruleTaskTimeout: '10m',
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'test',
|
||||
solution: 'stack',
|
||||
enabledInLicense: true,
|
||||
defaultScheduleInterval: '10m',
|
||||
doesSetRecoveryContext: false,
|
||||
hasAlertsMappings: true,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
const expectedResult: Array<
|
||||
AsApiContract<Omit<RegistryAlertTypeWithAuth, 'validLegacyConsumers'>>
|
||||
> = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
action_groups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
default_action_group_id: 'default',
|
||||
default_schedule_interval: '10m',
|
||||
does_set_recovery_context: false,
|
||||
minimum_license_required: 'basic',
|
||||
is_exportable: true,
|
||||
rule_task_timeout: '10m',
|
||||
recovery_action_group: RecoveredActionGroup,
|
||||
authorized_consumers: {},
|
||||
action_variables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'test',
|
||||
solution: 'stack',
|
||||
enabled_in_license: true,
|
||||
has_alerts_mappings: true,
|
||||
has_fields_for_a_a_d: false,
|
||||
},
|
||||
];
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments({ rulesClient }, {}, ['ok']);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Array [
|
||||
Object {
|
||||
"action_groups": Array [
|
||||
Object {
|
||||
"id": "default",
|
||||
"name": "Default",
|
||||
},
|
||||
],
|
||||
"action_variables": Object {
|
||||
"context": Array [],
|
||||
"state": Array [],
|
||||
},
|
||||
"authorized_consumers": Object {},
|
||||
"category": "test",
|
||||
"default_action_group_id": "default",
|
||||
"default_schedule_interval": "10m",
|
||||
"does_set_recovery_context": false,
|
||||
"enabled_in_license": true,
|
||||
"has_alerts_mappings": true,
|
||||
"has_fields_for_a_a_d": false,
|
||||
"id": "1",
|
||||
"is_exportable": true,
|
||||
"minimum_license_required": "basic",
|
||||
"name": "name",
|
||||
"producer": "test",
|
||||
"recovery_action_group": Object {
|
||||
"id": "recovered",
|
||||
"name": "Recovered",
|
||||
},
|
||||
"rule_task_timeout": "10m",
|
||||
"solution": "stack",
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
|
||||
expect(rulesClient.listRuleTypes).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: expectedResult,
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures the license allows listing rule types', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getRuleTypesInternalRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/_rule_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'alerts',
|
||||
solution: 'stack',
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await handler(context, req, res);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
|
||||
it('ensures the license check prevents listing rule types', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
(verifyApiAccess as jest.Mock).mockImplementation(() => {
|
||||
throw new Error('OMG');
|
||||
});
|
||||
|
||||
getRuleTypesInternalRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.get.mock.calls[0];
|
||||
|
||||
expect(config.path).toMatchInlineSnapshot(`"/internal/alerting/_rule_types"`);
|
||||
|
||||
const listTypes = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'name',
|
||||
actionGroups: [
|
||||
{
|
||||
id: 'default',
|
||||
name: 'Default',
|
||||
},
|
||||
],
|
||||
defaultActionGroupId: 'default',
|
||||
minimumLicenseRequired: 'basic',
|
||||
isExportable: true,
|
||||
recoveryActionGroup: RecoveredActionGroup,
|
||||
authorizedConsumers: {},
|
||||
actionVariables: {
|
||||
context: [],
|
||||
state: [],
|
||||
},
|
||||
category: 'test',
|
||||
producer: 'alerts',
|
||||
solution: 'stack',
|
||||
enabledInLicense: true,
|
||||
hasAlertsMappings: false,
|
||||
hasFieldsForAAD: false,
|
||||
validLegacyConsumers: [],
|
||||
} as RegistryAlertTypeWithAuth,
|
||||
];
|
||||
|
||||
rulesClient.listRuleTypes.mockResolvedValueOnce(listTypes);
|
||||
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
params: { id: '1' },
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
await expect(handler(context, req, res)).rejects.toMatchInlineSnapshot(`[Error: OMG]`);
|
||||
|
||||
expect(verifyApiAccess).toHaveBeenCalledWith(licenseState);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { IRouter } from '@kbn/core/server';
|
||||
import type { GetRuleTypesInternalResponseBodyV1 } from '../../../../../../common/routes/rule/apis/list_types/internal';
|
||||
import { getRuleTypesInternalResponseSchemaV1 } from '../../../../../../common/routes/rule/apis/list_types/internal';
|
||||
import type { ILicenseState } from '../../../../../lib';
|
||||
import { verifyAccessAndContext } from '../../../../lib';
|
||||
import type { AlertingRequestHandlerContext } from '../../../../../types';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../../../../types';
|
||||
import { transformRuleTypesInternalResponseV1 } from './transforms';
|
||||
import { DEFAULT_ALERTING_ROUTE_SECURITY } from '../../../../constants';
|
||||
|
||||
export const getRuleTypesInternalRoute = (
|
||||
router: IRouter<AlertingRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: `${INTERNAL_BASE_ALERTING_API_PATH}/_rule_types`,
|
||||
security: DEFAULT_ALERTING_ROUTE_SECURITY,
|
||||
options: {
|
||||
access: 'internal',
|
||||
},
|
||||
validate: {
|
||||
request: {},
|
||||
response: {
|
||||
200: {
|
||||
body: () => getRuleTypesInternalResponseSchemaV1,
|
||||
description: 'Indicates a successful call.',
|
||||
},
|
||||
401: {
|
||||
description: 'Authorization information is missing or invalid.',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const rulesClient = await (await context.alerting).getRulesClient();
|
||||
const ruleTypes = await rulesClient.listRuleTypes();
|
||||
|
||||
const responseBody: GetRuleTypesInternalResponseBodyV1 =
|
||||
transformRuleTypesInternalResponseV1(ruleTypes);
|
||||
|
||||
return res.ok({
|
||||
body: responseBody,
|
||||
});
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
|
@ -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 { getRuleTypesInternalRoute } from './get_rule_types_internal_route';
|
|
@ -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 { transformRuleTypesInternalResponse } from './transform_rule_types_internal_response/latest';
|
||||
export { transformRuleTypesInternalResponse as transformRuleTypesInternalResponseV1 } from './transform_rule_types_internal_response/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 * from './v1';
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { isBoolean } from 'lodash/fp';
|
||||
import type { RegistryAlertTypeWithAuth } from '../../../../../../../authorization';
|
||||
import type { GetRuleTypesInternalResponseBodyV1 } from '../../../../../../../../common/routes/rule/apis/list_types/internal';
|
||||
|
||||
export const transformRuleTypesInternalResponse = (
|
||||
ruleTypes: RegistryAlertTypeWithAuth[]
|
||||
): GetRuleTypesInternalResponseBodyV1 => {
|
||||
return ruleTypes.map((ruleType: RegistryAlertTypeWithAuth) => {
|
||||
return {
|
||||
...(ruleType.actionGroups ? { action_groups: ruleType.actionGroups } : {}),
|
||||
...(ruleType.actionVariables ? { action_variables: ruleType.actionVariables } : {}),
|
||||
...(ruleType.alerts ? { alerts: ruleType.alerts } : {}),
|
||||
authorized_consumers: ruleType.authorizedConsumers,
|
||||
category: ruleType.category,
|
||||
default_action_group_id: ruleType.defaultActionGroupId,
|
||||
...(ruleType.defaultScheduleInterval
|
||||
? { default_schedule_interval: ruleType.defaultScheduleInterval }
|
||||
: {}),
|
||||
...(isBoolean(ruleType.doesSetRecoveryContext)
|
||||
? { does_set_recovery_context: ruleType.doesSetRecoveryContext }
|
||||
: {}),
|
||||
enabled_in_license: ruleType.enabledInLicense,
|
||||
...(ruleType.fieldsForAAD ? { fieldsForAAD: ruleType.fieldsForAAD } : {}),
|
||||
has_alerts_mappings: ruleType.hasAlertsMappings,
|
||||
has_fields_for_a_a_d: ruleType.hasFieldsForAAD,
|
||||
id: ruleType.id,
|
||||
is_exportable: ruleType.isExportable,
|
||||
minimum_license_required: ruleType.minimumLicenseRequired,
|
||||
name: ruleType.name,
|
||||
producer: ruleType.producer,
|
||||
solution: ruleType.solution,
|
||||
recovery_action_group: ruleType.recoveryActionGroup,
|
||||
...(ruleType.ruleTaskTimeout ? { rule_task_timeout: ruleType.ruleTaskTimeout } : {}),
|
||||
};
|
||||
});
|
||||
};
|
|
@ -11,23 +11,20 @@ import { verifyApiAccess } from '../../../../lib/license_api_access';
|
|||
import { mockHandlerArguments } from '../../../_mock_handler_arguments';
|
||||
import { rulesClientMock } from '../../../../rules_client.mock';
|
||||
import { getRuleTagsRoute } from './get_rule_tags';
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
|
||||
const rulesClient = rulesClientMock.create();
|
||||
rulesClient.getTags.mockResolvedValue({
|
||||
data: ['a', 'b', 'c'],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
total: 3,
|
||||
});
|
||||
|
||||
jest.mock('../../../../lib/license_api_access', () => ({
|
||||
verifyApiAccess: jest.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
rulesClient.getTags.mockResolvedValueOnce({
|
||||
data: ['a', 'b', 'c'],
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
total: 3,
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRuleTagsRoute', () => {
|
||||
it('gets rule tags with proper parameters', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
|
@ -51,7 +48,6 @@ describe('getRuleTagsRoute', () => {
|
|||
},
|
||||
['ok']
|
||||
);
|
||||
|
||||
expect(await handler(context, req, res)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"body": Object {
|
||||
|
@ -66,6 +62,7 @@ describe('getRuleTagsRoute', () => {
|
|||
},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(res.ok).toHaveBeenCalledWith({
|
||||
body: {
|
||||
data: ['a', 'b', 'c'],
|
||||
|
@ -76,6 +73,70 @@ describe('getRuleTagsRoute', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('correctly parses query params', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
||||
getRuleTagsRoute(router, licenseState);
|
||||
const [, handler] = router.get.mock.calls[0];
|
||||
|
||||
// No rule_type_ids
|
||||
const query = {
|
||||
search: 'test',
|
||||
per_page: 10,
|
||||
page: 1,
|
||||
};
|
||||
const [context, req, res] = mockHandlerArguments(
|
||||
{ rulesClient },
|
||||
{
|
||||
query,
|
||||
},
|
||||
['ok']
|
||||
);
|
||||
await handler(context, req, res);
|
||||
expect(rulesClient.getTags).toHaveBeenCalledWith({
|
||||
search: 'test',
|
||||
perPage: 10,
|
||||
page: 1,
|
||||
});
|
||||
|
||||
// Singe rule_type_ids value
|
||||
await handler(
|
||||
context,
|
||||
{
|
||||
query: {
|
||||
...query,
|
||||
rule_type_ids: 'rule_type_1',
|
||||
},
|
||||
} as KibanaRequest,
|
||||
res
|
||||
);
|
||||
expect(rulesClient.getTags).toHaveBeenCalledWith({
|
||||
search: 'test',
|
||||
perPage: 10,
|
||||
page: 1,
|
||||
ruleTypeIds: ['rule_type_1'],
|
||||
});
|
||||
|
||||
// Array rule_type_ids
|
||||
await handler(
|
||||
context,
|
||||
{
|
||||
query: {
|
||||
...query,
|
||||
rule_type_ids: ['rule_type_1', 'rule_type_2'],
|
||||
},
|
||||
} as KibanaRequest,
|
||||
res
|
||||
);
|
||||
expect(rulesClient.getTags).toHaveBeenCalledWith({
|
||||
search: 'test',
|
||||
perPage: 10,
|
||||
page: 1,
|
||||
ruleTypeIds: ['rule_type_1', 'rule_type_2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('ensures the license allows getting rule tags', async () => {
|
||||
const licenseState = licenseStateMock.create();
|
||||
const router = httpServiceMock.createRouter();
|
||||
|
|
|
@ -5,17 +5,17 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RewriteRequestCase } from '@kbn/actions-plugin/common';
|
||||
import type { RuleTagsRequestQueryV1 } from '../../../../../../../common/routes/rule/apis/tags';
|
||||
import type { RuleTagsParams } from '../../../../../../application/rule/methods/tags';
|
||||
|
||||
export const transformRuleTagsQueryRequest: RewriteRequestCase<RuleTagsParams> = ({
|
||||
export const transformRuleTagsQueryRequest = ({
|
||||
per_page: perPage,
|
||||
page,
|
||||
search,
|
||||
rule_type_ids: ruleTypeIds,
|
||||
}) => ({
|
||||
}: RuleTagsRequestQueryV1): RuleTagsParams => ({
|
||||
page,
|
||||
search,
|
||||
perPage,
|
||||
ruleTypeIds,
|
||||
...(ruleTypeIds ? { ruleTypeIds: Array.isArray(ruleTypeIds) ? ruleTypeIds : [ruleTypeIds] } : {}),
|
||||
});
|
||||
|
|
|
@ -27,7 +27,7 @@ import type { SharePluginStart } from '@kbn/share-plugin/server';
|
|||
import type { DefaultAlert, FieldMap } from '@kbn/alerts-as-data-utils';
|
||||
import type { Alert } from '@kbn/alerts-as-data-utils';
|
||||
import type { ActionsApiRequestHandlerContext } from '@kbn/actions-plugin/server';
|
||||
import type { AlertsHealth } from '@kbn/alerting-types';
|
||||
import type { AlertsHealth, RuleTypeSolution } from '@kbn/alerting-types';
|
||||
import type { RuleTypeRegistry as OrigruleTypeRegistry } from './rule_type_registry';
|
||||
import type { AlertingServerSetup, AlertingServerStart } from './plugin';
|
||||
import type { RulesClient } from './rules_client';
|
||||
|
@ -265,8 +265,6 @@ export interface IRuleTypeAlerts<AlertData extends RuleAlertData = never> {
|
|||
formatAlert?: FormatAlert<AlertData>;
|
||||
}
|
||||
|
||||
export type RuleTypeSolution = 'observability' | 'security' | 'stack';
|
||||
|
||||
export interface RuleType<
|
||||
Params extends RuleTypeParams = never,
|
||||
ExtractedParams extends RuleTypeParams = never,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import { coreMock } from '@kbn/core/public/mocks';
|
||||
import { getMockPresentationContainer } from '@kbn/presentation-containers/mocks';
|
||||
import { fetchRuleTypes } from '@kbn/alerts-ui-shared/src/common/apis/fetch_rule_types';
|
||||
import { getRuleTypes } from '@kbn/response-ops-rules-apis/apis/get_rule_types';
|
||||
|
||||
import { getAddAlertsTableAction } from './add_alerts_table_action';
|
||||
import { ALERTS_FEATURE_ID } from '@kbn/alerts-ui-shared/src/common/constants';
|
||||
|
@ -15,15 +15,15 @@ import type { RuleType } from '@kbn/triggers-actions-ui-types';
|
|||
const core = coreMock.createStart();
|
||||
const mockPresentationContainer = getMockPresentationContainer();
|
||||
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_rule_types');
|
||||
const mockFetchRuleTypes = jest.mocked(fetchRuleTypes);
|
||||
jest.mock('@kbn/response-ops-rules-apis/apis/get_rule_types');
|
||||
const mockGetRuleTypes = jest.mocked(getRuleTypes);
|
||||
|
||||
describe('getAddAlertsTableAction', () => {
|
||||
it('should be compatible only when the user has access to at least one rule type', async () => {
|
||||
const ruleTypes = [
|
||||
{ authorizedConsumers: { [ALERTS_FEATURE_ID]: { all: true } } },
|
||||
] as unknown as Array<RuleType<string, string>>;
|
||||
mockFetchRuleTypes.mockResolvedValue(ruleTypes);
|
||||
mockGetRuleTypes.mockResolvedValue(ruleTypes);
|
||||
|
||||
const action = getAddAlertsTableAction({ http: core.http });
|
||||
|
||||
|
@ -36,7 +36,7 @@ describe('getAddAlertsTableAction', () => {
|
|||
|
||||
it("should not be compatible when the user doesn't have access to any rule type", async () => {
|
||||
const ruleTypes = [] as unknown as Array<RuleType<string, string>>;
|
||||
mockFetchRuleTypes.mockResolvedValue(ruleTypes);
|
||||
mockGetRuleTypes.mockResolvedValue(ruleTypes);
|
||||
|
||||
const action = getAddAlertsTableAction({ http: core.http });
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ADD_PANEL_VISUALIZATION_GROUP } from '@kbn/embeddable-plugin/public';
|
||||
import { fetchRuleTypes } from '@kbn/alerts-ui-shared/src/common/apis/fetch_rule_types';
|
||||
import { getRuleTypes } from '@kbn/response-ops-rules-apis/apis/get_rule_types';
|
||||
import { ALERTS_FEATURE_ID } from '@kbn/alerts-ui-shared/src/common/constants';
|
||||
import { apiIsPresentationContainer } from '@kbn/presentation-containers';
|
||||
import { IncompatibleActionError } from '@kbn/ui-actions-plugin/public';
|
||||
|
@ -18,7 +18,7 @@ import { ADD_ALERTS_TABLE_ACTION_ID, EMBEDDABLE_ALERTS_TABLE_ID } from '../const
|
|||
|
||||
const checkRuleTypesPermissions = async (http: CoreStart['http']) => {
|
||||
try {
|
||||
const ruleTypes = await fetchRuleTypes({ http });
|
||||
const ruleTypes = await getRuleTypes({ http });
|
||||
if (!ruleTypes.length) {
|
||||
// If no rule types we should not show the action.
|
||||
return false;
|
||||
|
|
|
@ -31,5 +31,6 @@
|
|||
"@kbn/core-ui-settings-browser",
|
||||
"@kbn/rule-data-utils",
|
||||
"@kbn/triggers-actions-ui-types",
|
||||
"@kbn/response-ops-rules-apis",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -33,13 +33,15 @@ jest.mock('./context/health_context', () => ({
|
|||
HealthContextProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
jest.mock('./hooks/use_load_rule_types_query', () => ({
|
||||
useLoadRuleTypesQuery: jest.fn().mockReturnValue({
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions', () => ({
|
||||
useGetRuleTypesPermissions: jest.fn().mockReturnValue({
|
||||
authorizedToReadAnyRules: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
const { useLoadRuleTypesQuery } = jest.requireMock('./hooks/use_load_rule_types_query');
|
||||
const { useGetRuleTypesPermissions } = jest.requireMock(
|
||||
'@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions'
|
||||
);
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
|
@ -47,7 +49,7 @@ describe('home', () => {
|
|||
beforeEach(() => {
|
||||
(hasShowActionsCapability as jest.Mock).mockClear();
|
||||
(getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation(() => false);
|
||||
useLoadRuleTypesQuery.mockClear();
|
||||
useGetRuleTypesPermissions.mockClear();
|
||||
});
|
||||
|
||||
it('renders rule list components', async () => {
|
||||
|
@ -111,7 +113,7 @@ describe('home', () => {
|
|||
});
|
||||
|
||||
it('hides the logs tab if the read rules privilege is missing', async () => {
|
||||
useLoadRuleTypesQuery.mockReturnValue({
|
||||
useGetRuleTypesPermissions.mockReturnValue({
|
||||
authorizedToReadAnyRules: false,
|
||||
});
|
||||
const props: RouteComponentProps<MatchParams> = {
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { EuiSpacer, EuiPageTemplate } from '@elastic/eui';
|
||||
|
||||
import { useGetRuleTypesPermissions } from '@kbn/alerts-ui-shared/src/common/hooks/use_get_rule_types_permissions';
|
||||
import { Section, routeToRules, routeToLogs } from './constants';
|
||||
import { getAlertingSectionBreadcrumb } from './lib/breadcrumb';
|
||||
import { getCurrentDocTitle } from './lib/doc_title';
|
||||
|
@ -20,7 +21,6 @@ import { HealthCheck } from './components/health_check';
|
|||
import { HealthContextProvider } from './context/health_context';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
|
||||
import { useLoadRuleTypesQuery } from './hooks/use_load_rule_types_query';
|
||||
|
||||
const RulesList = lazy(() => import('./sections/rules_list/components/rules_list'));
|
||||
const LogsList = lazy(
|
||||
|
@ -38,8 +38,17 @@ export const TriggersActionsUIHome: React.FunctionComponent<RouteComponentProps<
|
|||
history,
|
||||
}) => {
|
||||
const [headerActions, setHeaderActions] = useState<React.ReactNode[] | undefined>();
|
||||
const { chrome, setBreadcrumbs } = useKibana().services;
|
||||
const { authorizedToReadAnyRules } = useLoadRuleTypesQuery({ filteredRuleTypes: [] });
|
||||
const {
|
||||
chrome,
|
||||
setBreadcrumbs,
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const { authorizedToReadAnyRules } = useGetRuleTypesPermissions({
|
||||
http,
|
||||
toasts,
|
||||
filteredRuleTypes: [],
|
||||
});
|
||||
|
||||
const tabs: Array<{
|
||||
id: Section;
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export { useSubAction } from './use_sub_action';
|
||||
export { useLoadRuleTypesQuery } from './use_load_rule_types_query';
|
||||
|
|
|
@ -1,95 +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 { i18n } from '@kbn/i18n';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
import { useMemo } from 'react';
|
||||
import { loadRuleTypes } from '../lib/rule_api/rule_types';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import type { RuleType, RuleTypeIndex } from '../../types';
|
||||
|
||||
interface UseLoadRuleTypesQueryProps {
|
||||
filteredRuleTypes: string[];
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
const getFilteredIndex = (data: Array<RuleType<string, string>>, filteredRuleTypes: string[]) => {
|
||||
const index: RuleTypeIndex = new Map();
|
||||
for (const ruleType of data) {
|
||||
index.set(ruleType.id, ruleType);
|
||||
}
|
||||
let filteredIndex = index;
|
||||
if (filteredRuleTypes?.length) {
|
||||
filteredIndex = new Map(
|
||||
[...index].filter(([k, v]) => {
|
||||
return filteredRuleTypes.includes(v.id);
|
||||
})
|
||||
);
|
||||
}
|
||||
return filteredIndex;
|
||||
};
|
||||
|
||||
export const useLoadRuleTypesQuery = ({
|
||||
filteredRuleTypes,
|
||||
enabled = true,
|
||||
}: UseLoadRuleTypesQueryProps) => {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const queryFn = () => {
|
||||
return loadRuleTypes({ http });
|
||||
};
|
||||
|
||||
const onErrorFn = () => {
|
||||
toasts.addDanger(
|
||||
i18n.translate('xpack.triggersActionsUI.sections.rulesList.unableToLoadRuleTypesMessage', {
|
||||
defaultMessage: 'Unable to load rule types',
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const { data, isSuccess, isFetching, isInitialLoading, isLoading, error } = useQuery({
|
||||
queryKey: ['loadRuleTypes'],
|
||||
queryFn,
|
||||
onError: onErrorFn,
|
||||
refetchOnWindowFocus: false,
|
||||
// Leveraging TanStack Query's caching system to avoid duplicated requests as
|
||||
// other state-sharing solutions turned out to be overly complex and less readable
|
||||
staleTime: 60 * 1000,
|
||||
enabled,
|
||||
});
|
||||
|
||||
const filteredIndex = useMemo(
|
||||
() => (data ? getFilteredIndex(data, filteredRuleTypes) : new Map<string, RuleType>()),
|
||||
[data, filteredRuleTypes]
|
||||
);
|
||||
|
||||
const hasAnyAuthorizedRuleType = filteredIndex.size > 0;
|
||||
const authorizedRuleTypes = useMemo(() => [...filteredIndex.values()], [filteredIndex]);
|
||||
const authorizedToCreateAnyRules = authorizedRuleTypes.some(
|
||||
(ruleType) => ruleType.authorizedConsumers[ALERTING_FEATURE_ID]?.all
|
||||
);
|
||||
const authorizedToReadAnyRules =
|
||||
authorizedToCreateAnyRules ||
|
||||
authorizedRuleTypes.some((ruleType) => ruleType.authorizedConsumers[ALERTING_FEATURE_ID]?.read);
|
||||
|
||||
return {
|
||||
ruleTypesState: {
|
||||
initialLoad: isLoading || isInitialLoading,
|
||||
isLoading: isLoading || isFetching,
|
||||
data: filteredIndex,
|
||||
error,
|
||||
},
|
||||
hasAnyAuthorizedRuleType,
|
||||
authorizedRuleTypes,
|
||||
authorizedToReadAnyRules,
|
||||
authorizedToCreateAnyRules,
|
||||
isSuccess,
|
||||
};
|
||||
};
|
|
@ -34,6 +34,7 @@ describe('checkRuleTypeEnabled', () => {
|
|||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
category: 'my-category',
|
||||
isExportable: true,
|
||||
};
|
||||
expect(checkRuleTypeEnabled(alertType)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -59,6 +60,7 @@ describe('checkRuleTypeEnabled', () => {
|
|||
minimumLicenseRequired: 'gold',
|
||||
enabledInLicense: false,
|
||||
category: 'my-category',
|
||||
isExportable: true,
|
||||
};
|
||||
expect(checkRuleTypeEnabled(alertType)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
|
|
@ -64,6 +64,7 @@ function mockRuleType(overwrites: Partial<RuleType> = {}): RuleType {
|
|||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
category: 'my-category',
|
||||
isExportable: true,
|
||||
...overwrites,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { loadRuleAggregations, loadRuleTags } from './aggregate';
|
||||
import { loadRuleAggregations } from './aggregate';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
|
@ -287,41 +287,4 @@ describe('loadRuleAggregations', () => {
|
|||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('loadRuleTags should call the getTags API', async () => {
|
||||
const resolvedValue = {
|
||||
data: ['a', 'b', 'c'],
|
||||
total: 3,
|
||||
page: 2,
|
||||
per_page: 30,
|
||||
};
|
||||
http.get.mockResolvedValueOnce(resolvedValue);
|
||||
|
||||
const result = await loadRuleTags({
|
||||
http,
|
||||
search: 'test',
|
||||
page: 2,
|
||||
perPage: 30,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
data: ['a', 'b', 'c'],
|
||||
page: 2,
|
||||
perPage: 30,
|
||||
total: 3,
|
||||
});
|
||||
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/internal/alerting/rules/_tags",
|
||||
Object {
|
||||
"query": Object {
|
||||
"page": 2,
|
||||
"per_page": 30,
|
||||
"search": "test",
|
||||
},
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,36 +4,11 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { AsApiContract } from '@kbn/actions-plugin/common';
|
||||
import type { AggregateRulesResponseBody } from '@kbn/alerting-plugin/common/routes/rule/apis/aggregate';
|
||||
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
|
||||
import { mapFiltersToKql } from './map_filters_to_kql';
|
||||
import type {
|
||||
LoadRuleAggregationsProps,
|
||||
LoadRuleTagsProps,
|
||||
GetRuleTagsResponse,
|
||||
AggregateRulesResponse,
|
||||
} from './aggregate_helpers';
|
||||
import { rewriteBodyRes, rewriteTagsBodyRes } from './aggregate_helpers';
|
||||
|
||||
export async function loadRuleTags({
|
||||
http,
|
||||
search,
|
||||
perPage,
|
||||
page,
|
||||
}: LoadRuleTagsProps): Promise<GetRuleTagsResponse> {
|
||||
const res = await http.get<AsApiContract<GetRuleTagsResponse>>(
|
||||
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_tags`,
|
||||
{
|
||||
query: {
|
||||
search,
|
||||
per_page: perPage,
|
||||
page,
|
||||
},
|
||||
}
|
||||
);
|
||||
return rewriteTagsBodyRes(res);
|
||||
}
|
||||
import type { LoadRuleAggregationsProps, AggregateRulesResponse } from './aggregate_helpers';
|
||||
import { rewriteBodyRes } from './aggregate_helpers';
|
||||
|
||||
export async function loadRuleAggregations({
|
||||
http,
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import type { HttpSetup } from '@kbn/core/public';
|
||||
import type { RewriteRequestCase } from '@kbn/actions-plugin/common';
|
||||
import type { AggregateRulesResponseBody } from '@kbn/alerting-plugin/common/routes/rule/apis/aggregate';
|
||||
import type { RuleStatus } from '../../../types';
|
||||
|
||||
|
@ -43,21 +42,6 @@ export const rewriteBodyRes = ({
|
|||
ruleTags,
|
||||
});
|
||||
|
||||
export interface GetRuleTagsResponse {
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
data: string[];
|
||||
}
|
||||
|
||||
export const rewriteTagsBodyRes: RewriteRequestCase<GetRuleTagsResponse> = ({
|
||||
per_page: perPage,
|
||||
...rest
|
||||
}) => ({
|
||||
perPage,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export interface LoadRuleAggregationsProps {
|
||||
http: HttpSetup;
|
||||
searchText?: string;
|
||||
|
@ -69,10 +53,3 @@ export interface LoadRuleAggregationsProps {
|
|||
ruleTypeIds?: string[];
|
||||
consumers?: string[];
|
||||
}
|
||||
|
||||
export interface LoadRuleTagsProps {
|
||||
http: HttpSetup;
|
||||
search?: string;
|
||||
perPage?: number;
|
||||
page: number;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export type { LoadRuleAggregationsProps, LoadRuleTagsProps } from './aggregate_helpers';
|
||||
export type { LoadRuleAggregationsProps } from './aggregate_helpers';
|
||||
export type { LoadRulesProps } from './rules_helpers';
|
||||
export type {
|
||||
LoadExecutionLogAggregationsProps,
|
||||
|
|
|
@ -1,46 +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 { RuleType } from '../../../types';
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { loadRuleTypes } from './rule_types';
|
||||
import { ALERTING_FEATURE_ID } from '@kbn/alerting-plugin/common';
|
||||
|
||||
const http = httpServiceMock.createStartContract();
|
||||
|
||||
describe('loadRuleTypes', () => {
|
||||
test('should call get alert types API', async () => {
|
||||
const resolvedValue: RuleType[] = [
|
||||
{
|
||||
id: 'test',
|
||||
name: 'Test',
|
||||
actionVariables: {
|
||||
context: [{ name: 'var1', description: 'val1' }],
|
||||
state: [{ name: 'var2', description: 'val2' }],
|
||||
params: [{ name: 'var3', description: 'val3' }],
|
||||
},
|
||||
producer: ALERTING_FEATURE_ID,
|
||||
actionGroups: [{ id: 'default', name: 'Default' }],
|
||||
recoveryActionGroup: { id: 'recovered', name: 'Recovered' },
|
||||
defaultActionGroupId: 'default',
|
||||
authorizedConsumers: {},
|
||||
minimumLicenseRequired: 'basic',
|
||||
enabledInLicense: true,
|
||||
category: 'my-category',
|
||||
},
|
||||
];
|
||||
http.get.mockResolvedValueOnce(resolvedValue);
|
||||
|
||||
const result = await loadRuleTypes({ http });
|
||||
expect(result).toEqual(resolvedValue);
|
||||
expect(http.get.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/api/alerting/rule_types",
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -1,51 +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 { HttpSetup } from '@kbn/core/public';
|
||||
import type { AsApiContract, RewriteRequestCase } from '@kbn/actions-plugin/common';
|
||||
import type { RuleType } from '../../../types';
|
||||
import { BASE_ALERTING_API_PATH } from '../../constants';
|
||||
|
||||
const rewriteResponseRes = (results: Array<AsApiContract<RuleType>>): RuleType[] => {
|
||||
return results.map((item) => rewriteBodyReq(item));
|
||||
};
|
||||
|
||||
const rewriteBodyReq: RewriteRequestCase<RuleType> = ({
|
||||
enabled_in_license: enabledInLicense,
|
||||
recovery_action_group: recoveryActionGroup,
|
||||
action_groups: actionGroups,
|
||||
default_action_group_id: defaultActionGroupId,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
action_variables: actionVariables,
|
||||
authorized_consumers: authorizedConsumers,
|
||||
rule_task_timeout: ruleTaskTimeout,
|
||||
does_set_recovery_context: doesSetRecoveryContext,
|
||||
default_schedule_interval: defaultScheduleInterval,
|
||||
has_alerts_mappings: hasAlertsMappings,
|
||||
has_fields_for_a_a_d: hasFieldsForAAD,
|
||||
...rest
|
||||
}: AsApiContract<RuleType>) => ({
|
||||
enabledInLicense,
|
||||
recoveryActionGroup,
|
||||
actionGroups,
|
||||
defaultActionGroupId,
|
||||
minimumLicenseRequired,
|
||||
actionVariables,
|
||||
authorizedConsumers,
|
||||
ruleTaskTimeout,
|
||||
doesSetRecoveryContext,
|
||||
defaultScheduleInterval,
|
||||
hasAlertsMappings,
|
||||
hasFieldsForAAD,
|
||||
...rest,
|
||||
});
|
||||
|
||||
export async function loadRuleTypes({ http }: { http: HttpSetup }): Promise<RuleType[]> {
|
||||
const res = await http.get<Array<AsApiContract<RuleType<string, string>>>>(
|
||||
`${BASE_ALERTING_API_PATH}/rule_types`
|
||||
);
|
||||
return rewriteResponseRes(res);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue