mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
feat(slo): delete SLO instance (#182190)
This commit is contained in:
parent
5b4142c01b
commit
63ad54689f
16 changed files with 314 additions and 172 deletions
|
@ -9,7 +9,14 @@ import * as t from 'io-ts';
|
|||
import { sloIdSchema } from '../../schema/slo';
|
||||
|
||||
const deleteSLOInstancesParamsSchema = t.type({
|
||||
body: t.type({ list: t.array(t.type({ sloId: sloIdSchema, instanceId: t.string })) }),
|
||||
body: t.type({
|
||||
list: t.array(
|
||||
t.intersection([
|
||||
t.type({ sloId: sloIdSchema, instanceId: t.string }),
|
||||
t.partial({ excludeRollup: t.boolean }),
|
||||
])
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
type DeleteSLOInstancesInput = t.OutputOf<typeof deleteSLOInstancesParamsSchema.props.body>;
|
||||
|
|
|
@ -5,15 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
|
||||
import { KibanaReactStorybookDecorator } from '@kbn/observability-plugin/public';
|
||||
import {
|
||||
SloDeleteConfirmationModal as Component,
|
||||
SloDeleteConfirmationModalProps,
|
||||
} from './slo_delete_confirmation_modal';
|
||||
import { ComponentStory } from '@storybook/react';
|
||||
import React from 'react';
|
||||
import { buildSlo } from '../../../data/slo/slo';
|
||||
import { Props, SloDeleteModal as Component } from './slo_delete_confirmation_modal';
|
||||
|
||||
export default {
|
||||
component: Component,
|
||||
|
@ -21,9 +17,7 @@ export default {
|
|||
decorators: [KibanaReactStorybookDecorator],
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Component> = (props: SloDeleteConfirmationModalProps) => (
|
||||
<Component {...props} />
|
||||
);
|
||||
const Template: ComponentStory<typeof Component> = (props: Props) => <Component {...props} />;
|
||||
|
||||
const defaultProps = {
|
||||
slo: buildSlo(),
|
||||
|
|
|
@ -5,50 +5,158 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiConfirmModal } from '@elastic/eui';
|
||||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiFormRow,
|
||||
EuiModal,
|
||||
EuiModalBody,
|
||||
EuiModalFooter,
|
||||
EuiModalHeader,
|
||||
EuiModalHeaderTitle,
|
||||
EuiSwitch,
|
||||
useGeneratedHtmlId,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { ALL_VALUE, SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React from 'react';
|
||||
import { getGroupKeysProse } from '../../../utils/slo/groupings';
|
||||
import React, { useState } from 'react';
|
||||
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
|
||||
import { useDeleteSloInstance } from '../../../hooks/use_delete_slo_instance';
|
||||
|
||||
export interface SloDeleteConfirmationModalProps {
|
||||
export interface Props {
|
||||
slo: SLOWithSummaryResponse | SLODefinitionResponse;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
onSuccess: () => void;
|
||||
}
|
||||
|
||||
export function SloDeleteConfirmationModal({
|
||||
slo,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
}: SloDeleteConfirmationModalProps) {
|
||||
export function SloDeleteModal({ slo, onCancel, onSuccess }: Props) {
|
||||
const { name, groupBy } = slo;
|
||||
const instanceId =
|
||||
'instanceId' in slo && slo.instanceId !== ALL_VALUE ? slo.instanceId : undefined;
|
||||
const hasGroupBy = [groupBy].flat().some((group) => group !== ALL_VALUE);
|
||||
|
||||
const modalTitleId = useGeneratedHtmlId();
|
||||
|
||||
const { mutateAsync: deleteSloInstance, isLoading: isDeleteInstanceLoading } =
|
||||
useDeleteSloInstance();
|
||||
const { mutateAsync: deleteSlo, isLoading: isDeleteLoading } = useDeleteSlo();
|
||||
|
||||
const [isDeleteRollupDataChecked, toggleDeleteRollupDataSwitch] = useState<boolean>(false);
|
||||
const onDeleteRollupDataSwitchChange = () =>
|
||||
toggleDeleteRollupDataSwitch(!isDeleteRollupDataChecked);
|
||||
|
||||
const handleDeleteInstance = async () => {
|
||||
// @ts-ignore
|
||||
await deleteSloInstance({ slo, excludeRollup: isDeleteRollupDataChecked === false });
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
const handleDeleteAll = async () => {
|
||||
await deleteSlo({ id: slo.id, name: slo.name });
|
||||
onSuccess();
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiConfirmModal
|
||||
buttonColor="danger"
|
||||
data-test-subj="sloDeleteConfirmationModal"
|
||||
title={i18n.translate('xpack.slo.deleteConfirmationModal.title', {
|
||||
defaultMessage: 'Delete {name}?',
|
||||
values: { name },
|
||||
})}
|
||||
cancelButtonText={i18n.translate('xpack.slo.deleteConfirmationModal.cancelButtonLabel', {
|
||||
defaultMessage: 'Cancel',
|
||||
})}
|
||||
confirmButtonText={i18n.translate('xpack.slo.deleteConfirmationModal.deleteButtonLabel', {
|
||||
defaultMessage: 'Delete',
|
||||
})}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirm}
|
||||
>
|
||||
{groupBy !== ALL_VALUE
|
||||
? i18n.translate('xpack.slo.deleteConfirmationModal.groupByDisclaimerText', {
|
||||
defaultMessage:
|
||||
'This SLO has been generated with a group key on {groupKey}. Deleting this SLO definition will result in all instances being deleted.',
|
||||
values: { groupKey: getGroupKeysProse(slo.groupBy) },
|
||||
})
|
||||
: i18n.translate('xpack.slo.deleteConfirmationModal.descriptionText', {
|
||||
defaultMessage: "You can't recover this SLO after deleting it.",
|
||||
})}
|
||||
</EuiConfirmModal>
|
||||
<EuiModal aria-labelledby={modalTitleId} onClose={onCancel}>
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle id={modalTitleId}>
|
||||
{hasGroupBy && instanceId ? getInstanceTitleLabel(name, instanceId) : getTitleLabel(name)}
|
||||
</EuiModalHeaderTitle>
|
||||
</EuiModalHeader>
|
||||
<EuiModalBody>
|
||||
{hasGroupBy && instanceId ? (
|
||||
<EuiForm component="form">
|
||||
<EuiFlexItem grow style={{ marginBottom: '2rem' }}>
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.groupByDisclaimerText"
|
||||
defaultMessage="This SLO is an instance of many SLO instances."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFormRow>
|
||||
<EuiSwitch
|
||||
name="popswitch"
|
||||
label={i18n.translate('xpack.slo.deleteConfirmationModal.switchDeleteRollupData', {
|
||||
defaultMessage: 'Delete rollup data for this instance',
|
||||
})}
|
||||
checked={isDeleteRollupDataChecked}
|
||||
onChange={onDeleteRollupDataSwitchChange}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.descriptionText"
|
||||
defaultMessage="You can't recover this SLO after deleting it."
|
||||
/>
|
||||
)}
|
||||
</EuiModalBody>
|
||||
<EuiModalFooter>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="observabilitySolutionSloDeleteModalCancelButton"
|
||||
onClick={onCancel}
|
||||
disabled={isDeleteLoading || isDeleteInstanceLoading}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
|
||||
{hasGroupBy && instanceId && (
|
||||
<EuiButton
|
||||
data-test-subj="observabilitySolutionSloDeleteModalConfirmButton"
|
||||
type="submit"
|
||||
color="danger"
|
||||
onClick={handleDeleteInstance}
|
||||
disabled={isDeleteLoading || isDeleteInstanceLoading}
|
||||
fill
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.deleteInstanceButtonLabel"
|
||||
defaultMessage="Delete {instanceId}"
|
||||
values={{ instanceId }}
|
||||
/>
|
||||
</EuiButton>
|
||||
)}
|
||||
|
||||
<EuiButton
|
||||
data-test-subj="observabilitySolutionSloDeleteModalConfirmButton"
|
||||
type="submit"
|
||||
color="danger"
|
||||
onClick={handleDeleteAll}
|
||||
disabled={isDeleteLoading || isDeleteInstanceLoading}
|
||||
fill
|
||||
>
|
||||
{hasGroupBy && instanceId ? (
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.deleteAllButtonLabel"
|
||||
defaultMessage="Delete SLO and all Instances"
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.slo.deleteConfirmationModal.deleteButtonLabel"
|
||||
defaultMessage="Delete"
|
||||
/>
|
||||
)}
|
||||
</EuiButton>
|
||||
</EuiModalFooter>
|
||||
</EuiModal>
|
||||
);
|
||||
}
|
||||
|
||||
function getTitleLabel(name: string): React.ReactNode {
|
||||
return i18n.translate('xpack.slo.deleteConfirmationModal.title', {
|
||||
defaultMessage: 'Delete {name}?',
|
||||
values: { name },
|
||||
});
|
||||
}
|
||||
|
||||
function getInstanceTitleLabel(name: string, instanceId?: string): React.ReactNode {
|
||||
return i18n.translate('xpack.slo.deleteConfirmationModal.instanceTitle', {
|
||||
defaultMessage: 'Delete {name} [{instanceId}]?',
|
||||
values: { name, instanceId },
|
||||
});
|
||||
}
|
||||
|
|
|
@ -7,8 +7,7 @@
|
|||
|
||||
import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FindSLOResponse } from '@kbn/slo-schema';
|
||||
import { QueryKey, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
import { sloKeys } from './query_key_factory';
|
||||
|
||||
|
@ -21,12 +20,7 @@ export function useDeleteSlo() {
|
|||
} = useKibana().services;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<
|
||||
string,
|
||||
ServerError,
|
||||
{ id: string; name: string },
|
||||
{ previousData?: FindSLOResponse; queryKey?: QueryKey }
|
||||
>(
|
||||
return useMutation<string, ServerError, { id: string; name: string }>(
|
||||
['deleteSlo'],
|
||||
({ id }) => {
|
||||
try {
|
||||
|
@ -36,7 +30,7 @@ export function useDeleteSlo() {
|
|||
}
|
||||
},
|
||||
{
|
||||
onError: (error, { name }, context) => {
|
||||
onError: (error, { name }) => {
|
||||
toasts.addError(new Error(error.body?.message ?? error.message), {
|
||||
title: i18n.translate('xpack.slo.slo.delete.errorNotification', {
|
||||
defaultMessage: 'Failed to delete {name}',
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useKibana } from '../utils/kibana_react';
|
||||
import { sloKeys } from './query_key_factory';
|
||||
|
||||
type ServerError = IHttpFetchError<ResponseErrorBody>;
|
||||
|
||||
export function useDeleteSloInstance() {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<string, ServerError, { slo: SLOWithSummaryResponse; excludeRollup: boolean }>(
|
||||
['deleteSloInstance'],
|
||||
({ slo, excludeRollup }) => {
|
||||
try {
|
||||
return http.post(`/api/observability/slos/_delete_instances`, {
|
||||
body: JSON.stringify({
|
||||
list: [
|
||||
{
|
||||
sloId: slo.id,
|
||||
instanceId: slo.instanceId,
|
||||
excludeRollup,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
return Promise.reject(`Something went wrong: ${String(error)}`);
|
||||
}
|
||||
},
|
||||
{
|
||||
onError: (error, { slo }) => {
|
||||
toasts.addError(new Error(error.body?.message ?? error.message), {
|
||||
title: i18n.translate('xpack.slo.deleteInstance.errorNotification', {
|
||||
defaultMessage: 'Failed to delete {name} [instance: {instanceId}]',
|
||||
values: { name: slo.name, instanceId: slo.instanceId },
|
||||
}),
|
||||
});
|
||||
},
|
||||
onSuccess: (_data, { slo }) => {
|
||||
queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false });
|
||||
|
||||
toasts.addSuccess(
|
||||
i18n.translate('xpack.slo.slo.deleteInstance.successNotification', {
|
||||
defaultMessage: 'Deleted {name} [instance: {instanceId}]',
|
||||
values: { name: slo.name, instanceId: slo.instanceId },
|
||||
})
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
|
@ -18,11 +18,10 @@ import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils';
|
|||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { paths } from '../../../../common/locators/paths';
|
||||
import { SloDeleteConfirmationModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloDeleteModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloResetConfirmationModal } from '../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { useCapabilities } from '../../../hooks/use_capabilities';
|
||||
import { useCloneSlo } from '../../../hooks/use_clone_slo';
|
||||
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
|
||||
import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo';
|
||||
import { useResetSlo } from '../../../hooks/use_reset_slo';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
|
@ -56,7 +55,6 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
|
||||
const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false);
|
||||
|
||||
const { mutate: deleteSlo } = useDeleteSlo();
|
||||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
|
||||
const { data: rulesBySlo, refetchRules } = useFetchRulesForSlo({
|
||||
|
@ -127,11 +125,10 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (slo) {
|
||||
deleteSlo({ id: slo.id, name: slo.name });
|
||||
navigate(basePath.prepend(paths.slos));
|
||||
}
|
||||
const handleDeleteConfirm = async () => {
|
||||
removeDeleteQueryParam();
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
navigate(basePath.prepend(paths.slos));
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
|
@ -330,11 +327,7 @@ export function HeaderControl({ isLoading, slo }: Props) {
|
|||
) : null}
|
||||
|
||||
{slo && isDeleteConfirmationModalOpen ? (
|
||||
<SloDeleteConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
<SloDeleteModal slo={slo} onCancel={handleDeleteCancel} onSuccess={handleDeleteConfirm} />
|
||||
) : null}
|
||||
|
||||
{slo && isResetConfirmationModalOpen ? (
|
||||
|
|
|
@ -23,6 +23,7 @@ import { buildSlo } from '../../data/slo/slo';
|
|||
import { ActiveAlerts } from '../../hooks/active_alerts';
|
||||
import { useCapabilities } from '../../hooks/use_capabilities';
|
||||
import { useDeleteSlo } from '../../hooks/use_delete_slo';
|
||||
import { useDeleteSloInstance } from '../../hooks/use_delete_slo_instance';
|
||||
import { useFetchActiveAlerts } from '../../hooks/use_fetch_active_alerts';
|
||||
import { useFetchHistoricalSummary } from '../../hooks/use_fetch_historical_summary';
|
||||
import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details';
|
||||
|
@ -45,6 +46,7 @@ jest.mock('../../hooks/use_fetch_active_alerts');
|
|||
jest.mock('../../hooks/use_fetch_slo_details');
|
||||
jest.mock('../../hooks/use_fetch_historical_summary');
|
||||
jest.mock('../../hooks/use_delete_slo');
|
||||
jest.mock('../../hooks/use_delete_slo_instance');
|
||||
|
||||
const useKibanaMock = useKibana as jest.Mock;
|
||||
|
||||
|
@ -54,6 +56,7 @@ const useFetchActiveAlertsMock = useFetchActiveAlerts as jest.Mock;
|
|||
const useFetchSloDetailsMock = useFetchSloDetails as jest.Mock;
|
||||
const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock;
|
||||
const useDeleteSloMock = useDeleteSlo as jest.Mock;
|
||||
const useDeleteSloInstanceMock = useDeleteSloInstance as jest.Mock;
|
||||
const TagsListMock = TagsList as jest.Mock;
|
||||
TagsListMock.mockReturnValue(<div>Tags list</div>);
|
||||
|
||||
|
@ -63,6 +66,7 @@ HeaderMenuPortalMock.mockReturnValue(<div>Portal node</div>);
|
|||
const mockNavigate = jest.fn();
|
||||
const mockLocator = jest.fn();
|
||||
const mockDelete = jest.fn();
|
||||
const mockDeleteInstance = jest.fn();
|
||||
const mockCapabilities = {
|
||||
apm: { show: true },
|
||||
} as unknown as Capabilities;
|
||||
|
@ -133,7 +137,8 @@ describe('SLO Details Page', () => {
|
|||
data: historicalSummaryData,
|
||||
});
|
||||
useFetchActiveAlertsMock.mockReturnValue({ isLoading: false, data: new ActiveAlerts() });
|
||||
useDeleteSloMock.mockReturnValue({ mutate: mockDelete });
|
||||
useDeleteSloMock.mockReturnValue({ mutateAsync: mockDelete });
|
||||
useDeleteSloInstanceMock.mockReturnValue({ mutateAsync: mockDeleteInstance });
|
||||
jest
|
||||
.spyOn(Router, 'useLocation')
|
||||
.mockReturnValue({ pathname: '/slos/1234', search: '', state: '', hash: '' });
|
||||
|
@ -287,7 +292,9 @@ describe('SLO Details Page', () => {
|
|||
|
||||
fireEvent.click(button!);
|
||||
|
||||
const deleteModalConfirmButton = screen.queryByTestId('confirmModalConfirmButton');
|
||||
const deleteModalConfirmButton = screen.queryByTestId(
|
||||
'observabilitySolutionSloDeleteModalConfirmButton'
|
||||
);
|
||||
|
||||
fireEvent.click(deleteModalConfirmButton!);
|
||||
|
||||
|
|
|
@ -5,33 +5,32 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiLoadingSpinner, EuiSkeletonText } from '@elastic/eui';
|
||||
import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
|
||||
import type { IBasePath } from '@kbn/core-http-browser';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
import type { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useIsMutating } from '@tanstack/react-query';
|
||||
import dedent from 'dedent';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useIsMutating } from '@tanstack/react-query';
|
||||
import { EuiLoadingSpinner, EuiSkeletonText } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { IBasePath } from '@kbn/core-http-browser';
|
||||
import type { ChromeBreadcrumb } from '@kbn/core-chrome-browser';
|
||||
import type { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
import dedent from 'dedent';
|
||||
import { useSelectedTab } from './hooks/use_selected_tab';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
import { HeaderMenu } from '../../components/header_menu/header_menu';
|
||||
import { useSloDetailsTabs } from './hooks/use_slo_details_tabs';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { AutoRefreshButton } from '../../components/slo/auto_refresh_button';
|
||||
import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage';
|
||||
import { useFetchSloDetails } from '../../hooks/use_fetch_slo_details';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import PageNotFound from '../404';
|
||||
import { SloDetails } from './components/slo_details';
|
||||
import { HeaderTitle } from './components/header_title';
|
||||
import { HeaderControl } from './components/header_control';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
import type { SloDetailsPathParams } from './types';
|
||||
import { AutoRefreshButton } from '../../components/slo/auto_refresh_button';
|
||||
import { HeaderTitle } from './components/header_title';
|
||||
import { SloDetails } from './components/slo_details';
|
||||
import { useGetQueryParams } from './hooks/use_get_query_params';
|
||||
import { useAutoRefreshStorage } from '../../components/slo/auto_refresh_button/hooks/use_auto_refresh_storage';
|
||||
import { useSelectedTab } from './hooks/use_selected_tab';
|
||||
import { useSloDetailsTabs } from './hooks/use_slo_details_tabs';
|
||||
import type { SloDetailsPathParams } from './types';
|
||||
|
||||
export function SloDetailsPage() {
|
||||
const {
|
||||
|
|
|
@ -8,9 +8,8 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elasti
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { SLODefinitionResponse } from '@kbn/slo-schema';
|
||||
import React, { useState } from 'react';
|
||||
import { SloDeleteConfirmationModal } from '../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloDeleteModal } from '../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloResetConfirmationModal } from '../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { useDeleteSlo } from '../../hooks/use_delete_slo';
|
||||
import { useResetSlo } from '../../hooks/use_reset_slo';
|
||||
import { SloIndicatorTypeBadge } from '../slos/components/badges/slo_indicator_type_badge';
|
||||
import { SloTimeWindowBadge } from '../slos/components/badges/slo_time_window_badge';
|
||||
|
@ -23,7 +22,6 @@ interface OutdatedSloProps {
|
|||
|
||||
export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
|
||||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
const { mutateAsync: deleteSlo, isLoading: isDeleteLoading } = useDeleteSlo();
|
||||
const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false);
|
||||
const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false);
|
||||
|
||||
|
@ -33,7 +31,6 @@ export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
|
|||
|
||||
const handleDeleteConfirm = async () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
await deleteSlo({ id: slo.id, name: slo.name });
|
||||
onDelete();
|
||||
};
|
||||
|
||||
|
@ -96,7 +93,6 @@ export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
|
|||
data-test-subj="o11ySlosOutdatedDefinitionsDeleteButton"
|
||||
color="danger"
|
||||
fill
|
||||
isLoading={isDeleteLoading}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -107,11 +103,7 @@ export function OutdatedSlo({ slo, onReset, onDelete }: OutdatedSloProps) {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{isDeleteConfirmationModalOpen ? (
|
||||
<SloDeleteConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
<SloDeleteModal slo={slo} onCancel={handleDeleteCancel} onSuccess={handleDeleteConfirm} />
|
||||
) : null}
|
||||
{isResetConfirmationModalOpen ? (
|
||||
<SloResetConfirmationModal
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { Rule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import React, { useState } from 'react';
|
||||
import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloDeleteModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { useResetSlo } from '../../../../hooks/use_reset_slo';
|
||||
import { BurnRateRuleParams } from '../../../../typings';
|
||||
|
@ -83,13 +83,15 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
|
|||
|
||||
const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value');
|
||||
|
||||
const { handleCreateRule, handleDeleteCancel, handleDeleteConfirm, handleAttachToDashboardSave } =
|
||||
useSloListActions({
|
||||
slo,
|
||||
setDeleteConfirmationModalOpen,
|
||||
setIsActionsPopoverOpen,
|
||||
setIsAddRuleFlyoutOpen,
|
||||
});
|
||||
const { handleCreateRule, handleAttachToDashboardSave } = useSloListActions({
|
||||
slo,
|
||||
setIsActionsPopoverOpen,
|
||||
setIsAddRuleFlyoutOpen,
|
||||
});
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
|
||||
|
@ -166,11 +168,7 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet
|
|||
/>
|
||||
|
||||
{isDeleteConfirmationModalOpen ? (
|
||||
<SloDeleteConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
<SloDeleteModal slo={slo} onCancel={closeDeleteModal} onSuccess={closeDeleteModal} />
|
||||
) : null}
|
||||
|
||||
{isResetConfirmationModalOpen ? (
|
||||
|
|
|
@ -23,14 +23,13 @@ import { useQueryClient } from '@tanstack/react-query';
|
|||
import React, { useState } from 'react';
|
||||
import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n';
|
||||
import { paths } from '../../../../../common/locators/paths';
|
||||
import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloDeleteModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { SloStatusBadge } from '../../../../components/slo/slo_status_badge';
|
||||
import { SloActiveAlertsBadge } from '../../../../components/slo/slo_status_badge/slo_active_alerts_badge';
|
||||
import { sloKeys } from '../../../../hooks/query_key_factory';
|
||||
import { useCapabilities } from '../../../../hooks/use_capabilities';
|
||||
import { useCloneSlo } from '../../../../hooks/use_clone_slo';
|
||||
import { useDeleteSlo } from '../../../../hooks/use_delete_slo';
|
||||
import { useFetchActiveAlerts } from '../../../../hooks/use_fetch_active_alerts';
|
||||
import { useFetchHistoricalSummary } from '../../../../hooks/use_fetch_historical_summary';
|
||||
import { useFetchRulesForSlo } from '../../../../hooks/use_fetch_rules_for_slo';
|
||||
|
@ -79,7 +78,6 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
|
|||
const filteredRuleTypes = useGetFilteredRuleTypes();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: deleteSlo } = useDeleteSlo();
|
||||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
|
||||
const [sloToAddRule, setSloToAddRule] = useState<SLOWithSummaryResponse | undefined>(undefined);
|
||||
|
@ -87,9 +85,6 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
|
|||
const [sloToReset, setSloToReset] = useState<SLOWithSummaryResponse | undefined>(undefined);
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
if (sloToDelete) {
|
||||
deleteSlo({ id: sloToDelete.id, name: sloToDelete.name });
|
||||
}
|
||||
setSloToDelete(undefined);
|
||||
};
|
||||
|
||||
|
@ -476,10 +471,10 @@ export function SloListCompactView({ sloList, loading, error }: Props) {
|
|||
) : null}
|
||||
|
||||
{sloToDelete ? (
|
||||
<SloDeleteConfirmationModal
|
||||
<SloDeleteModal
|
||||
slo={sloToDelete}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
onSuccess={handleDeleteConfirm}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiText } from '@elastic/eui';
|
|||
import { HistoricalSummaryResponse, SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import type { Rule } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import React, { useState } from 'react';
|
||||
import { SloDeleteConfirmationModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloDeleteModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal';
|
||||
import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal';
|
||||
import { useResetSlo } from '../../../../hooks/use_reset_slo';
|
||||
import { BurnRateRuleParams } from '../../../../typings';
|
||||
|
@ -48,13 +48,16 @@ export function SloListItem({
|
|||
const { mutateAsync: resetSlo, isLoading: isResetLoading } = useResetSlo();
|
||||
const { sloDetailsUrl } = useSloFormattedSummary(slo);
|
||||
|
||||
const { handleCreateRule, handleDeleteCancel, handleDeleteConfirm } = useSloListActions({
|
||||
const { handleCreateRule } = useSloListActions({
|
||||
slo,
|
||||
setDeleteConfirmationModalOpen,
|
||||
setIsActionsPopoverOpen,
|
||||
setIsAddRuleFlyoutOpen,
|
||||
});
|
||||
|
||||
const closeDeleteModal = () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
const handleResetConfirm = async () => {
|
||||
await resetSlo({ id: slo.id, name: slo.name });
|
||||
setResetConfirmationModalOpen(false);
|
||||
|
@ -63,6 +66,7 @@ export function SloListItem({
|
|||
const handleResetCancel = () => {
|
||||
setResetConfirmationModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj="sloItem" hasBorder hasShadow={false}>
|
||||
<EuiFlexGroup responsive={false} alignItems="center">
|
||||
|
@ -129,11 +133,7 @@ export function SloListItem({
|
|||
/>
|
||||
|
||||
{isDeleteConfirmationModalOpen ? (
|
||||
<SloDeleteConfirmationModal
|
||||
slo={slo}
|
||||
onCancel={handleDeleteCancel}
|
||||
onConfirm={handleDeleteConfirm}
|
||||
/>
|
||||
<SloDeleteModal slo={slo} onCancel={closeDeleteModal} onSuccess={closeDeleteModal} />
|
||||
) : null}
|
||||
|
||||
{isResetConfirmationModalOpen ? (
|
||||
|
|
|
@ -5,35 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { SaveModalDashboardProps } from '@kbn/presentation-util-plugin/public';
|
||||
import { SLOWithSummaryResponse } from '@kbn/slo-schema';
|
||||
import { useCallback } from 'react';
|
||||
import { useKibana } from '../../../utils/kibana_react';
|
||||
import { useDeleteSlo } from '../../../hooks/use_delete_slo';
|
||||
import { SLO_OVERVIEW_EMBEDDABLE_ID } from '../../../embeddable/slo/overview/constants';
|
||||
|
||||
export function useSloListActions({
|
||||
slo,
|
||||
setIsAddRuleFlyoutOpen,
|
||||
setIsActionsPopoverOpen,
|
||||
setDeleteConfirmationModalOpen,
|
||||
}: {
|
||||
slo: SLOWithSummaryResponse;
|
||||
setIsActionsPopoverOpen: (val: boolean) => void;
|
||||
setIsAddRuleFlyoutOpen: (val: boolean) => void;
|
||||
setDeleteConfirmationModalOpen: (val: boolean) => void;
|
||||
}) {
|
||||
const { embeddable } = useKibana().services;
|
||||
const { mutate: deleteSlo } = useDeleteSlo();
|
||||
|
||||
const handleDeleteConfirm = () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
deleteSlo({ id: slo.id, name: slo.name });
|
||||
};
|
||||
|
||||
const handleDeleteCancel = () => {
|
||||
setDeleteConfirmationModalOpen(false);
|
||||
};
|
||||
const handleCreateRule = () => {
|
||||
setIsActionsPopoverOpen(false);
|
||||
setIsAddRuleFlyoutOpen(true);
|
||||
|
@ -66,8 +54,6 @@ export function useSloListActions({
|
|||
);
|
||||
|
||||
return {
|
||||
handleDeleteConfirm,
|
||||
handleDeleteCancel,
|
||||
handleCreateRule,
|
||||
handleAttachToDashboardSave,
|
||||
};
|
||||
|
|
|
@ -18,6 +18,7 @@ import { emptySloList, sloList } from '../../data/slo/slo';
|
|||
import { useCapabilities } from '../../hooks/use_capabilities';
|
||||
import { useCreateSlo } from '../../hooks/use_create_slo';
|
||||
import { useDeleteSlo } from '../../hooks/use_delete_slo';
|
||||
import { useDeleteSloInstance } from '../../hooks/use_delete_slo_instance';
|
||||
import { useFetchHistoricalSummary } from '../../hooks/use_fetch_historical_summary';
|
||||
import { useFetchSloList } from '../../hooks/use_fetch_slo_list';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
|
@ -39,6 +40,7 @@ jest.mock('../../hooks/use_fetch_slo_list');
|
|||
jest.mock('../../hooks/use_create_slo');
|
||||
jest.mock('../slo_settings/use_get_settings');
|
||||
jest.mock('../../hooks/use_delete_slo');
|
||||
jest.mock('../../hooks/use_delete_slo_instance');
|
||||
jest.mock('../../hooks/use_fetch_historical_summary');
|
||||
jest.mock('../../hooks/use_capabilities');
|
||||
|
||||
|
@ -48,9 +50,11 @@ const useLicenseMock = useLicense as jest.Mock;
|
|||
const useFetchSloListMock = useFetchSloList as jest.Mock;
|
||||
const useCreateSloMock = useCreateSlo as jest.Mock;
|
||||
const useDeleteSloMock = useDeleteSlo as jest.Mock;
|
||||
const useDeleteSloInstanceMock = useDeleteSloInstance as jest.Mock;
|
||||
const useFetchHistoricalSummaryMock = useFetchHistoricalSummary as jest.Mock;
|
||||
const useCapabilitiesMock = useCapabilities as jest.Mock;
|
||||
const TagsListMock = TagsList as jest.Mock;
|
||||
|
||||
TagsListMock.mockReturnValue(<div>Tags list</div>);
|
||||
|
||||
const HeaderMenuPortalMock = HeaderMenuPortal as jest.Mock;
|
||||
|
@ -58,9 +62,11 @@ HeaderMenuPortalMock.mockReturnValue(<div>Portal node</div>);
|
|||
|
||||
const mockCreateSlo = jest.fn();
|
||||
const mockDeleteSlo = jest.fn();
|
||||
const mockDeleteInstance = jest.fn();
|
||||
|
||||
useCreateSloMock.mockReturnValue({ mutate: mockCreateSlo });
|
||||
useDeleteSloMock.mockReturnValue({ mutate: mockDeleteSlo });
|
||||
useDeleteSloMock.mockReturnValue({ mutateAsync: mockDeleteSlo });
|
||||
useDeleteSloInstanceMock.mockReturnValue({ mutateAsync: mockDeleteInstance });
|
||||
|
||||
const mockNavigate = jest.fn();
|
||||
const mockAddSuccess = jest.fn();
|
||||
|
@ -337,7 +343,7 @@ describe('SLOs Page', () => {
|
|||
|
||||
button.click();
|
||||
|
||||
screen.getByTestId('confirmModalConfirmButton').click();
|
||||
screen.getByTestId('observabilitySolutionSloDeleteModalConfirmButton').click();
|
||||
|
||||
expect(mockDeleteSlo).toBeCalledWith({
|
||||
id: sloList.results.at(0)?.id,
|
||||
|
|
|
@ -5,21 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { HeaderMenu } from '../../components/header_menu/header_menu';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { FeedbackButton } from './components/common/feedback_button';
|
||||
import { CreateSloBtn } from './components/common/create_slo_btn';
|
||||
import { SloListSearchBar } from './components/slo_list_search_bar';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { useFetchSloList } from '../../hooks/use_fetch_slo_list';
|
||||
import { SloList } from './components/slo_list';
|
||||
import { useBreadcrumbs } from '@kbn/observability-shared-plugin/public';
|
||||
import React, { useEffect } from 'react';
|
||||
import { paths } from '../../../common/locators/paths';
|
||||
import { HeaderMenu } from '../../components/header_menu/header_menu';
|
||||
import { SloOutdatedCallout } from '../../components/slo/slo_outdated_callout';
|
||||
import { useFetchSloList } from '../../hooks/use_fetch_slo_list';
|
||||
import { useLicense } from '../../hooks/use_license';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useKibana } from '../../utils/kibana_react';
|
||||
import { CreateSloBtn } from './components/common/create_slo_btn';
|
||||
import { FeedbackButton } from './components/common/feedback_button';
|
||||
import { SloList } from './components/slo_list';
|
||||
import { SloListSearchBar } from './components/slo_list_search_bar';
|
||||
|
||||
export const SLO_PAGE_ID = 'slo-page-container';
|
||||
|
||||
|
|
|
@ -13,10 +13,7 @@ import {
|
|||
} from '../../common/constants';
|
||||
import { IllegalArgumentError } from '../errors';
|
||||
|
||||
interface SloInstanceTuple {
|
||||
sloId: string;
|
||||
instanceId: string;
|
||||
}
|
||||
type List = DeleteSLOInstancesParams['list'];
|
||||
|
||||
export class DeleteSLOInstances {
|
||||
constructor(private esClient: ElasticsearchClient) {}
|
||||
|
@ -31,26 +28,29 @@ export class DeleteSLOInstances {
|
|||
await this.deleteSummaryData(params.list);
|
||||
}
|
||||
|
||||
private async deleteRollupData(list: SloInstanceTuple[]): Promise<void> {
|
||||
// Delete rollup data when excluding rollup data is not explicitly requested
|
||||
private async deleteRollupData(list: List): Promise<void> {
|
||||
await this.esClient.deleteByQuery({
|
||||
index: SLO_DESTINATION_INDEX_PATTERN,
|
||||
wait_for_completion: false,
|
||||
query: {
|
||||
bool: {
|
||||
should: list.map((item) => ({
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { 'slo.id': item.sloId } },
|
||||
{ term: { 'slo.instanceId': item.instanceId } },
|
||||
],
|
||||
},
|
||||
})),
|
||||
should: list
|
||||
.filter((item) => item.excludeRollup !== true)
|
||||
.map((item) => ({
|
||||
bool: {
|
||||
must: [
|
||||
{ term: { 'slo.id': item.sloId } },
|
||||
{ term: { 'slo.instanceId': item.instanceId } },
|
||||
],
|
||||
},
|
||||
})),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async deleteSummaryData(list: SloInstanceTuple[]): Promise<void> {
|
||||
private async deleteSummaryData(list: List): Promise<void> {
|
||||
await this.esClient.deleteByQuery({
|
||||
index: SLO_SUMMARY_DESTINATION_INDEX_PATTERN,
|
||||
refresh: true,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue