Skip to content

Commit

Permalink
[ResponseOps][Flapping] Add Rule Specific Flapping Form to New Rule F…
Browse files Browse the repository at this point in the history
…orm Page (elastic#194516)

## Summary

Depends on: elastic#194086
Designs:
https://www.figma.com/design/eTr6WsKzhSLcQ24AlgrY8R/Flapping-per-Rule--%3E-%23294?node-id=5265-29867&node-type=frame&t=1VfgdlcjkSHmpbje-0

Adds the rule specific flapping form to the new rule form page. 

## To test:

1. change `IS_RULE_SPECIFIC_FLAPPING_ENABLED` to true 
2. run `yarn start --run-examples`
3. assert the new flapping UI exists by going to developer examples ->
create/edit rule

<img width="1162" alt="Screenshot 2024-09-30 at 11 43 16 PM"
src="https://github.com/user-attachments/assets/a1275a49-f2ed-43ce-815b-5c0bd93770e5">

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

---------

Co-authored-by: kibanamachine <[email protected]>
Co-authored-by: Elastic Machine <[email protected]>
  • Loading branch information
3 people authored Oct 10, 2024
1 parent b51ba0a commit 447617e
Show file tree
Hide file tree
Showing 37 changed files with 1,162 additions and 540 deletions.
1 change: 1 addition & 0 deletions packages/kbn-alerting-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ export * from './r_rule_types';
export * from './rule_notify_when_type';
export * from './rule_type_types';
export * from './rule_types';
export * from './rule_settings';
export * from './search_strategy_types';
46 changes: 46 additions & 0 deletions packages/kbn-alerting-types/rule_settings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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 interface RulesSettingsModificationMetadata {
createdBy: string | null;
updatedBy: string | null;
createdAt: string;
updatedAt: string;
}

export interface RulesSettingsFlappingProperties {
enabled: boolean;
lookBackWindow: number;
statusChangeThreshold: number;
}

export interface RuleSpecificFlappingProperties {
lookBackWindow: number;
statusChangeThreshold: number;
}

export type RulesSettingsFlapping = RulesSettingsFlappingProperties &
RulesSettingsModificationMetadata;

export interface RulesSettingsQueryDelayProperties {
delay: number;
}

export type RulesSettingsQueryDelay = RulesSettingsQueryDelayProperties &
RulesSettingsModificationMetadata;

export interface RulesSettingsProperties {
flapping?: RulesSettingsFlappingProperties;
queryDelay?: RulesSettingsQueryDelayProperties;
}

export interface RulesSettings {
flapping?: RulesSettingsFlapping;
queryDelay?: RulesSettingsQueryDelay;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* 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 { httpServiceMock } from '@kbn/core/public/mocks';
import { fetchFlappingSettings } from './fetch_flapping_settings';

const http = httpServiceMock.createStartContract();

describe('fetchFlappingSettings', () => {
beforeEach(() => {
jest.resetAllMocks();
});

test('should call fetch rule flapping API', async () => {
const now = new Date().toISOString();
http.get.mockResolvedValue({
created_by: 'test',
updated_by: 'test',
created_at: now,
updated_at: now,
enabled: true,
look_back_window: 20,
status_change_threshold: 20,
});

const result = await fetchFlappingSettings({ http });

expect(result).toEqual({
createdBy: 'test',
updatedBy: 'test',
createdAt: now,
updatedAt: now,
enabled: true,
lookBackWindow: 20,
statusChangeThreshold: 20,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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 { HttpSetup } from '@kbn/core/public';
import { AsApiContract } from '@kbn/actions-types';
import { RulesSettingsFlapping } from '@kbn/alerting-types';
import { INTERNAL_BASE_ALERTING_API_PATH } from '../../constants';
import { transformFlappingSettingsResponse } from './transform_flapping_settings_response';

export const fetchFlappingSettings = async ({ http }: { http: HttpSetup }) => {
const res = await http.get<AsApiContract<RulesSettingsFlapping>>(
`${INTERNAL_BASE_ALERTING_API_PATH}/rules/settings/_flapping`
);
return transformFlappingSettingsResponse(res);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", 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 * from './fetch_flapping_settings';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 { transformFlappingSettingsResponse } from './transform_flapping_settings_response';

describe('transformFlappingSettingsResponse', () => {
test('should transform flapping settings response', () => {
const now = new Date().toISOString();

const result = transformFlappingSettingsResponse({
created_by: 'test',
updated_by: 'test',
created_at: now,
updated_at: now,
enabled: true,
look_back_window: 20,
status_change_threshold: 20,
});

expect(result).toEqual({
createdBy: 'test',
updatedBy: 'test',
createdAt: now,
updatedAt: now,
enabled: true,
lookBackWindow: 20,
statusChangeThreshold: 20,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { AsApiContract } from '@kbn/actions-types';
import { RulesSettingsFlapping } from '@kbn/alerting-types';

export const transformFlappingSettingsResponse = ({
look_back_window: lookBackWindow,
status_change_threshold: statusChangeThreshold,
created_at: createdAt,
created_by: createdBy,
updated_at: updatedAt,
updated_by: updatedBy,
...rest
}: AsApiContract<RulesSettingsFlapping>): RulesSettingsFlapping => ({
...rest,
lookBackWindow,
statusChangeThreshold,
createdAt,
createdBy,
updatedAt,
updatedBy,
});
Original file line number Diff line number Diff line change
@@ -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".
*/

// Feature flag for frontend rule specific flapping in rule flyout
export const IS_RULE_SPECIFIC_FLAPPING_ENABLED = false;
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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, { FunctionComponent } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook } from '@testing-library/react-hooks';
import { testQueryClientConfig } from '../test_utils/test_query_client_config';
import { useFetchFlappingSettings } from './use_fetch_flapping_settings';
import { httpServiceMock } from '@kbn/core-http-browser-mocks';

const queryClient = new QueryClient(testQueryClientConfig);

const wrapper: FunctionComponent<React.PropsWithChildren<{}>> = ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);

const http = httpServiceMock.createStartContract();

const now = new Date().toISOString();

describe('useFetchFlappingSettings', () => {
beforeEach(() => {
http.get.mockResolvedValue({
created_by: 'test',
updated_by: 'test',
created_at: now,
updated_at: now,
enabled: true,
look_back_window: 20,
status_change_threshold: 20,
});
});

afterEach(() => {
jest.resetAllMocks();
queryClient.clear();
});

test('should call fetchFlappingSettings with the correct parameters', async () => {
const { result, waitFor } = renderHook(
() => useFetchFlappingSettings({ http, enabled: true }),
{
wrapper,
}
);

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

expect(result.current.data).toEqual({
createdAt: now,
createdBy: 'test',
updatedAt: now,
updatedBy: 'test',
enabled: true,
lookBackWindow: 20,
statusChangeThreshold: 20,
});
});

test('should not call fetchFlappingSettings if enabled is false', async () => {
const { result, waitFor } = renderHook(
() => useFetchFlappingSettings({ http, enabled: false }),
{
wrapper,
}
);

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

expect(http.get).not.toHaveBeenCalled();
});

test('should call onSuccess when the fetching was successful', async () => {
const onSuccessMock = jest.fn();
const { result, waitFor } = renderHook(
() => useFetchFlappingSettings({ http, enabled: true, onSuccess: onSuccessMock }),
{
wrapper,
}
);

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

expect(onSuccessMock).toHaveBeenCalledWith({
createdAt: now,
createdBy: 'test',
updatedAt: now,
updatedBy: 'test',
enabled: true,
lookBackWindow: 20,
statusChangeThreshold: 20,
});
});
});
Original file line number Diff line number Diff line change
@@ -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", 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 { useQuery } from '@tanstack/react-query';
import { HttpStart } from '@kbn/core-http-browser';
import { RulesSettingsFlapping } from '@kbn/alerting-types/rule_settings';
import { fetchFlappingSettings } from '../apis/fetch_flapping_settings';

interface UseFetchFlappingSettingsProps {
http: HttpStart;
enabled: boolean;
onSuccess?: (settings: RulesSettingsFlapping) => void;
}

export const useFetchFlappingSettings = (props: UseFetchFlappingSettingsProps) => {
const { http, enabled, onSuccess } = props;

const queryFn = () => {
return fetchFlappingSettings({ http });
};

const { data, isFetching, isError, isLoadingError, isLoading, isInitialLoading } = useQuery({
queryKey: ['fetchFlappingSettings'],
queryFn,
onSuccess,
enabled,
refetchOnWindowFocus: false,
retry: false,
});

return {
isInitialLoading,
isLoading: isLoading || isFetching,
isError: isError || isLoadingError,
data,
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
connectors,
connectorTypes,
aadTemplateFields,
flappingSettings,
} = useLoadDependencies({
http,
toasts: notifications.toasts,
Expand All @@ -117,6 +118,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
actions: newFormData.actions,
notifyWhen: newFormData.notifyWhen,
alertDelay: newFormData.alertDelay,
flapping: newFormData.flapping,
},
});
},
Expand Down Expand Up @@ -173,6 +175,7 @@ export const CreateRuleForm = (props: CreateRuleFormProps) => {
selectedRuleTypeModel: ruleTypeModel,
selectedRuleType: ruleType,
validConsumers,
flappingSettings,
canShowConsumerSelection,
showMustacheAutocompleteSwitch,
multiConsumerSelection: getInitialMultiConsumer({
Expand Down
Loading

0 comments on commit 447617e

Please sign in to comment.