diff --git a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_delete.ts b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_delete.ts index 546100b12bdf3..3d21a17448998 100644 --- a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_delete.ts +++ b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_delete.ts @@ -21,6 +21,9 @@ const bulkDeleteStatusParamsSchema = t.type({ type BulkDeleteInput = t.OutputOf; type BulkDeleteParams = t.TypeOf; +interface BulkDeleteResponse { + taskId: string; +} interface BulkDeleteResult { id: string; @@ -34,5 +37,11 @@ interface BulkDeleteStatusResponse { error?: string; } -export type { BulkDeleteInput, BulkDeleteParams, BulkDeleteResult, BulkDeleteStatusResponse }; +export type { + BulkDeleteInput, + BulkDeleteParams, + BulkDeleteResponse, + BulkDeleteResult, + BulkDeleteStatusResponse, +}; export { bulkDeleteParamsSchema, bulkDeleteStatusParamsSchema }; diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_delete_confirmation_modal/bulk_delete_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_delete_confirmation_modal/bulk_delete_confirmation_modal.tsx new file mode 100644 index 0000000000000..8f7621e42f673 --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_delete_confirmation_modal/bulk_delete_confirmation_modal.tsx @@ -0,0 +1,51 @@ +/* + * 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 { EuiConfirmModal } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { SLODefinitionResponse } from '@kbn/slo-schema'; +import React from 'react'; +import { useBulkDeleteSlo } from '../../../pages/slo_management/hooks/use_bulk_delete_slo'; + +export interface Props { + onCancel: () => void; + onConfirm: () => void; + items: SLODefinitionResponse[]; +} + +export function SloBulkDeleteConfirmationModal({ items, onCancel, onConfirm }: Props) { + const { mutate: bulkDelete } = useBulkDeleteSlo(); + + return ( + { + bulkDelete({ items: items.map((item) => ({ id: item.id })) }); + onConfirm(); + }} + > + {i18n.translate('xpack.slo.bulkDeleteConfirmationModal.descriptionText', { + defaultMessage: 'This will delete the SLOs, their instances and all their data.', + })} + + ); +} diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx index b622cfa4c0764..8e57180354888 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.stories.tsx @@ -7,7 +7,7 @@ import { KibanaReactStorybookDecorator } from '../../../utils/kibana_react.storybook_decorator'; import { buildSlo } from '../../../data/slo/slo'; -import { SloDeleteModal as Component } from './slo_delete_confirmation_modal'; +import { SloDeleteConfirmationModal as Component } from './slo_delete_confirmation_modal'; export default { component: Component, diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx index c5dcf745e25de..4c2947708b92d 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/delete_confirmation_modal/slo_delete_confirmation_modal.tsx @@ -26,39 +26,26 @@ import React, { useState } from 'react'; import { useDeleteSlo } from '../../../hooks/use_delete_slo'; import { useDeleteSloInstance } from '../../../hooks/use_delete_slo_instance'; -export interface Props { +interface Props { slo: SLOWithSummaryResponse | SLODefinitionResponse; onCancel: () => void; - onSuccess: () => void; + onConfirm: () => void; } -export function SloDeleteModal({ slo, onCancel, onSuccess }: Props) { +export function SloDeleteConfirmationModal({ slo, onCancel, onConfirm }: Props) { + const modalTitleId = useGeneratedHtmlId(); + const { mutate: deleteSlo } = useDeleteSlo(); + const { mutate: deleteSloInstance } = useDeleteSloInstance(); + 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(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 ( @@ -69,7 +56,7 @@ export function SloDeleteModal({ slo, onCancel, onSuccess }: Props) { {hasGroupBy && instanceId ? ( - + { + deleteSloInstance({ + slo: { id: slo.id, instanceId, name: slo.name }, + excludeRollup: isDeleteRollupDataChecked === false, + }); + onConfirm(); + }} fill > { + deleteSlo({ id: slo.id, name: slo.name }); + onConfirm(); + }} fill > {hasGroupBy && instanceId ? ( diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/disable_confirmation_modal/slo_disable_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/disable_confirmation_modal/slo_disable_confirmation_modal.tsx index b249544849923..5c1eab571c081 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/slo/disable_confirmation_modal/slo_disable_confirmation_modal.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/disable_confirmation_modal/slo_disable_confirmation_modal.tsx @@ -9,23 +9,23 @@ import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { useDisableSlo } from '../../../hooks/use_disable_slo'; export interface Props { slo: SLOWithSummaryResponse | SLODefinitionResponse; onCancel: () => void; onConfirm: () => void; - isLoading?: boolean; } -export function SloDisableConfirmationModal({ slo, onCancel, onConfirm, isLoading }: Props) { - const { name } = slo; +export function SloDisableConfirmationModal({ slo, onCancel, onConfirm }: Props) { + const { mutate: disableSlo } = useDisableSlo(); return ( { + disableSlo({ id: slo.id, name: slo.name }); + onConfirm(); + }} > {i18n.translate('xpack.slo.disableConfirmationModal.descriptionText', { defaultMessage: 'Disabling this SLO will stop generating data until it is enabled again.', diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/enable_confirmation_modal/slo_enable_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/enable_confirmation_modal/slo_enable_confirmation_modal.tsx index 1fd63eb31f81c..d6386b41de0ba 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/slo/enable_confirmation_modal/slo_enable_confirmation_modal.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/enable_confirmation_modal/slo_enable_confirmation_modal.tsx @@ -9,23 +9,24 @@ import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { useEnableSlo } from '../../../hooks/use_enable_slo'; export interface Props { slo: SLOWithSummaryResponse | SLODefinitionResponse; onCancel: () => void; onConfirm: () => void; - isLoading?: boolean; } -export function SloEnableConfirmationModal({ slo, onCancel, onConfirm, isLoading }: Props) { - const { name } = slo; +export function SloEnableConfirmationModal({ slo, onCancel, onConfirm }: Props) { + const { mutate: enableSlo } = useEnableSlo(); + return ( { + enableSlo({ id: slo.id, name: slo.name }); + onConfirm(); + }} > {i18n.translate('xpack.slo.enableConfirmationModal.descriptionText', { defaultMessage: 'Enabling this SLO will generate the missing data since it was disabled.', diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx index fb295d709a455..e48cf63268ac3 100644 --- a/x-pack/solutions/observability/plugins/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/reset_confirmation_modal/slo_reset_confirmation_modal.tsx @@ -9,28 +9,27 @@ import { EuiConfirmModal } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { useResetSlo } from '../../../hooks/use_reset_slo'; export interface SloResetConfirmationModalProps { slo: SLOWithSummaryResponse | SLODefinitionResponse; onCancel: () => void; onConfirm: () => void; - isLoading?: boolean; } export function SloResetConfirmationModal({ slo, onCancel, onConfirm, - isLoading, }: SloResetConfirmationModalProps) { - const { name } = slo; + const { mutate: resetSlo } = useResetSlo(); return ( { + resetSlo({ id: slo.id, name: slo.name }); + onConfirm(); + }} > {i18n.translate('xpack.slo.resetConfirmationModal.descriptionText', { defaultMessage: 'Resetting this SLO will also regenerate the historical data.', diff --git a/x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx new file mode 100644 index 0000000000000..e90f94ebf74ed --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx @@ -0,0 +1,120 @@ +/* + * 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 { SLODefinitionResponse, SLOWithSummaryResponse } from '@kbn/slo-schema'; +import React, { ReactNode, createContext, useContext, useState } from 'react'; +import { SloBulkDeleteConfirmationModal } from '../components/slo/bulk_delete_confirmation_modal/bulk_delete_confirmation_modal'; +import { SloDeleteConfirmationModal } from '../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; +import { SloDisableConfirmationModal } from '../components/slo/disable_confirmation_modal/slo_disable_confirmation_modal'; +import { SloEnableConfirmationModal } from '../components/slo/enable_confirmation_modal/slo_enable_confirmation_modal'; +import { SloResetConfirmationModal } from '../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal'; +import { useCloneSlo } from '../hooks/use_clone_slo'; + +type Action = SingleAction | BulkAction; + +interface BaseAction { + onConfirm?: () => void; + onCancel?: () => void; +} + +interface SingleAction extends BaseAction { + type: 'clone' | 'delete' | 'reset' | 'enable' | 'disable'; + item: SLODefinitionResponse | SLOWithSummaryResponse; +} + +interface BulkAction extends BaseAction { + type: 'bulk_delete'; + items: SLODefinitionResponse[]; +} + +interface ActionModalContextValue { + triggerAction: (action: Action) => void; +} + +const ActionModalContext = createContext(undefined); + +export function ActionModalProvider({ children }: { children: ReactNode }) { + const [action, triggerAction] = useState(); + + const navigateToClone = useCloneSlo(); + + function handleOnCancel() { + triggerAction(undefined); + action?.onCancel?.(); + } + + function handleOnConfirm() { + triggerAction(undefined); + action?.onConfirm?.(); + } + + function handleAction() { + switch (action?.type) { + case 'clone': + navigateToClone(action.item); + return; + case 'delete': + return ( + + ); + + case 'reset': + return ( + + ); + case 'enable': + return ( + + ); + case 'disable': + return ( + + ); + case 'bulk_delete': + return ( + + ); + default: + return null; + } + } + + return ( + + {children} + {handleAction()} + + ); +} + +export function useActionModal() { + const context = useContext(ActionModalContext); + if (!context) { + throw new Error('useActionModal must be used within an ActionModalProvider'); + } + return context; +} diff --git a/x-pack/solutions/observability/plugins/slo/public/hooks/__storybook_mocks__/use_capabilities.ts b/x-pack/solutions/observability/plugins/slo/public/hooks/__storybook_mocks__/use_capabilities.ts deleted file mode 100644 index f34d7a4a7070b..0000000000000 --- a/x-pack/solutions/observability/plugins/slo/public/hooks/__storybook_mocks__/use_capabilities.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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. - */ - -export function useCapabilities() { - return { - hasReadCapabilities: true, - hasWriteCapabilities: true, - }; -} diff --git a/x-pack/solutions/observability/plugins/slo/public/hooks/query_key_factory.ts b/x-pack/solutions/observability/plugins/slo/public/hooks/query_key_factory.ts index d30b41adfe04d..1224406d65161 100644 --- a/x-pack/solutions/observability/plugins/slo/public/hooks/query_key_factory.ts +++ b/x-pack/solutions/observability/plugins/slo/public/hooks/query_key_factory.ts @@ -88,6 +88,7 @@ export const sloKeys = { excludeStale?: boolean; remoteName?: string; }) => [...sloKeys.all, 'fetch_slo_groupings', params] as const, + bulkDeleteStatus: (taskId: string) => [...sloKeys.all, 'bulkDeleteStatus', taskId] as const, }; export type SloKeys = typeof sloKeys; diff --git a/x-pack/solutions/observability/plugins/slo/public/hooks/use_capabilities.ts b/x-pack/solutions/observability/plugins/slo/public/hooks/use_capabilities.ts deleted file mode 100644 index 52db1245b5608..0000000000000 --- a/x-pack/solutions/observability/plugins/slo/public/hooks/use_capabilities.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { sloFeatureId } from '@kbn/observability-plugin/common'; -import { useKibana } from './use_kibana'; - -export function useCapabilities() { - const { - application: { capabilities }, - } = useKibana().services; - - return { - hasReadCapabilities: !!capabilities[sloFeatureId].read ?? false, - hasWriteCapabilities: !!capabilities[sloFeatureId].write ?? false, - }; -} diff --git a/x-pack/solutions/observability/plugins/slo/public/hooks/use_delete_slo_instance.ts b/x-pack/solutions/observability/plugins/slo/public/hooks/use_delete_slo_instance.ts index 9b84e6f565ead..83fab8f10ec99 100644 --- a/x-pack/solutions/observability/plugins/slo/public/hooks/use_delete_slo_instance.ts +++ b/x-pack/solutions/observability/plugins/slo/public/hooks/use_delete_slo_instance.ts @@ -7,10 +7,9 @@ import { IHttpFetchError, ResponseErrorBody } from '@kbn/core/public'; import { i18n } from '@kbn/i18n'; -import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useKibana } from './use_kibana'; import { sloKeys } from './query_key_factory'; +import { useKibana } from './use_kibana'; import { usePluginContext } from './use_plugin_context'; type ServerError = IHttpFetchError; @@ -22,7 +21,11 @@ export function useDeleteSloInstance() { const { sloClient } = usePluginContext(); const queryClient = useQueryClient(); - return useMutation( + return useMutation< + void, + ServerError, + { slo: { id: string; instanceId: string; name: string }; excludeRollup: boolean } + >( ['deleteSloInstance'], ({ slo, excludeRollup }) => { try { @@ -32,7 +35,7 @@ export function useDeleteSloInstance() { list: [ { sloId: slo.id, - instanceId: slo.instanceId ?? ALL_VALUE, + instanceId: slo.instanceId, excludeRollup, }, ], diff --git a/x-pack/solutions/observability/plugins/slo/public/hooks/use_reset_slo.ts b/x-pack/solutions/observability/plugins/slo/public/hooks/use_reset_slo.ts index fdca20517102e..e1e4f5085d4da 100644 --- a/x-pack/solutions/observability/plugins/slo/public/hooks/use_reset_slo.ts +++ b/x-pack/solutions/observability/plugins/slo/public/hooks/use_reset_slo.ts @@ -50,6 +50,8 @@ export function useResetSlo() { queryClient.invalidateQueries({ queryKey: sloKeys.lists(), exact: false }); queryClient.invalidateQueries({ queryKey: sloKeys.historicalSummaries(), exact: false }); queryClient.invalidateQueries({ queryKey: sloKeys.details(), exact: false }); + queryClient.invalidateQueries({ queryKey: sloKeys.allDefinitions(), exact: false }); + toasts.addSuccess( i18n.translate('xpack.slo.slo.reset.successNotification', { defaultMessage: '{name} reset successfully', diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/header_control.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/header_control.tsx index 307c695f40931..acb91d7c6bfd6 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/header_control.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/components/header_control.tsx @@ -13,28 +13,21 @@ import { EuiPopover, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout'; import { sloFeatureId } from '@kbn/observability-plugin/common'; +import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout'; 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 { SloDeleteModal } from '../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; -import { SloResetConfirmationModal } from '../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal'; -import { useCloneSlo } from '../../../hooks/use_clone_slo'; +import { useActionModal } from '../../../context/action_modal'; import { useFetchRulesForSlo } from '../../../hooks/use_fetch_rules_for_slo'; import { useKibana } from '../../../hooks/use_kibana'; import { usePermissions } from '../../../hooks/use_permissions'; -import { useResetSlo } from '../../../hooks/use_reset_slo'; import { convertSliApmParamsToApmAppDeeplinkUrl } from '../../../utils/slo/convert_sli_apm_params_to_apm_app_deeplink_url'; import { isApmIndicatorType } from '../../../utils/slo/indicator'; import { EditBurnRateRuleFlyout } from '../../slos/components/common/edit_burn_rate_rule_flyout'; import { useGetQueryParams } from '../hooks/use_get_query_params'; import { useSloActions } from '../hooks/use_slo_actions'; -import { SloDisableConfirmationModal } from '../../../components/slo/disable_confirmation_modal/slo_disable_confirmation_modal'; -import { SloEnableConfirmationModal } from '../../../components/slo/enable_confirmation_modal/slo_enable_confirmation_modal'; -import { useDisableSlo } from '../../../hooks/use_disable_slo'; -import { useEnableSlo } from '../../../hooks/use_enable_slo'; export interface Props { slo: SLOWithSummaryResponse; @@ -50,6 +43,7 @@ export function HeaderControl({ slo }: Props) { const hasApmReadCapabilities = capabilities.apm.show; const { data: permissions } = usePermissions(); + const { triggerAction } = useActionModal(); const { isDeletingSlo, @@ -65,14 +59,6 @@ export function HeaderControl({ slo }: Props) { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [isRuleFlyoutVisible, setRuleFlyoutVisibility] = useState(false); const [isEditRuleFlyoutOpen, setIsEditRuleFlyoutOpen] = useState(false); - const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); - const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false); - const [isEnableConfirmationModalOpen, setEnableConfirmationModalOpen] = useState(false); - const [isDisableConfirmationModalOpen, setDisableConfirmationModalOpen] = useState(false); - - const { mutate: resetSlo, isLoading: isResetLoading } = useResetSlo(); - const { mutate: enableSlo, isLoading: isEnableLoading } = useEnableSlo(); - const { mutate: disableSlo, isLoading: isDisableLoading } = useDisableSlo(); const { data: rulesBySlo, refetchRules } = useFetchRulesForSlo({ sloIds: [slo.id], @@ -83,20 +69,35 @@ export function HeaderControl({ slo }: Props) { const handleActionsClick = () => setIsPopoverOpen((value) => !value); const closePopover = () => setIsPopoverOpen(false); + const navigate = useCallback( + (url: string) => setTimeout(() => navigateToUrl(url)), + [navigateToUrl] + ); + useEffect(() => { if (isDeletingSlo) { - setDeleteConfirmationModalOpen(true); + triggerAction({ + type: 'delete', + item: slo, + onConfirm: () => { + navigate(basePath.prepend(paths.slos)); + }, + }); + removeDeleteQueryParam(); } if (isResettingSlo) { - setResetConfirmationModalOpen(true); + triggerAction({ type: 'reset', item: slo }); + removeResetQueryParam(); } if (isEnablingSlo) { - setEnableConfirmationModalOpen(true); + triggerAction({ type: 'enable', item: slo }); + removeEnableQueryParam(); } if (isDisablingSlo) { - setDisableConfirmationModalOpen(true); + triggerAction({ type: 'disable', item: slo }); + removeDisableQueryParam(); } - }, [isDeletingSlo, isResettingSlo, isEnablingSlo, isDisablingSlo]); + }); const onCloseRuleFlyout = () => { setRuleFlyoutVisibility(false); @@ -128,93 +129,70 @@ export function HeaderControl({ slo }: Props) { } }; - const navigateToClone = useCloneSlo(); - - const handleClone = async () => { - setIsPopoverOpen(false); - navigateToClone(slo); + const handleClone = () => { + triggerAction({ type: 'clone', item: slo }); }; const handleDelete = () => { if (!!remoteDeleteUrl) { window.open(remoteDeleteUrl, '_blank'); } else { - setDeleteConfirmationModalOpen(true); - setIsPopoverOpen(false); + triggerAction({ + type: 'delete', + item: slo, + onConfirm: () => { + navigate(basePath.prepend(paths.slos)); + setIsPopoverOpen(false); + }, + }); + removeDeleteQueryParam(); } }; - const handleDeleteCancel = () => { - removeDeleteQueryParam(); - setDeleteConfirmationModalOpen(false); - }; - - const handleDeleteConfirm = async () => { - removeDeleteQueryParam(); - setDeleteConfirmationModalOpen(false); - navigate(basePath.prepend(paths.slos)); - }; - const handleReset = () => { if (!!remoteResetUrl) { window.open(remoteResetUrl, '_blank'); } else { - setResetConfirmationModalOpen(true); - setIsPopoverOpen(false); + triggerAction({ + type: 'reset', + item: slo, + onConfirm: () => { + setIsPopoverOpen(false); + }, + }); + removeResetQueryParam(); } }; - const handleResetConfirm = () => { - resetSlo({ id: slo.id, name: slo.name }); - removeResetQueryParam(); - setResetConfirmationModalOpen(false); - }; - - const handleResetCancel = () => { - removeResetQueryParam(); - setResetConfirmationModalOpen(false); - }; - const handleEnable = () => { if (!!remoteEnableUrl) { window.open(remoteEnableUrl, '_blank'); } else { - setEnableConfirmationModalOpen(true); - setIsPopoverOpen(false); + triggerAction({ + type: 'enable', + item: slo, + onConfirm: () => { + setIsPopoverOpen(false); + }, + }); + removeEnableQueryParam(); } }; - const handleEnableCancel = () => { - removeEnableQueryParam(); - setEnableConfirmationModalOpen(false); - }; - const handleEnableConfirm = () => { - enableSlo({ id: slo.id, name: slo.name }); - removeEnableQueryParam(); - setEnableConfirmationModalOpen(false); - }; const handleDisable = () => { if (!!remoteDisableUrl) { window.open(remoteDisableUrl, '_blank'); } else { - setDisableConfirmationModalOpen(true); - setIsPopoverOpen(false); + triggerAction({ + type: 'disable', + item: slo, + onConfirm: () => { + setIsPopoverOpen(false); + }, + }); + removeDisableQueryParam(); } }; - const handleDisableCancel = () => { - removeDisableQueryParam(); - setDisableConfirmationModalOpen(false); - }; - const handleDisableConfirm = () => { - disableSlo({ id: slo.id, name: slo.name }); - removeDisableQueryParam(); - setDisableConfirmationModalOpen(false); - }; - - const navigate = useCallback( - (url: string) => setTimeout(() => navigateToUrl(url)), - [navigateToUrl] - ); const isRemote = !!slo?.remote; const hasUndefinedRemoteKibanaUrl = !!slo?.remote && slo?.remote?.kibanaUrl === ''; @@ -412,37 +390,6 @@ export function HeaderControl({ slo }: Props) { shouldUseRuleProducer /> ) : null} - - {isDeleteConfirmationModalOpen ? ( - - ) : null} - - {isResetConfirmationModalOpen ? ( - - ) : null} - - {isEnableConfirmationModalOpen ? ( - - ) : null} - - {isDisableConfirmationModalOpen ? ( - - ) : null} ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.test.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.test.tsx index e84bf353476ac..5760c9e846b9d 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.test.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.test.tsx @@ -156,8 +156,8 @@ describe('SLO Details Page', () => { data: historicalSummaryData, }); useFetchActiveAlertsMock.mockReturnValue({ isLoading: false, data: new ActiveAlerts() }); - useDeleteSloMock.mockReturnValue({ mutateAsync: mockDelete }); - useDeleteSloInstanceMock.mockReturnValue({ mutateAsync: mockDeleteInstance }); + useDeleteSloMock.mockReturnValue({ mutate: mockDelete }); + useDeleteSloInstanceMock.mockReturnValue({ mutate: mockDeleteInstance }); jest .spyOn(Router, 'useLocation') .mockReturnValue({ pathname: '/slos/1234', search: '', state: '', hash: '' }); diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.tsx index 491c850bf03b8..757fa6a661cd9 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_details/slo_details.tsx @@ -34,6 +34,7 @@ import { useGetQueryParams } from './hooks/use_get_query_params'; import { useSelectedTab } from './hooks/use_selected_tab'; import { useSloDetailsTabs } from './hooks/use_slo_details_tabs'; import type { SloDetailsPathParams } from './types'; +import { ActionModalProvider } from '../../context/action_modal'; export function SloDetailsPage() { const { onPageReady } = usePerformanceContext(); @@ -128,7 +129,9 @@ export function SloDetailsPage() { children: , rightSideItems: !isLoading ? [ - , + + + , { + triggerAction({ items, type: 'bulk_delete' }); + }} + size="xs" + css={{ blockSize: '0px' }} + > + {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel', { + defaultMessage: 'Delete {count} SLOs', + values: { + count: items.length, + }, + })} + + ); +} diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_content.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_content.tsx deleted file mode 100644 index dd564d35491c9..0000000000000 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_content.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { EuiFlexGroup } from '@elastic/eui'; -import React from 'react'; -import { SloManagementTable } from './slo_management_table'; -import { SloOutdatedFilterCallout } from './slo_management_outdated_filter_callout'; - -export function SloManagementContent() { - return ( - - - - - ); -} diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_outdated_filter_callout.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_outdated_filter_callout.tsx index d141870d00c8a..c41a324482cef 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_outdated_filter_callout.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_outdated_filter_callout.tsx @@ -4,10 +4,10 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiCallOut, EuiIcon, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; +import { EuiCallOut, EuiIcon, EuiLink, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useUrlSearchState } from './hooks/use_url_search_state'; +import React from 'react'; +import { useUrlSearchState } from '../hooks/use_url_search_state'; export function SloOutdatedFilterCallout() { const { state, onStateChange } = useUrlSearchState(); @@ -17,30 +17,27 @@ export function SloOutdatedFilterCallout() { } return ( - <> - - - {' '} - {i18n.translate('xpack.slo.outdatedSloFilterCallout.title', { - defaultMessage: - "You're currently viewing only outdated SLOs. You can reset them from the action menu to bring them up to date.", - })}{' '} - { - onStateChange({ - ...state, - includeOutdatedOnly: false, - }); - }} - > - {i18n.translate('xpack.slo.outdatedSloFilterCallout.action', { - defaultMessage: 'Remove filter', - })} - - - - - + + + {' '} + {i18n.translate('xpack.slo.outdatedSloFilterCallout.title', { + defaultMessage: + "You're currently viewing only outdated SLOs. You can reset them from the action menu to bring them up to date.", + })}{' '} + { + onStateChange({ + ...state, + includeOutdatedOnly: false, + }); + }} + > + {i18n.translate('xpack.slo.outdatedSloFilterCallout.action', { + defaultMessage: 'Remove filter', + })} + + + ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_search_bar.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_search_bar.tsx index 1dd3748313464..b72585cbe9648 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_search_bar.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_search_bar.tsx @@ -11,7 +11,7 @@ import { EuiComboBox, EuiComboBoxOptionOption, EuiText } from '@elastic/eui'; import { observabilityAppId } from '@kbn/observability-shared-plugin/common'; import { useFetchSLOSuggestions } from '../../slo_edit/hooks/use_fetch_suggestions'; import { useKibana } from '../../../hooks/use_kibana'; -import { useUrlSearchState } from './hooks/use_url_search_state'; +import { useUrlSearchState } from '../hooks/use_url_search_state'; interface Props { onRefresh: () => void; diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_table.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_table.tsx index 0349a8f69a646..2b69e049f31ae 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_table.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_table.tsx @@ -13,41 +13,40 @@ import { EuiBasicTableColumn, EuiFlexGroup, EuiFlexItem, + EuiHealth, EuiLink, EuiPanel, EuiSpacer, + EuiTableSelectionType, EuiText, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; import { ALL_VALUE, SLODefinitionResponse } from '@kbn/slo-schema'; +import React, { useState } from 'react'; +import { sloPaths } from '../../../../common'; +import { SLO_MODEL_VERSION } from '../../../../common/constants'; +import { paths } from '../../../../common/locators/paths'; import { useFetchSloDefinitions } from '../../../hooks/use_fetch_slo_definitions'; import { useKibana } from '../../../hooks/use_kibana'; import { usePermissions } from '../../../hooks/use_permissions'; -import { useResetSlo } from '../../../hooks/use_reset_slo'; -import { useEnableSlo } from '../../../hooks/use_enable_slo'; -import { useDisableSlo } from '../../../hooks/use_disable_slo'; -import { useCloneSlo } from '../../../hooks/use_clone_slo'; -import { sloPaths } from '../../../../common'; -import { paths } from '../../../../common/locators/paths'; +import { useActionModal } from '../../../context/action_modal'; +import { useBulkOperation } from '../context/bulk_operation'; +import { useUrlSearchState } from '../hooks/use_url_search_state'; +import { SloManagementBulkActions } from './slo_management_bulk_actions'; import { SloManagementSearchBar } from './slo_management_search_bar'; -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 { SloEnableConfirmationModal } from '../../../components/slo/enable_confirmation_modal/slo_enable_confirmation_modal'; -import { SloDisableConfirmationModal } from '../../../components/slo/disable_confirmation_modal/slo_disable_confirmation_modal'; -import { SLO_MODEL_VERSION } from '../../../../common/constants'; -import { useUrlSearchState } from './hooks/use_url_search_state'; export function SloManagementTable() { const { state, onStateChange } = useUrlSearchState(); const { search, page, perPage, tags, includeOutdatedOnly } = state; - const { services } = useKibana(); - const { - http, - application: { navigateToUrl }, - } = services; + services: { + http, + application: { navigateToUrl }, + }, + } = useKibana(); + const { triggerAction } = useActionModal(); + const { data: permissions } = usePermissions(); const { isLoading, isError, data, refetch } = useFetchSloDefinitions({ page: page + 1, perPage, @@ -55,61 +54,23 @@ export function SloManagementTable() { tags, includeOutdatedOnly, }); + const { tasks } = useBulkOperation(); - const { data: permissions } = usePermissions(); - - const [sloToDelete, setSloToDelete] = useState(undefined); - const [sloToReset, setSloToReset] = useState(undefined); - const [sloToEnable, setSloToEnable] = useState(undefined); - const [sloToDisable, setSloToDisable] = useState(undefined); - - const { mutate: resetSlo, isLoading: isResetLoading } = useResetSlo(); - const { mutate: enableSlo, isLoading: isEnableLoading } = useEnableSlo(); - const { mutate: disableSlo, isLoading: isDisableLoading } = useDisableSlo(); - - const handleDeleteConfirm = () => { - setSloToDelete(undefined); - }; - - const handleDeleteCancel = () => { - setSloToDelete(undefined); - }; - - const handleResetConfirm = () => { - if (sloToReset) { - resetSlo({ id: sloToReset.id, name: sloToReset.name }); - setSloToReset(undefined); - } - }; - - const handleResetCancel = () => { - setSloToReset(undefined); + const [selectedItems, setSelectedItems] = useState([]); + const onSelectionChange = (items: SLODefinitionResponse[]) => { + setSelectedItems(items); }; - const handleEnableConfirm = async () => { - if (sloToEnable) { - enableSlo({ id: sloToEnable.id, name: sloToEnable.name }); - setSloToEnable(undefined); - } - }; - - const handleEnableCancel = () => { - setSloToEnable(undefined); - }; - - const handleDisableConfirm = async () => { - if (sloToDisable) { - disableSlo({ id: sloToDisable.id, name: sloToDisable.name }); - setSloToDisable(undefined); - } - }; - - const handleDisableCancel = () => { - setSloToDisable(undefined); + const selection: EuiTableSelectionType = { + selectable: (item: SLODefinitionResponse) => { + return !tasks.find( + (task) => task.status === 'in-progress' && task.items.some((i) => i.id === item.id) + ); + }, + onSelectionChange, + initialSelected: [], }; - const navigateToClone = useCloneSlo(); - const actions: Array> = [ { type: 'icon', @@ -136,9 +97,7 @@ export function SloManagementTable() { defaultMessage: 'Clone', }), 'data-test-subj': 'sloActionsClone', - onClick: (slo: SLODefinitionResponse) => { - navigateToClone(slo); - }, + onClick: (slo: SLODefinitionResponse) => triggerAction({ item: slo, type: 'clone' }), }, { type: 'icon', @@ -163,12 +122,7 @@ export function SloManagementTable() { enabled: () => !!permissions?.hasAllWriteRequested, onClick: (slo: SLODefinitionResponse) => { const isEnabled = slo.enabled; - - if (isEnabled) { - setSloToDisable(slo); - } else { - setSloToEnable(slo); - } + triggerAction({ item: slo, type: isEnabled ? 'disable' : 'enable' }); }, }, { @@ -182,9 +136,7 @@ export function SloManagementTable() { }), 'data-test-subj': 'sloActionsDelete', enabled: (slo: SLODefinitionResponse) => !!permissions?.hasAllWriteRequested, - onClick: (slo: SLODefinitionResponse) => { - setSloToDelete(slo); - }, + onClick: (slo: SLODefinitionResponse) => triggerAction({ item: slo, type: 'delete' }), }, { @@ -198,9 +150,7 @@ export function SloManagementTable() { }), 'data-test-subj': 'sloActionsReset', enabled: () => !!permissions?.hasAllWriteRequested, - onClick: (slo: SLODefinitionResponse) => { - setSloToReset(slo); - }, + onClick: (slo: SLODefinitionResponse) => triggerAction({ item: slo, type: 'reset' }), }, ]; @@ -228,8 +178,8 @@ export function SloManagementTable() { name: i18n.translate('xpack.slo.sloManagementTable.columns.versionLabel', { defaultMessage: 'Version', }), - render: (item: SLODefinitionResponse['version']) => { - return item < SLO_MODEL_VERSION ? ( + render: (value: SLODefinitionResponse['version']) => { + return value < SLO_MODEL_VERSION ? ( {i18n.translate('xpack.slo.sloManagementTable.version.outdated', { defaultMessage: 'Outdated', @@ -249,10 +199,10 @@ export function SloManagementTable() { name: i18n.translate('xpack.slo.sloManagementTable.columns.tagsLabel', { defaultMessage: 'Tags', }), - render: (item: SLODefinitionResponse['tags']) => { + render: (value: SLODefinitionResponse['tags']) => { return ( - {item.map((tag) => ( + {value.map((tag) => ( {tag} @@ -261,9 +211,21 @@ export function SloManagementTable() { ); }, }, + { + field: 'State', + width: '20%', + name: i18n.translate('xpack.slo.sloManagementTable.columns.state', { + defaultMessage: 'State', + }), + render: (_: SLODefinitionResponse['enabled'], item: SLODefinitionResponse) => { + const color = item.enabled ? 'success' : 'danger'; + const label = item.enabled ? 'Running' : 'Paused'; + return {label}; + }, + }, { name: 'Actions', - width: '5%', + width: '10%', actions, }, ]; @@ -289,62 +251,44 @@ export function SloManagementTable() { }; return ( - <> - - - - - tableCaption={TABLE_CAPTION} - error={ - isError - ? i18n.translate('xpack.slo.sloManagementTable.error', { - defaultMessage: 'An error occurred while retrieving SLO definitions', - }) - : undefined - } - items={data?.results ?? []} - rowHeader="name" - columns={columns} - pagination={pagination} - onChange={onTableChange} - loading={isLoading} - /> - - {sloToDelete ? ( - - ) : null} + + + - {sloToReset ? ( - - ) : null} + {!selectedItems.length ? ( + + {i18n.translate('xpack.slo.sloManagementTable.itemCount', { + defaultMessage: 'Showing {count} of {total} SLOs', + values: { + count: data?.results.length ?? 0, + total: data?.total ?? 0, + }, + })} + + ) : ( + + )} - {sloToEnable ? ( - - ) : null} - - {sloToDisable ? ( - - ) : null} - + + + tableCaption={TABLE_CAPTION} + error={ + isError + ? i18n.translate('xpack.slo.sloManagementTable.error', { + defaultMessage: 'An error occurred while retrieving SLO definitions', + }) + : undefined + } + items={data?.results ?? []} + rowHeader="name" + columns={columns} + itemId="id" + pagination={pagination} + onChange={onTableChange} + loading={isLoading} + selection={selection} + /> + ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/context/bulk_operation.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/context/bulk_operation.tsx new file mode 100644 index 0000000000000..23c6d8d3c3588 --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/context/bulk_operation.tsx @@ -0,0 +1,165 @@ +/* + * 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 { useQueries, useQueryClient } from '@tanstack/react-query'; +import React, { createContext, useCallback, useContext, useState } from 'react'; +import { sloKeys } from '../../../hooks/query_key_factory'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { useKibana } from '../../../hooks/use_kibana'; + +interface BulkOperationTask { + taskId: string; + operation: 'delete'; + status: 'in-progress' | 'completed' | 'failed'; + items: Array<{ id: string; success: boolean; error?: string }>; + createdAt: Date; + updatedAt: Date; + error?: string; +} + +interface RegisterBulkOperationTask { + taskId: string; + operation: 'delete'; + items: Array<{ id: string }>; +} + +interface BulkOperationContext { + tasks: BulkOperationTask[]; + register: (task: RegisterBulkOperationTask) => void; +} + +export const useBulkOperation = (): BulkOperationContext => { + const context = useContext(BulkOperationContext); + if (!context) { + throw new Error('useBulkOperation must be used within a BulkOperationProvider'); + } + return context; +}; + +const BulkOperationContext = createContext(undefined); + +export function BulkOperationProvider({ children }: { children: React.ReactNode }) { + const { + notifications: { toasts }, + } = useKibana().services; + const queryClient = useQueryClient(); + const { sloClient } = usePluginContext(); + + const [tasks, setTasks] = useState([]); + + const register = useCallback((task: RegisterBulkOperationTask) => { + setTasks((prevTasks) => [ + ...prevTasks, + { + ...task, + items: task.items.map((item) => ({ ...item, success: false })), + status: 'in-progress', + createdAt: new Date(), + updatedAt: new Date(), + }, + ]); + }, []); + + useQueries({ + queries: tasks + .filter((task) => task.status === 'in-progress') + .map((task) => ({ + queryKey: sloKeys.bulkDeleteStatus(task.taskId), + queryFn: async () => { + const response = await sloClient.fetch( + 'GET /api/observability/slos/_bulk_delete/{taskId} 2023-10-31', + { params: { path: { taskId: task.taskId } } } + ); + + if (!response.isDone) { + setTasks((prevTasks) => + prevTasks.map((prevTask) => { + if (prevTask.taskId === task.taskId) { + return { + ...prevTask, + status: 'in-progress', + updatedAt: new Date(), + }; + } + return prevTask; + }) + ); + + return response; + } + + queryClient.invalidateQueries({ queryKey: sloKeys.allDefinitions(), exact: false }); + + if (!!response.error) { + setTasks((prevTasks) => + prevTasks.map((prevTask) => { + if (prevTask.taskId === task.taskId) { + return { + ...prevTask, + items: prevTask.items.map((item) => ({ + ...item, + success: false, + error: response.error, + })), + status: 'failed', + error: response.error, + updatedAt: new Date(), + }; + } + return prevTask; + }) + ); + + toasts.addError(new Error(response.error), { + title: `Bulk ${task.operation} failed`, + }); + + return response; + } + + setTasks((prevTasks) => + prevTasks.map((prevTask) => { + if (prevTask.taskId === task.taskId) { + return { + ...prevTask, + items: prevTask.items.map((item) => { + const result = response.results?.find((i) => i.id === item.id); + return { + ...item, + success: result?.success ?? false, + error: result?.error, + }; + }), + status: 'completed', + updatedAt: new Date(), + }; + } + return prevTask; + }) + ); + + toasts.addSuccess({ + title: `Bulk ${task.operation} completed`, + text: `Successfully deleted ${response.results?.filter((i) => i.success).length} on ${ + response.results?.length + } SLOs`, + }); + + return response; + }, + refetchInterval: 3000, + retry: false, + refetchOnWindowFocus: false, + })), + }); + + return ( + + {children} + + ); +} diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_delete_slo.ts b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_delete_slo.ts new file mode 100644 index 0000000000000..b2f5bdc4af56d --- /dev/null +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_delete_slo.ts @@ -0,0 +1,70 @@ +/* + * 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 { BulkDeleteResponse, SLODefinitionResponse } from '@kbn/slo-schema'; +import { useMutation } from '@tanstack/react-query'; +import { useKibana } from '../../../hooks/use_kibana'; +import { usePluginContext } from '../../../hooks/use_plugin_context'; +import { useBulkOperation } from '../context/bulk_operation'; + +type ServerError = IHttpFetchError; + +export function useBulkDeleteSlo() { + const { + notifications: { toasts }, + } = useKibana().services; + const { sloClient } = usePluginContext(); + const bulkOperation = useBulkOperation(); + + return useMutation< + BulkDeleteResponse, + ServerError, + { items: Array> } + >( + ['bulkDeleteSlo'], + ({ items }) => { + try { + return sloClient.fetch('POST /api/observability/slos/_bulk_delete 2023-10-31', { + params: { + body: { + list: items.map(({ id }) => id), + }, + }, + }); + } catch (error) { + return Promise.reject( + i18n.translate('xpack.slo.bulkDelete.errorMessage', { + defaultMessage: 'Failed to bulk delete {count} SLOs, something went wrong: {error}', + values: { error: String(error), count: items.length }, + }) + ); + } + }, + { + onError: (error, { items }) => { + toasts.addError(new Error(error.body?.message ?? error.message), { + title: i18n.translate('xpack.slo.bulkDelete.errorNotification', { + defaultMessage: 'Failed to schedule deletion of {count} SLOs', + values: { count: items.length }, + }), + }); + }, + onSuccess: (response, { items }) => { + bulkOperation.register({ taskId: response.taskId, operation: 'delete', items }); + + toasts.addSuccess( + i18n.translate('xpack.slo.bulkDelete.successNotification', { + defaultMessage: 'Bulk delete of {count} SLOs scheduled', + values: { count: items.length }, + }) + ); + }, + } + ); +} diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/hooks/use_url_search_state.ts b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_url_search_state.ts similarity index 97% rename from x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/hooks/use_url_search_state.ts rename to x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_url_search_state.ts index 9893d4fb135b7..ff2226b3d31f8 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/hooks/use_url_search_state.ts +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_url_search_state.ts @@ -12,7 +12,7 @@ import { import deepmerge from 'deepmerge'; import { useCallback, useEffect, useRef, useState } from 'react'; import { useHistory } from 'react-router-dom'; -import { DEFAULT_SLO_PAGE_SIZE } from '../../../../../common/constants'; +import { DEFAULT_SLO_PAGE_SIZE } from '../../../../common/constants'; export const SLO_MANAGEMENT_SEARCH_URL_STORAGE_KEY = 'search'; export const SLO_MANAGEMENT_SEARCH_SESSION_STORAGE_KEY = 'slo.management_page_search_state'; diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/slo_management_page.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/slo_management_page.tsx index 0537ea53c5b8d..295e429aff92f 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/slo_management_page.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/slo_management_page.tsx @@ -5,17 +5,21 @@ * 2.0. */ +import { EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; 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 { usePermissions } from '../../hooks/use_permissions'; -import { useLicense } from '../../hooks/use_license'; +import { useFetchSloDefinitions } from '../../hooks/use_fetch_slo_definitions'; import { useKibana } from '../../hooks/use_kibana'; +import { useLicense } from '../../hooks/use_license'; +import { usePermissions } from '../../hooks/use_permissions'; import { usePluginContext } from '../../hooks/use_plugin_context'; -import { SloManagementContent } from './components/slo_management_content'; -import { useFetchSloDefinitions } from '../../hooks/use_fetch_slo_definitions'; +import { SloOutdatedFilterCallout } from './components/slo_management_outdated_filter_callout'; +import { SloManagementTable } from './components/slo_management_table'; +import { ActionModalProvider } from '../../context/action_modal'; +import { BulkOperationProvider } from './context/bulk_operation'; export function SloManagementPage() { const { @@ -71,7 +75,14 @@ export function SloManagementPage() { }} > - + + + + + + + + ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_card_item.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_card_item.tsx index 95c1ed696b3e1..cad64662b4d69 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_card_item.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_card_item.tsx @@ -18,14 +18,7 @@ import { ALL_VALUE, HistoricalSummaryResponse, SLOWithSummaryResponse } from '@k import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import moment from 'moment'; import React, { useState } from 'react'; -import { SloDeleteModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; -import { SloDisableConfirmationModal } from '../../../../components/slo/disable_confirmation_modal/slo_disable_confirmation_modal'; -import { SloEnableConfirmationModal } from '../../../../components/slo/enable_confirmation_modal/slo_enable_confirmation_modal'; -import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal'; -import { useDisableSlo } from '../../../../hooks/use_disable_slo'; -import { useEnableSlo } from '../../../../hooks/use_enable_slo'; import { useKibana } from '../../../../hooks/use_kibana'; -import { useResetSlo } from '../../../../hooks/use_reset_slo'; import { BurnRateRuleParams } from '../../../../typings'; import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter'; import { useSloListActions } from '../../hooks/use_slo_list_actions'; @@ -74,10 +67,6 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet const [isActionsPopoverOpen, setIsActionsPopoverOpen] = useState(false); const [isAddRuleFlyoutOpen, setIsAddRuleFlyoutOpen] = useState(false); const [isEditRuleFlyoutOpen, setIsEditRuleFlyoutOpen] = useState(false); - const [isDeleteConfirmationModalOpen, setDeleteConfirmationModalOpen] = useState(false); - const [isResetConfirmationModalOpen, setResetConfirmationModalOpen] = useState(false); - const [isEnableConfirmationModalOpen, setEnableConfirmationModalOpen] = useState(false); - const [isDisableConfirmationModalOpen, setDisableConfirmationModalOpen] = useState(false); const [isDashboardAttachmentReady, setDashboardAttachmentReady] = useState(false); const historicalSliData = formatHistoricalData(historicalSummary, 'sli_value'); @@ -88,39 +77,6 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet setIsAddRuleFlyoutOpen, }); - const closeDeleteModal = () => { - setDeleteConfirmationModalOpen(false); - }; - - const { mutate: resetSlo, isLoading: isResetLoading } = useResetSlo(); - const { mutate: enableSlo, isLoading: isEnableLoading } = useEnableSlo(); - const { mutate: disableSlo, isLoading: isDisableLoading } = useDisableSlo(); - - const handleResetConfirm = () => { - resetSlo({ id: slo.id, name: slo.name }); - setResetConfirmationModalOpen(false); - }; - - const handleResetCancel = () => { - setResetConfirmationModalOpen(false); - }; - - const handleEnableCancel = () => { - setEnableConfirmationModalOpen(false); - }; - const handleEnableConfirm = () => { - enableSlo({ id: slo.id, name: slo.name }); - setEnableConfirmationModalOpen(false); - }; - - const handleDisableCancel = () => { - setDisableConfirmationModalOpen(false); - }; - const handleDisableConfirm = () => { - disableSlo({ id: slo.id, name: slo.name }); - setDisableConfirmationModalOpen(false); - }; - return ( <> @@ -200,37 +152,6 @@ export function SloCardItem({ slo, rules, activeAlerts, historicalSummary, refet refetchRules={refetchRules} /> - {isDeleteConfirmationModalOpen ? ( - - ) : null} - - {isResetConfirmationModalOpen ? ( - - ) : null} - - {isEnableConfirmationModalOpen ? ( - - ) : null} - - {isDisableConfirmationModalOpen ? ( - - ) : null} - {isDashboardAttachmentReady ? ( void; - setDeleteConfirmationModalOpen: (value: boolean) => void; - setResetConfirmationModalOpen: (value: boolean) => void; - setEnableConfirmationModalOpen: (value: boolean) => void; - setDisableConfirmationModalOpen: (value: boolean) => void; setIsAddRuleFlyoutOpen: (value: boolean) => void; setIsEditRuleFlyoutOpen: (value: boolean) => void; setDashboardAttachmentReady: (value: boolean) => void; diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_list_card_view.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_list_card_view.tsx index d0cff25edeb52..e001b60d9e29d 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_list_card_view.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/card_view/slo_list_card_view.tsx @@ -97,7 +97,7 @@ function LoadingSloGrid({ gridSize }: { gridSize: number }) { {loaders.map((_, i) => ( - + {' '} diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx index f13a03de41138..fe9140aef8ecd 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/compact_view/slo_list_compact_view.tsx @@ -15,32 +15,24 @@ import { } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; -import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout'; import { rulesLocatorID, sloFeatureId } from '@kbn/observability-plugin/common'; import { RulesParams } from '@kbn/observability-plugin/public'; +import { RuleFormFlyout } from '@kbn/response-ops-rule-form/flyout'; import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { ALL_VALUE, SLOWithSummaryResponse } from '@kbn/slo-schema'; 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 { SloDeleteModal } from '../../../../components/slo/delete_confirmation_modal/slo_delete_confirmation_modal'; -import { SloDisableConfirmationModal } from '../../../../components/slo/disable_confirmation_modal/slo_disable_confirmation_modal'; -import { SloEnableConfirmationModal } from '../../../../components/slo/enable_confirmation_modal/slo_enable_confirmation_modal'; -import { SloResetConfirmationModal } from '../../../../components/slo/reset_confirmation_modal/slo_reset_confirmation_modal'; import { SloStateBadge, SloStatusBadge } from '../../../../components/slo/slo_badges'; import { SloActiveAlertsBadge } from '../../../../components/slo/slo_badges/slo_active_alerts_badge'; import { sloKeys } from '../../../../hooks/query_key_factory'; -import { useCloneSlo } from '../../../../hooks/use_clone_slo'; -import { useDisableSlo } from '../../../../hooks/use_disable_slo'; -import { useEnableSlo } from '../../../../hooks/use_enable_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'; import { useGetFilteredRuleTypes } from '../../../../hooks/use_get_filtered_rule_types'; import { useKibana } from '../../../../hooks/use_kibana'; import { usePermissions } from '../../../../hooks/use_permissions'; -import { useResetSlo } from '../../../../hooks/use_reset_slo'; import { useSpace } from '../../../../hooks/use_space'; import { formatHistoricalData } from '../../../../utils/slo/chart_data_formatter'; import { @@ -50,6 +42,7 @@ import { createRemoteSloEnableUrl, createRemoteSloResetUrl, } from '../../../../utils/slo/remote_slo_urls'; +import { useActionModal } from '../../../../context/action_modal'; import { SloRemoteBadge } from '../badges/slo_remote_badge'; import { SloRulesBadge } from '../badges/slo_rules_badge'; import { SLOGroupings } from '../common/slo_groupings'; @@ -83,57 +76,9 @@ export function SloListCompactView({ sloList, loading, error }: Props) { const { data: permissions } = usePermissions(); const filteredRuleTypes = useGetFilteredRuleTypes(); const queryClient = useQueryClient(); - - const { mutate: resetSlo, isLoading: isResetLoading } = useResetSlo(); - const { mutate: enableSlo, isLoading: isEnableLoading } = useEnableSlo(); - const { mutate: disableSlo, isLoading: isDisableLoading } = useDisableSlo(); + const { triggerAction } = useActionModal(); const [sloToAddRule, setSloToAddRule] = useState(undefined); - const [sloToDelete, setSloToDelete] = useState(undefined); - const [sloToReset, setSloToReset] = useState(undefined); - const [sloToEnable, setSloToEnable] = useState(undefined); - const [sloToDisable, setSloToDisable] = useState(undefined); - - const handleDeleteConfirm = () => { - setSloToDelete(undefined); - }; - - const handleDeleteCancel = () => { - setSloToDelete(undefined); - }; - - const handleResetConfirm = () => { - if (sloToReset) { - resetSlo({ id: sloToReset.id, name: sloToReset.name }); - setSloToReset(undefined); - } - }; - - const handleResetCancel = () => { - setSloToReset(undefined); - }; - - const handleEnableConfirm = async () => { - if (sloToEnable) { - enableSlo({ id: sloToEnable.id, name: sloToEnable.name }); - setSloToEnable(undefined); - } - }; - - const handleEnableCancel = () => { - setSloToEnable(undefined); - }; - - const handleDisableConfirm = async () => { - if (sloToDisable) { - disableSlo({ id: sloToDisable.id, name: sloToDisable.name }); - setSloToDisable(undefined); - } - }; - - const handleDisableCancel = () => { - setSloToDisable(undefined); - }; const handleSavedRule = () => { queryClient.invalidateQueries({ queryKey: sloKeys.rules(), exact: false }); @@ -149,8 +94,6 @@ export function SloListCompactView({ sloList, loading, error }: Props) { sloList, }); - const navigateToClone = useCloneSlo(); - const isRemote = (slo: SLOWithSummaryResponse) => !!slo.remote; const hasRemoteKibanaUrl = (slo: SLOWithSummaryResponse) => !!slo.remote && slo.remote.kibanaUrl !== ''; @@ -276,11 +219,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { if (!!remoteUrl) { window.open(remoteUrl, '_blank'); } else { - if (isEnabled) { - setSloToDisable(slo); - } else { - setSloToEnable(slo); - } + triggerAction({ item: slo, type: isEnabled ? 'disable' : 'enable' }); } }, }, @@ -299,7 +238,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { enabled: (slo: SLOWithSummaryResponse) => (permissions?.hasAllWriteRequested && !isRemote(slo)) || hasRemoteKibanaUrl(slo), onClick: (slo: SLOWithSummaryResponse) => { - navigateToClone(slo); + triggerAction({ item: slo, type: 'clone' }); }, }, { @@ -321,7 +260,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { if (!!remoteDeleteUrl) { window.open(remoteDeleteUrl, '_blank'); } else { - setSloToDelete(slo); + triggerAction({ item: slo, type: 'delete' }); } }, }, @@ -344,7 +283,7 @@ export function SloListCompactView({ sloList, loading, error }: Props) { if (!!remoteResetUrl) { window.open(remoteResetUrl, '_blank'); } else { - setSloToReset(slo); + triggerAction({ item: slo, type: 'reset' }); } }, }, @@ -533,41 +472,6 @@ export function SloListCompactView({ sloList, loading, error }: Props) { shouldUseRuleProducer /> ) : null} - - {sloToDelete ? ( - - ) : null} - - {sloToReset ? ( - - ) : null} - - {sloToEnable ? ( - - ) : null} - - {sloToDisable ? ( - - ) : null} ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slo_item_actions.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slo_item_actions.tsx index d5a90e95a6686..45a50b477956b 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slo_item_actions.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slo_item_actions.tsx @@ -19,20 +19,16 @@ import { i18n } from '@kbn/i18n'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import { Rule } from '@kbn/triggers-actions-ui-plugin/public'; import React from 'react'; -import { useCloneSlo } from '../../../hooks/use_clone_slo'; import { useKibana } from '../../../hooks/use_kibana'; import { usePermissions } from '../../../hooks/use_permissions'; import { BurnRateRuleParams } from '../../../typings'; import { useSloActions } from '../../slo_details/hooks/use_slo_actions'; +import { useActionModal } from '../../../context/action_modal'; interface Props { slo: SLOWithSummaryResponse; isActionsPopoverOpen: boolean; setIsActionsPopoverOpen: (value: boolean) => void; - setDeleteConfirmationModalOpen: (value: boolean) => void; - setResetConfirmationModalOpen: (value: boolean) => void; - setEnableConfirmationModalOpen: (value: boolean) => void; - setDisableConfirmationModalOpen: (value: boolean) => void; setIsAddRuleFlyoutOpen: (value: boolean) => void; setIsEditRuleFlyoutOpen: (value: boolean) => void; setDashboardAttachmentReady?: (value: boolean) => void; @@ -65,10 +61,6 @@ export function SloItemActions({ setIsActionsPopoverOpen, setIsAddRuleFlyoutOpen, setIsEditRuleFlyoutOpen, - setDeleteConfirmationModalOpen, - setResetConfirmationModalOpen, - setEnableConfirmationModalOpen, - setDisableConfirmationModalOpen, setDashboardAttachmentReady, btnProps, }: Props) { @@ -79,7 +71,7 @@ export function SloItemActions({ const executionContextName = executionContext.get().name; const isDashboardContext = executionContextName === 'dashboards'; const { data: permissions } = usePermissions(); - const navigateToClone = useCloneSlo(); + const { triggerAction } = useActionModal(); const { handleNavigateToRules, @@ -105,15 +97,14 @@ export function SloItemActions({ }; const handleClone = () => { - navigateToClone(slo); + triggerAction({ type: 'clone', item: slo, onConfirm: () => setIsActionsPopoverOpen(false) }); }; const handleDelete = () => { if (!!remoteDeleteUrl) { window.open(remoteDeleteUrl, '_blank'); } else { - setDeleteConfirmationModalOpen(true); - setIsActionsPopoverOpen(false); + triggerAction({ type: 'delete', item: slo, onConfirm: () => setIsActionsPopoverOpen(false) }); } }; @@ -121,8 +112,7 @@ export function SloItemActions({ if (!!remoteResetUrl) { window.open(remoteResetUrl, '_blank'); } else { - setResetConfirmationModalOpen(true); - setIsActionsPopoverOpen(false); + triggerAction({ type: 'reset', item: slo, onConfirm: () => setIsActionsPopoverOpen(false) }); } }; @@ -130,8 +120,7 @@ export function SloItemActions({ if (!!remoteEnableUrl) { window.open(remoteEnableUrl, '_blank'); } else { - setEnableConfirmationModalOpen(true); - setIsActionsPopoverOpen(false); + triggerAction({ type: 'enable', item: slo, onConfirm: () => setIsActionsPopoverOpen(false) }); } }; @@ -139,8 +128,11 @@ export function SloItemActions({ if (!!remoteDisableUrl) { window.open(remoteDisableUrl, '_blank'); } else { - setDisableConfirmationModalOpen(true); - setIsActionsPopoverOpen(false); + triggerAction({ + type: 'disable', + item: slo, + onConfirm: () => setIsActionsPopoverOpen(false), + }); } }; @@ -319,7 +311,7 @@ export function SloItemActions({ {showRemoteLinkIcon} , ].concat( - !isDashboardContext ? ( + !isDashboardContext && !!setDashboardAttachmentReady ? ( { - setDeleteConfirmationModalOpen(false); - }; - - const handleResetConfirm = () => { - resetSlo({ id: slo.id, name: slo.name }); - setResetConfirmationModalOpen(false); - }; - - const handleResetCancel = () => { - setResetConfirmationModalOpen(false); - }; - - const handleEnableCancel = () => { - setEnableConfirmationModalOpen(false); - }; - const handleEnableConfirm = () => { - enableSlo({ id: slo.id, name: slo.name }); - setEnableConfirmationModalOpen(false); - }; - - const handleDisableCancel = () => { - setDisableConfirmationModalOpen(false); - }; - const handleDisableConfirm = () => { - disableSlo({ id: slo.id, name: slo.name }); - setDisableConfirmationModalOpen(false); - }; - return ( @@ -139,13 +93,10 @@ export function SloListItem({ setIsAddRuleFlyoutOpen={setIsAddRuleFlyoutOpen} setIsEditRuleFlyoutOpen={setIsEditRuleFlyoutOpen} setIsActionsPopoverOpen={setIsActionsPopoverOpen} - setDeleteConfirmationModalOpen={setDeleteConfirmationModalOpen} - setResetConfirmationModalOpen={setResetConfirmationModalOpen} - setEnableConfirmationModalOpen={setEnableConfirmationModalOpen} - setDisableConfirmationModalOpen={setDisableConfirmationModalOpen} /> + - - {isDeleteConfirmationModalOpen ? ( - - ) : null} - - {isResetConfirmationModalOpen ? ( - - ) : null} - - {isEnableConfirmationModalOpen ? ( - - ) : null} - - {isDisableConfirmationModalOpen ? ( - - ) : null} ); } diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slos_view.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slos_view.tsx index ae8ec83e33fa3..92195666499f9 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slos_view.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/components/slos_view.tsx @@ -8,13 +8,14 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { SLOWithSummaryResponse } from '@kbn/slo-schema'; import React from 'react'; +import { ActionModalProvider } from '../../../context/action_modal'; +import type { ViewType } from '../types'; import { SloListCardView } from './card_view/slo_list_card_view'; import { SloListCompactView } from './compact_view/slo_list_compact_view'; import { HealthCallout } from './health_callout/health_callout'; import { SloListEmpty } from './slo_list_empty'; import { SloListError } from './slo_list_error'; import { SloListView } from './slo_list_view/slo_list_view'; -import type { ViewType } from '../types'; export interface Props { sloList: SLOWithSummaryResponse[]; @@ -34,37 +35,35 @@ export function SlosView({ sloList, loading, error, view }: Props) { if (view === 'cardView') { return ( - - - - - - - - + + + ); } if (view === 'compactView') { return ( - - - - - - - - + + + ); } + return ( + + + + ); +} + +function Wrapper({ children, sloList }: { children: React.ReactNode } & Pick) { return ( - + {children} ); diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx index 4f6df06d76f80..830119bef8f79 100644 --- a/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx +++ b/x-pack/solutions/observability/plugins/slo/public/pages/slos/slos.test.tsx @@ -45,7 +45,6 @@ jest.mock('../../hooks/use_delete_slo'); jest.mock('../../hooks/use_delete_slo_instance'); jest.mock('../../hooks/use_fetch_historical_summary'); jest.mock('../../hooks/use_permissions'); -jest.mock('../../hooks/use_capabilities'); jest.mock('../../hooks/use_create_data_view'); jest.mock('@kbn/ebt-tools'); @@ -72,8 +71,8 @@ const mockDeleteSlo = jest.fn(); const mockDeleteInstance = jest.fn(); useCreateSloMock.mockReturnValue({ mutate: mockCreateSlo }); -useDeleteSloMock.mockReturnValue({ mutateAsync: mockDeleteSlo }); -useDeleteSloInstanceMock.mockReturnValue({ mutateAsync: mockDeleteInstance }); +useDeleteSloMock.mockReturnValue({ mutate: mockDeleteSlo }); +useDeleteSloInstanceMock.mockReturnValue({ mutate: mockDeleteInstance }); useCreateDataViewMock.mockReturnValue({}); const mockNavigate = jest.fn(); diff --git a/x-pack/solutions/observability/plugins/slo/server/routes/slo/bulk_delete.ts b/x-pack/solutions/observability/plugins/slo/server/routes/slo/bulk_delete.ts index 21a780bc9e1b6..ac30139085183 100644 --- a/x-pack/solutions/observability/plugins/slo/server/routes/slo/bulk_delete.ts +++ b/x-pack/solutions/observability/plugins/slo/server/routes/slo/bulk_delete.ts @@ -4,7 +4,6 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { BulkDeleteStatusResponse, bulkDeleteParamsSchema, @@ -54,7 +53,7 @@ export const getBulkDeleteStatusRoute = createSloServerRoute({ }, }, params: bulkDeleteStatusParamsSchema, - handler: async ({ params, plugins }) => { + handler: async ({ params, plugins }): Promise => { await assertPlatinumLicense(plugins); const taskManager = await plugins.taskManager.start(); @@ -63,7 +62,6 @@ export const getBulkDeleteStatusRoute = createSloServerRoute({ if (!task) { return { isDone: true, - results: [], error: 'Task not found', } satisfies BulkDeleteStatusResponse; } diff --git a/x-pack/test/api_integration/deployment_agnostic/apis/observability/slo/bulk_delete.ts b/x-pack/test/api_integration/deployment_agnostic/apis/observability/slo/bulk_delete.ts index 2b4f94f4c70bc..242a2901fa10d 100644 --- a/x-pack/test/api_integration/deployment_agnostic/apis/observability/slo/bulk_delete.ts +++ b/x-pack/test/api_integration/deployment_agnostic/apis/observability/slo/bulk_delete.ts @@ -78,7 +78,7 @@ export default function ({ getService }: DeploymentAgnosticFtrProviderContext) { it('returns task not found', async () => { const status = await sloApi.bulkDeleteStatus('inexistant', adminRoleAuthc); - expect(status).eql({ isDone: true, results: [], error: 'Task not found' }); + expect(status).eql({ isDone: true, error: 'Task not found' }); }); }); }