feat(slo): delete SLO instance (#182190)

This commit is contained in:
Kevin Delemme 2024-05-03 11:15:53 -04:00 committed by GitHub
parent 5b4142c01b
commit 63ad54689f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 314 additions and 172 deletions

View file

@ -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>;

View file

@ -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(),

View file

@ -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 },
});
}

View file

@ -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}',

View file

@ -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 },
})
);
},
}
);
}

View file

@ -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 ? (

View file

@ -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!);

View file

@ -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 {

View file

@ -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

View file

@ -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 ? (

View file

@ -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}

View file

@ -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 ? (

View file

@ -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,
};

View file

@ -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,

View file

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

View file

@ -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,