Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0be26ba
Move get rule tags API to @kbn/response-ops-rules-apis
umbopepato Mar 12, 2025
edcde44
Add internal rule types route, fetcher and query hook
umbopepato Mar 12, 2025
67b20ac
Fix translations
umbopepato Mar 12, 2025
08b7383
Fix type error
umbopepato Mar 12, 2025
9cf0f85
Fix tests
umbopepato Mar 12, 2025
e05ef91
Add last shared rule APIs to package
umbopepato Mar 13, 2025
e1e55f4
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Mar 13, 2025
c2a425f
Fix translations
umbopepato Mar 13, 2025
706a8be
Fix tests
umbopepato Mar 13, 2025
9e77938
Fix tests
umbopepato Mar 13, 2025
d9d4c47
Fix RuleDefinition props instability
umbopepato Mar 14, 2025
1865c02
Merge branch 'main' into 213059-response-ops-rules-apis-package
umbopepato Mar 17, 2025
01bfbbd
Ensure rule_type_ids filter in get rule tags API is always a JSON array
umbopepato Mar 17, 2025
d69bef0
Merge branch 'main' into 213059-response-ops-rules-apis-package
umbopepato Mar 17, 2025
752b87f
Merge branch 'main' of github.com:elastic/kibana into 213059-response…
umbopepato Mar 20, 2025
270c282
Add tests, apply suggested naming changes
umbopepato Mar 20, 2025
0561de5
Merge branch 'main' of github.com:elastic/kibana into 213059-response…
umbopepato Mar 21, 2025
590f8df
Move get rule types internal/external routes in subfolders
umbopepato Mar 21, 2025
789a713
Extend rule tags API rule_type_ids schema to avoid JSON.stringifying …
umbopepato Mar 21, 2025
77c45c4
Merge branch 'main' of github.com:elastic/kibana into 213059-response…
umbopepato Mar 21, 2025
027bfef
[CI] Auto-commit changed files from 'node scripts/notice'
kibanamachine Mar 21, 2025
f04eb76
Fix integration test
umbopepato Mar 21, 2025
859f879
Merge branch 'main' of github.com:elastic/kibana into 213059-response…
umbopepato Mar 21, 2025
e115542
Fix unit test
umbopepato Mar 21, 2025
c933c02
Merge branch 'main' into 213059-response-ops-rules-apis-package
umbopepato Mar 24, 2025
6a3b06d
Remove unnecessary mock
umbopepato Mar 24, 2025
74ed203
Merge branch 'main' of github.com:elastic/kibana into 213059-response…
umbopepato Mar 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,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
src/platform/packages/shared/serverless/settings/chat_project @elastic/search-kibana
src/platform/packages/shared/serverless/settings/common @elastic/appex-sharedux @elastic/kibana-management
src/platform/packages/shared/serverless/settings/observability_project @elastic/appex-sharedux @elastic/kibana-management @elastic/obs-ux-management-team
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,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",
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using kebab-case for package folders as requested by the operations team

"@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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
import type { Filter } from '@kbn/es-query';
import type { RuleNotifyWhenType, RRuleParams } from '.';

export type RuleTypeSolution = 'observability' | 'security' | 'stack';
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to move this here to avoid circular dependencies between plugins and packages

export type RuleTypeParams = Record<string, unknown>;
export type RuleActionParams = SavedObjectAttributes;
export type RuleActionParam = SavedObjectAttribute;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,6 @@ function getAlertType(actionVariables: ActionVariables): RuleType {
minimumLicenseRequired: 'basic',
enabledInLicense: true,
category: 'my-category',
isExportable: true,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export * from './use_fetch_alerts_index_names_query';
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';
Original file line number Diff line number Diff line change
@@ -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(() => result.current.isSuccess);
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(() => result.current.isSuccess);
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(() => result.current.isSuccess);
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(() => result.current.isSuccess);
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(() => result.current.isSuccess);
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(() => result.current.isSuccess);
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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand All @@ -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') : {};
Expand All @@ -63,19 +63,15 @@ const getFilteredIndex = ({
return filteredIndex;
};

export const useLoadRuleTypesQuery = ({
export const useGetRuleTypesPermissions = ({
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook was acting as the main rule types fetcher but also as privilege checker, returning computed values and not the original query. I extracted the react-query part that only fetches rule types to the new package, keeping this hook as a more privilege-checking-focused one (hence the new name). I avoided further refactors such as removing the returned rule types registry thing since there is a bit of filtering logic that might be worth studying better, and moving the hook to the new package as it's not properly a data-fetching hook (I'm wondering where these higher-level hooks should belong in our new package structure 🤔).

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', {
Expand All @@ -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(
() =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
"@kbn/core-notifications-browser-mocks",
"@kbn/shared-ux-table-persist",
"@kbn/presentation-publishing",
"@kbn/response-ops-rules-apis",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface RuleType<
| 'defaultScheduleInterval'
| 'doesSetRecoveryContext'
| 'category'
| 'isExportable'
> {
actionVariables: ActionVariables;
authorizedConsumers: Record<string, { read: boolean; all: boolean }>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,4 @@
*/

export const BASE_ALERTING_API_PATH = '/api/alerting';

export const queryKeys = {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved all queryKeys and queryMutations of the new packages to dedicated files to increase visibility and because they aren't properly constants in the JS reference sense of the term

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';
Loading