Skip to content

Commit

Permalink
[Tech Debt] Refactor Rule Details page (elastic#160229)
Browse files Browse the repository at this point in the history
Co-authored-by: Kibana Machine <[email protected]>
  • Loading branch information
CoenWarmer and kibanamachine authored Jun 30, 2023
1 parent aec3a4b commit ef661d1
Show file tree
Hide file tree
Showing 28 changed files with 1,018 additions and 921 deletions.
63 changes: 63 additions & 0 deletions x-pack/plugins/observability/public/hooks/use_delete_rules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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 { useMutation } from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import type { KueryNode } from '@kbn/es-query';
import { INTERNAL_BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common';
import { useKibana } from '../utils/kibana_react';

export function useDeleteRules() {
const {
http,
notifications: { toasts },
} = useKibana().services;

const deleteRules = useMutation<
string,
string,
{ ids: string[]; filter?: KueryNode | null | undefined }
>(
['deleteRules'],
({ ids, filter }) => {
try {
const body = JSON.stringify({
...(ids?.length ? { ids } : {}),
...(filter ? { filter: JSON.stringify(filter) } : {}),
});
return http.patch(`${INTERNAL_BASE_ALERTING_API_PATH}/rules/_bulk_delete`, { body });
} catch (e) {
throw new Error(`Unable to parse bulk delete params: ${e}`);
}
},
{
onError: (_err, rule, context) => {
toasts.addDanger(
i18n.translate(
'xpack.observability.rules.deleteConfirmationModal.errorNotification.descriptionText',
{
defaultMessage: 'Failed to delete rule',
}
)
);
},

onSuccess: () => {
toasts.addSuccess(
i18n.translate(
'xpack.observability.rules.deleteConfirmationModal.successNotification.descriptionText',
{
defaultMessage: 'Deleted rule',
}
)
);
},
}
);

return deleteRules;
}
106 changes: 71 additions & 35 deletions x-pack/plugins/observability/public/hooks/use_fetch_rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,80 @@
* 2.0.
*/

import { useEffect, useState, useCallback } from 'react';
import { loadRule } from '@kbn/triggers-actions-ui-plugin/public';
import { FetchRuleProps, FetchRule } from '../pages/rule_details/types';
import { RULE_LOAD_ERROR } from '../pages/rule_details/translations';
import {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
useQuery,
} from '@tanstack/react-query';
import { i18n } from '@kbn/i18n';
import { INTERNAL_BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common';
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
import type { AsApiContract } from '@kbn/actions-plugin/common';
import { transformRule } from '@kbn/triggers-actions-ui-plugin/public';
import { useKibana } from '../utils/kibana_react';

export function useFetchRule({ ruleId, http }: FetchRuleProps) {
const [ruleSummary, setRuleSummary] = useState<FetchRule>({
isRuleLoading: true,
rule: undefined,
errorRule: undefined,
});
export interface UseFetchRuleResponse {
isInitialLoading: boolean;
isLoading: boolean;
isRefetching: boolean;
isSuccess: boolean;
isError: boolean;
rule: Rule | undefined;
refetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<Rule | undefined, unknown>>;
}

export function useFetchRule({ ruleId }: { ruleId?: string }): UseFetchRuleResponse {
const {
http,
notifications: { toasts },
} = useKibana().services;

const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
{
queryKey: ['fetchRule', ruleId],
queryFn: async ({ signal }) => {
try {
if (!ruleId) return;

const fetchRuleSummary = useCallback(async () => {
try {
if (!ruleId) return;
const rule = await loadRule({
http,
ruleId,
});
const res = await http.get<AsApiContract<Rule>>(
`${INTERNAL_BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(ruleId)}`,
{
signal,
}
);

setRuleSummary((oldState: FetchRule) => ({
...oldState,
isRuleLoading: false,
rule,
}));
} catch (error) {
setRuleSummary((oldState: FetchRule) => ({
...oldState,
isRuleLoading: false,
errorRule: RULE_LOAD_ERROR(
error instanceof Error ? error.message : typeof error === 'string' ? error : ''
),
}));
return transformRule(res);
} catch (error) {
throw error;
}
},
keepPreviousData: true,
enabled: Boolean(ruleId),
refetchOnWindowFocus: false,
onError: (error: Error) => {
toasts.addError(error, {
title: i18n.translate('xpack.observability.ruleDetails.ruleLoadError', {
defaultMessage: 'Unable to load rule. Reason: {message}',
values: {
message:
error instanceof Error ? error.message : typeof error === 'string' ? error : '',
},
}),
});
},
}
}, [ruleId, http]);
useEffect(() => {
fetchRuleSummary();
}, [fetchRuleSummary]);
);

return { ...ruleSummary, reloadRule: fetchRuleSummary };
return {
rule: data,
isLoading,
isInitialLoading,
isRefetching,
isSuccess,
isError,
refetch,
};
}

This file was deleted.

89 changes: 89 additions & 0 deletions x-pack/plugins/observability/public/hooks/use_fetch_rule_types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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 {
QueryObserverResult,
RefetchOptions,
RefetchQueryFilters,
useQuery,
} from '@tanstack/react-query';
import { camelCase, mapKeys } from 'lodash';
import { i18n } from '@kbn/i18n';
import { BASE_ALERTING_API_PATH } from '@kbn/alerting-plugin/common';
import type { RuleType } from '@kbn/triggers-actions-ui-plugin/public';
import type { AsApiContract } from '@kbn/actions-plugin/common';
import { useKibana } from '../utils/kibana_react';

export interface UseFetchRuleTypesResponse {
isInitialLoading: boolean;
isLoading: boolean;
isRefetching: boolean;
isSuccess: boolean;
isError: boolean;
ruleTypes: RuleType[] | undefined;
refetch: <TPageData>(
options?: (RefetchOptions & RefetchQueryFilters<TPageData>) | undefined
) => Promise<QueryObserverResult<RuleType[] | undefined, unknown>>;
}

export function useFetchRuleTypes({
filterByRuleTypeIds,
}: {
filterByRuleTypeIds?: string[] | undefined;
}): UseFetchRuleTypesResponse {
const {
http,
notifications: { toasts },
} = useKibana().services;

const { isInitialLoading, isLoading, isError, isSuccess, isRefetching, data, refetch } = useQuery(
{
queryKey: ['fetchRuleTypes', filterByRuleTypeIds],
queryFn: async ({ signal }) => {
try {
const res = await http.get<Array<AsApiContract<RuleType<string, string>>>>(
`${BASE_ALERTING_API_PATH}/rule_types`,
{ signal }
);

const response = res.map((item) => {
return mapKeys(item, (_, k) => camelCase(k));
}) as unknown as Array<RuleType<string, string>>;

return filterByRuleTypeIds && filterByRuleTypeIds.length > 0
? response.filter((item) => filterByRuleTypeIds.includes(item.id))
: response;
} catch (error) {
throw error;
}
},
keepPreviousData: true,
refetchOnWindowFocus: false,
onError: (error: Error) => {
toasts.addError(error, {
title: i18n.translate('xpack.observability.ruleDetails.ruleLoadError', {
defaultMessage: 'Unable to load rule. Reason: {message}',
values: {
message:
error instanceof Error ? error.message : typeof error === 'string' ? error : '',
},
}),
});
},
}
);

return {
ruleTypes: data,
isLoading,
isInitialLoading,
isRefetching,
isSuccess,
isError,
refetch,
};
}
17 changes: 13 additions & 4 deletions x-pack/plugins/observability/public/locators/rule_details.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
*/

import { ACTIVE_ALERTS } from '../components/alert_search_bar/constants';
import { EXECUTION_TAB, ALERTS_TAB } from '../pages/rule_details/constants';
import {
RULE_DETAILS_EXECUTION_TAB,
RULE_DETAILS_ALERTS_TAB,
} from '../pages/rule_details/constants';
import { getRuleDetailsPath, RuleDetailsLocatorDefinition } from './rule_details';
import { RULES_PATH } from '../routes/paths';

Expand All @@ -21,12 +24,18 @@ describe('RuleDetailsLocator', () => {
});

it('should return correct url when tabId is execution', async () => {
const location = await locator.getLocation({ ruleId: mockedRuleId, tabId: EXECUTION_TAB });
const location = await locator.getLocation({
ruleId: mockedRuleId,
tabId: RULE_DETAILS_EXECUTION_TAB,
});
expect(location.path).toEqual(`${RULES_PATH}/${mockedRuleId}?tabId=execution`);
});

it('should return correct url when tabId is alerts without extra search params', async () => {
const location = await locator.getLocation({ ruleId: mockedRuleId, tabId: ALERTS_TAB });
const location = await locator.getLocation({
ruleId: mockedRuleId,
tabId: RULE_DETAILS_ALERTS_TAB,
});
expect(location.path).toEqual(
`${RULES_PATH}/${mockedRuleId}?tabId=alerts&searchBarParams=(kuery:'',rangeFrom:now-15m,rangeTo:now,status:all)`
);
Expand All @@ -35,7 +44,7 @@ describe('RuleDetailsLocator', () => {
it('should return correct url when tabId is alerts with search params', async () => {
const location = await locator.getLocation({
ruleId: mockedRuleId,
tabId: ALERTS_TAB,
tabId: RULE_DETAILS_ALERTS_TAB,
rangeFrom: 'mockedRangeTo',
rangeTo: 'mockedRangeFrom',
kuery: 'mockedKuery',
Expand Down
14 changes: 7 additions & 7 deletions x-pack/plugins/observability/public/locators/rule_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { ruleDetailsLocatorID } from '../../common';
import { RULES_PATH } from '../routes/paths';
import { ALL_ALERTS } from '../components/alert_search_bar/constants';
import {
ALERTS_TAB,
EXECUTION_TAB,
SEARCH_BAR_URL_STORAGE_KEY,
RULE_DETAILS_ALERTS_TAB,
RULE_DETAILS_EXECUTION_TAB,
RULE_DETAILS_SEARCH_BAR_URL_STORAGE_KEY,
} from '../pages/rule_details/constants';
import type { TabId } from '../pages/rule_details/types';
import type { TabId } from '../pages/rule_details/rule_details';
import type { AlertStatus } from '../../common/typings';

export interface RuleDetailsLocatorParams extends SerializableRecord {
Expand Down Expand Up @@ -52,15 +52,15 @@ export class RuleDetailsLocatorDefinition implements LocatorDefinition<RuleDetai

let path = getRuleDetailsPath(ruleId);

if (tabId === ALERTS_TAB) {
if (tabId === RULE_DETAILS_ALERTS_TAB) {
path = `${path}?tabId=${tabId}`;
path = setStateToKbnUrl(
SEARCH_BAR_URL_STORAGE_KEY,
RULE_DETAILS_SEARCH_BAR_URL_STORAGE_KEY,
appState,
{ useHash: false, storeInHashQuery: false },
path
);
} else if (tabId === EXECUTION_TAB) {
} else if (tabId === RULE_DETAILS_EXECUTION_TAB) {
path = `${path}?tabId=${tabId}`;
}

Expand Down
Loading

0 comments on commit ef661d1

Please sign in to comment.