Skip to content

Commit

Permalink
[Response Ops][Rules] Add New Rule Form to Stack Management (#194655)
Browse files Browse the repository at this point in the history
## Summary

Enables and adds the new rule form to stack management. We are only
going to turn this on for stack management for now until we are
confident that this is fairly bug free.

### To test:

1. Switch `USE_NEW_RULE_FORM_FEATURE_FLAG` to true
2. Navigate to stack management -> rules list
3. Click "Create rule" 
4. Assert the user is navigated to the new form
5. Create rule
6. Assert the user is navigated to the rule details page
7. Click "Edit"
8. Edit rule
9. Assert the user is navigated to the rule details page
10. Try editing a rule in the rules list and assert everything works as
expected

We should also make sure this rule form is not enabled in other
solutions.

### Checklist
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: Elastic Machine <[email protected]>
Co-authored-by: Christos Nasikas <[email protected]>
  • Loading branch information
3 people authored Oct 15, 2024
1 parent 3034dc8 commit 5c2df63
Show file tree
Hide file tree
Showing 60 changed files with 857 additions and 297 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ export const transformUpdateRuleBody: RewriteResponseCase<UpdateRuleBody> = ({
...(uuid && { uuid }),
};
}),
...(alertDelay ? { alert_delay: alertDelay } : {}),
...(alertDelay !== undefined ? { alert_delay: alertDelay } : {}),
...(flapping !== undefined ? { flapping: transformUpdateRuleFlapping(flapping) } : {}),
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
*/

// Feature flag for frontend rule specific flapping in rule flyout
export const IS_RULE_SPECIFIC_FLAPPING_ENABLED = false;
export const IS_RULE_SPECIFIC_FLAPPING_ENABLED = true;
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser';
import { createRule, CreateRuleBody } from '../apis/create_rule';
import { Rule } from '../types';

export interface UseCreateRuleProps {
http: HttpStart;
onSuccess?: (formData: CreateRuleBody) => void;
onSuccess?: (rule: Rule) => void;
onError?: (error: IHttpFetchError<{ message: string }>) => void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ export interface UseLoadConnectorsProps {
http: HttpStart;
includeSystemActions?: boolean;
enabled?: boolean;
cacheTime?: number;
}

export const useLoadConnectors = (props: UseLoadConnectorsProps) => {
const { http, includeSystemActions = false, enabled = true } = props;
const { http, includeSystemActions = false, enabled = true, cacheTime } = props;

const queryFn = () => {
return fetchConnectors({ http, includeSystemActions });
Expand All @@ -27,6 +28,7 @@ export const useLoadConnectors = (props: UseLoadConnectorsProps) => {
const { data, isLoading, isFetching, isInitialLoading } = useQuery({
queryKey: ['useLoadConnectors', includeSystemActions],
queryFn,
cacheTime,
refetchOnWindowFocus: false,
enabled,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ export interface UseLoadRuleTypeAadTemplateFieldProps {
http: HttpStart;
ruleTypeId?: string;
enabled: boolean;
cacheTime?: number;
}

export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplateFieldProps) => {
const { http, ruleTypeId, enabled } = props;
const { http, ruleTypeId, enabled, cacheTime } = props;

const queryFn = () => {
if (!ruleTypeId) {
Expand All @@ -43,6 +44,7 @@ export const useLoadRuleTypeAadTemplateField = (props: UseLoadRuleTypeAadTemplat
description: getDescription(d.name, EcsFlat),
}));
},
cacheTime,
refetchOnWindowFocus: false,
enabled,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import { RuleFormData } from '../../rule_form';
export interface UseResolveProps {
http: HttpStart;
id?: string;
cacheTime?: number;
}

export const useResolveRule = (props: UseResolveProps) => {
const { id, http } = props;
const { id, http, cacheTime } = props;

const queryFn = () => {
if (id) {
Expand All @@ -30,6 +31,7 @@ export const useResolveRule = (props: UseResolveProps) => {
queryKey: ['useResolveRule', id],
queryFn,
enabled: !!id,
cacheTime,
select: (rule): RuleFormData | null => {
if (!rule) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
import { useMutation } from '@tanstack/react-query';
import type { HttpStart, IHttpFetchError } from '@kbn/core-http-browser';
import { updateRule, UpdateRuleBody } from '../apis/update_rule';
import { Rule } from '../types';

export interface UseUpdateRuleProps {
http: HttpStart;
onSuccess?: (formData: UpdateRuleBody) => void;
onSuccess?: (rule: Rule) => void;
onError?: (error: IHttpFetchError<{ message: string }>) => void;
}

Expand Down
2 changes: 0 additions & 2 deletions packages/kbn-alerts-ui-shared/src/common/types/rule_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import { TypeRegistry } from '../type_registry';

export type { SanitizedRuleAction as RuleAction } from '@kbn/alerting-types';

export type { Flapping } from '@kbn/alerting-types';

export type RuleTypeWithDescription = RuleType<string, string> & { description?: string };

export type RuleTypeIndexWithDescriptions = Map<string, RuleTypeWithDescription>;
Expand Down
3 changes: 2 additions & 1 deletion packages/kbn-alerts-ui-shared/src/rule_form/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const DEFAULT_FREQUENCY = {
summary: false,
};

export const GET_DEFAULT_FORM_DATA = ({
export const getDefaultFormData = ({
ruleTypeId,
name,
consumer,
Expand All @@ -50,6 +50,7 @@ export const GET_DEFAULT_FORM_DATA = ({
ruleTypeId,
name,
actions,
alertDelay: { active: 1 },
};
};

Expand Down
22 changes: 16 additions & 6 deletions packages/kbn-alerts-ui-shared/src/rule_form/create_rule_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EuiLoadingElastic } from '@elastic/eui';
import { toMountPoint } from '@kbn/react-kibana-mount';
import { type RuleCreationValidConsumer } from '@kbn/rule-data-utils';
import type { RuleFormData, RuleFormPlugins } from './types';
import { DEFAULT_VALID_CONSUMERS, GET_DEFAULT_FORM_DATA } from './constants';
import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants';
import { RuleFormStateProvider } from './rule_form_state';
import { useCreateRule } from '../common/hooks';
import { RulePage } from './rule_page';
Expand All @@ -24,6 +24,7 @@ import {
} from './rule_form_errors';
import { useLoadDependencies } from './hooks/use_load_dependencies';
import {
getAvailableRuleTypes,
getInitialConsumer,
getInitialMultiConsumer,
getInitialSchedule,
Expand All @@ -42,7 +43,8 @@ export interface CreateRuleFormProps {
shouldUseRuleProducer?: boolean;
canShowConsumerSelection?: boolean;
showMustacheAutocompleteSwitch?: boolean;
returnUrl: string;
onCancel?: () => void;
onSubmit?: (ruleId: string) => void;
}

export const CreateRuleForm = (props: CreateRuleFormProps) => {
Expand All @@ -56,16 +58,18 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
shouldUseRuleProducer = false,
canShowConsumerSelection = true,
showMustacheAutocompleteSwitch = false,
returnUrl,
onCancel,
onSubmit,
} = props;

const { http, docLinks, notifications, ruleTypeRegistry, i18n, theme } = plugins;
const { toasts } = notifications;

const { mutate, isLoading: isSaving } = useCreateRule({
http,
onSuccess: ({ name }) => {
onSuccess: ({ name, id }) => {
toasts.addSuccess(RULE_CREATE_SUCCESS_TEXT(name));
onSubmit?.(id);
},
onError: (error) => {
const message = parseRuleCircuitBreakerErrorMessage(
Expand All @@ -86,6 +90,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
const {
isInitialLoading,
ruleType,
ruleTypes,
ruleTypeModel,
uiConfig,
healthCheckError,
Expand Down Expand Up @@ -153,7 +158,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
<div data-test-subj="createRuleForm">
<RuleFormStateProvider
initialRuleFormState={{
formData: GET_DEFAULT_FORM_DATA({
formData: getDefaultFormData({
ruleTypeId,
name: `${ruleType.name} rule`,
consumer: getInitialConsumer({
Expand All @@ -174,6 +179,11 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
selectedRuleTypeModel: ruleTypeModel,
selectedRuleType: ruleType,
availableRuleTypes: getAvailableRuleTypes({
consumer,
ruleTypes,
ruleTypeRegistry,
}).map(({ ruleType: rt }) => rt),
validConsumers,
flappingSettings,
canShowConsumerSelection,
Expand All @@ -185,7 +195,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
}),
}}
>
<RulePage isEdit={false} isSaving={isSaving} returnUrl={returnUrl} onSave={onSave} />
<RulePage isEdit={false} isSaving={isSaving} onCancel={onCancel} onSave={onSave} />
</RuleFormStateProvider>
</div>
);
Expand Down
28 changes: 23 additions & 5 deletions packages/kbn-alerts-ui-shared/src/rule_form/edit_rule_form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,27 @@ import {
RuleFormRuleTypeError,
} from './rule_form_errors';
import { RULE_EDIT_ERROR_TEXT, RULE_EDIT_SUCCESS_TEXT } from './translations';
import { parseRuleCircuitBreakerErrorMessage } from './utils';
import { getAvailableRuleTypes, parseRuleCircuitBreakerErrorMessage } from './utils';
import { DEFAULT_VALID_CONSUMERS, getDefaultFormData } from './constants';

export interface EditRuleFormProps {
id: string;
plugins: RuleFormPlugins;
showMustacheAutocompleteSwitch?: boolean;
returnUrl: string;
onCancel?: () => void;
onSubmit?: (ruleId: string) => void;
}

export const EditRuleForm = (props: EditRuleFormProps) => {
const { id, plugins, returnUrl, showMustacheAutocompleteSwitch = false } = props;
const { id, plugins, showMustacheAutocompleteSwitch = false, onCancel, onSubmit } = props;
const { http, notifications, docLinks, ruleTypeRegistry, i18n, theme, application } = plugins;
const { toasts } = notifications;

const { mutate, isLoading: isSaving } = useUpdateRule({
http,
onSuccess: ({ name }) => {
toasts.addSuccess(RULE_EDIT_SUCCESS_TEXT(name));
onSubmit?.(id);
},
onError: (error) => {
const message = parseRuleCircuitBreakerErrorMessage(
Expand All @@ -62,6 +65,7 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
const {
isInitialLoading,
ruleType,
ruleTypes,
ruleTypeModel,
uiConfig,
healthCheckError,
Expand Down Expand Up @@ -156,17 +160,31 @@ export const EditRuleForm = (props: EditRuleFormProps) => {
connectors,
connectorTypes,
aadTemplateFields,
formData: fetchedFormData,
formData: {
...getDefaultFormData({
ruleTypeId: fetchedFormData.ruleTypeId,
name: fetchedFormData.name,
consumer: fetchedFormData.consumer,
actions: fetchedFormData.actions,
}),
...fetchedFormData,
},
id,
plugins,
minimumScheduleInterval: uiConfig?.minimumScheduleInterval,
selectedRuleType: ruleType,
selectedRuleTypeModel: ruleTypeModel,
availableRuleTypes: getAvailableRuleTypes({
consumer: fetchedFormData.consumer,
ruleTypes,
ruleTypeRegistry,
}).map(({ ruleType: rt }) => rt),
flappingSettings,
validConsumers: DEFAULT_VALID_CONSUMERS,
showMustacheAutocompleteSwitch,
}}
>
<RulePage isEdit={true} isSaving={isSaving} returnUrl={returnUrl} onSave={onSave} />
<RulePage isEdit={true} isSaving={isSaving} onSave={onSave} onCancel={onCancel} />
</RuleFormStateProvider>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,6 @@ jest.mock('../../common/hooks/use_load_rule_type_aad_template_fields', () => ({
useLoadRuleTypeAadTemplateField: jest.fn(),
}));

jest.mock('../utils/get_authorized_rule_types', () => ({
getAvailableRuleTypes: jest.fn(),
}));

jest.mock('../../common/hooks/use_fetch_flapping_settings', () => ({
useFetchFlappingSettings: jest.fn(),
}));
Expand All @@ -63,7 +59,6 @@ const { useLoadRuleTypeAadTemplateField } = jest.requireMock(
'../../common/hooks/use_load_rule_type_aad_template_fields'
);
const { useLoadRuleTypesQuery } = jest.requireMock('../../common/hooks/use_load_rule_types_query');
const { getAvailableRuleTypes } = jest.requireMock('../utils/get_authorized_rule_types');
const { useFetchFlappingSettings } = jest.requireMock(
'../../common/hooks/use_fetch_flapping_settings'
);
Expand Down Expand Up @@ -168,13 +163,6 @@ useLoadRuleTypesQuery.mockReturnValue({
},
});

getAvailableRuleTypes.mockReturnValue([
{
ruleType: indexThresholdRuleType,
ruleTypeModel: indexThresholdRuleTypeModel,
},
]);

const mockConnector = {
id: 'test-connector',
name: 'Test',
Expand Down Expand Up @@ -236,7 +224,7 @@ const toastsMock = jest.fn();
const ruleTypeRegistryMock: RuleTypeRegistryContract = {
has: jest.fn(),
register: jest.fn(),
get: jest.fn(),
get: jest.fn().mockReturnValue(indexThresholdRuleTypeModel),
list: jest.fn(),
};

Expand Down Expand Up @@ -272,6 +260,7 @@ describe('useLoadDependencies', () => {
isLoading: false,
isInitialLoading: false,
ruleType: indexThresholdRuleType,
ruleTypes: [...ruleTypeIndex.values()],
ruleTypeModel: indexThresholdRuleTypeModel,
uiConfig: uiConfigMock,
healthCheckError: null,
Expand Down Expand Up @@ -317,39 +306,6 @@ describe('useLoadDependencies', () => {
});
});

test('should call getAvailableRuleTypes with the correct params', async () => {
const { result } = renderHook(
() => {
return useLoadDependencies({
http: httpMock as unknown as HttpStart,
toasts: toastsMock as unknown as ToastsStart,
ruleTypeRegistry: ruleTypeRegistryMock,
validConsumers: ['stackAlerts', 'logs'],
consumer: 'logs',
capabilities: {
actions: {
show: true,
save: true,
execute: true,
},
} as unknown as ApplicationStart['capabilities'],
});
},
{ wrapper }
);

await waitFor(() => {
return expect(result.current.isInitialLoading).toEqual(false);
});

expect(getAvailableRuleTypes).toBeCalledWith({
consumer: 'logs',
ruleTypeRegistry: ruleTypeRegistryMock,
ruleTypes: [indexThresholdRuleType],
validConsumers: ['stackAlerts', 'logs'],
});
});

test('should call resolve rule with the correct params', async () => {
const { result } = renderHook(
() => {
Expand Down Expand Up @@ -377,6 +333,7 @@ describe('useLoadDependencies', () => {
expect(useResolveRule).toBeCalledWith({
http: httpMock,
id: 'test-rule-id',
cacheTime: 0,
});
});

Expand Down
Loading

0 comments on commit 5c2df63

Please sign in to comment.