Repository: elastic/kibana
Pull request #220123: [SLO] Bulk purge UI
Author: login: baileycash-elastic
Labels: name: release_note:skip
description: Skip the PR/issue when compiling release notes
name: Team:actionable-obs
description: Formerly "obs-ux-management", responsible for SLO, o11y alerting, significant events, & synthetics.
name: backport:version
description: Backport to applied version labels
name: v9.1.0
name: v8.19.0
Created: 2025-05-05T17:01:40Z | Merged: 2025-05-23T13:33:48Z

Implements #219835

<img width="700" alt="Screenshot 2025-05-22 at 5 08 17 PM" src="https://github.com/user-attachments/assets/46691652-e290-461f-8ff9-a2f91bb72f11" />



<!--ONMERGE {"backportTargets":["8.19"]} ONMERGE-->


Diff:
diff --git a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts
index 7764a20bf6ef6..cc67a8bc8b9f8 100644
--- a/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts
+++ b/x-pack/platform/packages/shared/kbn-slo-schema/src/rest_specs/routes/bulk_purge_rollup.ts
@@ -40,7 +40,13 @@ interface BulkPurgeRollupResponse {
 }
 
 type BulkPurgePolicyType = t.TypeOf<typeof bulkPurgePolicy>;
+type BulkPurgeRollupInput = t.OutputOf<typeof bulkPurgeRollupSchema.props.body>; // Raw payload sent by the frontend
 type BulkPurgeRollupParams = t.TypeOf<typeof bulkPurgeRollupSchema.props.body>;
 
-export type { BulkPurgeRollupResponse, BulkPurgePolicyType, BulkPurgeRollupParams };
+export type {
+  BulkPurgeRollupResponse,
+  BulkPurgePolicyType,
+  BulkPurgeRollupInput,
+  BulkPurgeRollupParams,
+};
 export { bulkPurgeRollupSchema };
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
index 8f7621e42f673..fa4f09e1b387f 100644
--- 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
@@ -18,7 +18,7 @@ export interface Props {
 }
 
 export function SloBulkDeleteConfirmationModal({ items, onCancel, onConfirm }: Props) {
-  const { mutate: bulkDelete } = useBulkDeleteSlo();
+  const { mutate: bulkDelete } = useBulkDeleteSlo({ onConfirm });
 
   return (
     <EuiConfirmModal
@@ -40,7 +40,6 @@ export function SloBulkDeleteConfirmationModal({ items, onCancel, onConfirm }: P
       onCancel={onCancel}
       onConfirm={() => {
         bulkDelete({ items: items.map((item) => ({ id: item.id })) });
-        onConfirm();
       }}
     >
       {i18n.translate('xpack.slo.bulkDeleteConfirmationModal.descriptionText', {
diff --git a/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx b/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
new file mode 100644
index 0000000000000..e4f48cd07ff87
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
@@ -0,0 +1,173 @@
+/*
+ * 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 {
+  EuiCallOut,
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutate: bulkPurge } = useBulkPurgeRollupData({ onConfirm });
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  const purgeTimeLabel = i18n.translate(
+    'xpack.slo.bulkPurgeConfirmationModal.purgeDataDescription',
+    {
+      defaultMessage: 'Purge data older than',
+    }
+  );
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={() => {
+        bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Choose basis by which to purge data',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Age of data',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Specific date and time',
+              }),
+            },
+          ]}
+          idSelected={purgeType}
+          onChange={(val: string) => {
+            setPurgeType(val);
+          }}
+          name="radio group"
+        />
+      </EuiFormRow>
+      {purgeType === 'fixed_age' ? (
+        <EuiFormRow label={purgeTimeLabel}>
+          <EuiRadioGroup
+            data-test-subj="sloBulkPurgeConfirmationModalPurgeAge"
+            options={[
+              {
+                id: '7d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last7DaysLabel', {
+                  defaultMessage: '7 days',
+                }),
+              },
+              {
+                id: '30d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last30DaysLabel', {
+                  defaultMessage: '30 days',
+                }),
+              },
+              {
+                id: '90d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last90DaysLabel', {
+                  defaultMessage: '90 days',
+                }),
+              },
+            ]}
+            idSelected={age}
+            onChange={setAge}
+          />
+        </EuiFormRow>
+      ) : (
+        <EuiFormRow label={purgeTimeLabel}>
+          <EuiDatePicker showTimeSelect selected={purgeDate} onChange={setPurgeDate} />
+        </EuiFormRow>
+      )}
+      <EuiFormRow>
+        <EuiCheckbox
+          id={basicCheckboxId}
+          checked={forcePurge}
+          onChange={(e) => {
+            setForgePurge(e.target.checked);
+          }}
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.forcePurge', {
+            defaultMessage: 'Ignore purge policy restrictions',
+          })}
+        />
+      </EuiFormRow>
+      {forcePurge && (
+        <>
+          <EuiSpacer size="s" />
+          <EuiCallOut
+            color="warning"
+            size="s"
+            title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.forceWarning', {
+              defaultMessage:
+                'Ignoring purge policy restrictions may delete data that is used to calculate SLOs',
+            })}
+          />
+        </>
+      )}
+    </EuiConfirmModal>
+  );
+}
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
index e90f94ebf74ed..e473b6dca34fc 100644
--- a/x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx
+++ b/x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx
@@ -13,6 +13,7 @@ import { SloDisableConfirmationModal } from '../components/slo/disable_confirmat
 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';
+import { SloBulkPurgeConfirmationModal } from '../components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal';
 
 type Action = SingleAction | BulkAction;
 
@@ -27,7 +28,7 @@ interface SingleAction extends BaseAction {
 }
 
 interface BulkAction extends BaseAction {
-  type: 'bulk_delete';
+  type: 'bulk_delete' | 'bulk_purge';
   items: SLODefinitionResponse[];
 }
 
@@ -98,6 +99,14 @@ export function ActionModalProvider({ children }: { children: ReactNode }) {
             onConfirm={handleOnConfirm}
           />
         );
+      case 'bulk_purge':
+        return (
+          <SloBulkPurgeConfirmationModal
+            items={action.items}
+            onCancel={handleOnCancel}
+            onConfirm={handleOnConfirm}
+          />
+        );
       default:
         return null;
     }
diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx
index 08b09af29ff19..aacbf68b0468a 100644
--- a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx
+++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx
@@ -5,31 +5,96 @@
  * 2.0.
  */
 
-import { EuiButtonEmpty } from '@elastic/eui';
+import { EuiButtonEmpty, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import { SLODefinitionResponse } from '@kbn/slo-schema';
 import React from 'react';
 import { useActionModal } from '../../../context/action_modal';
 
-export function SloManagementBulkActions({ items }: { items: SLODefinitionResponse[] }) {
+export function SloManagementBulkActions({
+  items,
+  setSelectedItems,
+}: {
+  items: SLODefinitionResponse[];
+  setSelectedItems: Function;
+}) {
   const { triggerAction } = useActionModal();
+  const [isOpen, setIsOpen] = React.useState(false);
+
+  const resetSelectedItems = () => {
+    setSelectedItems([]);
+  };
+
+  const SELECTED_SLOS = (count: number) => {
+    return i18n.translate('xpack.slo.sloManagementTable.selectedSLOsButton', {
+      values: { count },
+      defaultMessage: 'Selected {count} {count, plural, =1 {SLO} other {SLOs}}',
+    });
+  };
+
+  const panels = [
+    {
+      id: 0,
+      hasFocus: false,
+      items: [
+        {
+          'data-test-subj': 'sloSloManagementTableBulkDeleteButton',
+          onClick: () => {
+            triggerAction({ items, type: 'bulk_delete', onConfirm: () => resetSelectedItems() });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel',
+            {
+              defaultMessage: 'Delete',
+            }
+          ),
+        },
+        {
+          'data-test-subj': 'sloSloManagementTableBulkPurgeButton',
+          onClick: () => {
+            triggerAction({
+              items,
+              type: 'bulk_purge',
+              onConfirm: () => resetSelectedItems(),
+            });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkPurgeButtonLabel',
+            {
+              defaultMessage: 'Purge Rollup Data',
+            }
+          ),
+        },
+      ],
+    },
+  ];
 
   return (
-    <EuiButtonEmpty
-      data-test-subj="sloSloManagementTableBulkDeleteButton"
-      disabled={items.length === 0}
-      onClick={() => {
-        triggerAction({ items, type: 'bulk_delete' });
-      }}
-      size="xs"
-      css={{ blockSize: '0px' }}
+    <EuiPopover
+      id={`popover-slo-management-bulk-actions`}
+      panelPaddingSize="none"
+      button={
+        <EuiButtonEmpty
+          data-test-subj="sloManagementTableBulkMenuButton"
+          css={{ blockSize: '0px', marginBottom: '5px' }}
+          size="xs"
+          onClick={() => setIsOpen(!isOpen)}
+        >
+          {SELECTED_SLOS(items.length)} <EuiIcon type="arrowDown" size="s" />
+        </EuiButtonEmpty>
+      }
+      isOpen={isOpen}
+      closePopover={() => setIsOpen(false)}
+      anchorPosition="downLeft"
     >
-      {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel', {
-        defaultMessage: 'Delete {count} SLOs',
-        values: {
-          count: items.length,
-        },
-      })}
-    </EuiButtonEmpty>
+      <EuiContextMenu
+        initialPanelId={0}
+        size="s"
+        panels={panels}
+        data-test-subj="detailsCollapsedActionPanel"
+      />
+    </EuiPopover>
   );
 }
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 2b69e049f31ae..6297bf44ec28f 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
@@ -57,18 +57,19 @@ export function SloManagementTable() {
   const { tasks } = useBulkOperation();
 
   const [selectedItems, setSelectedItems] = useState<SLODefinitionResponse[]>([]);
+
   const onSelectionChange = (items: SLODefinitionResponse[]) => {
     setSelectedItems(items);
   };
 
   const selection: EuiTableSelectionType<SLODefinitionResponse> = {
+    selected: selectedItems,
     selectable: (item: SLODefinitionResponse) => {
       return !tasks.find(
         (task) => task.status === 'in-progress' && task.items.some((i) => i.id === item.id)
       );
     },
     onSelectionChange,
-    initialSelected: [],
   };
 
   const actions: Array<DefaultItemAction<SLODefinitionResponse>> = [
@@ -266,7 +267,7 @@ export function SloManagementTable() {
           })}
         </EuiText>
       ) : (
-        <SloManagementBulkActions items={selectedItems} />
+        <SloManagementBulkActions items={selectedItems} setSelectedItems={onSelectionChange} />
       )}
 
       <EuiSpacer size="s" />
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
index b2f5bdc4af56d..431d2b3c48ddf 100644
--- 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
@@ -15,7 +15,7 @@ import { useBulkOperation } from '../context/bulk_operation';
 
 type ServerError = IHttpFetchError<ResponseErrorBody>;
 
-export function useBulkDeleteSlo() {
+export function useBulkDeleteSlo({ onConfirm }: { onConfirm?: () => void } = {}) {
   const {
     notifications: { toasts },
   } = useKibana().services;
@@ -29,22 +29,13 @@ export function useBulkDeleteSlo() {
   >(
     ['bulkDeleteSlo'],
     ({ items }) => {
-      try {
-        return sloClient.fetch('POST /api/observability/slos/_bulk_delete 2023-10-31', {
-          params: {
-            body: {
-              list: items.map(({ id }) => id),
-            },
+      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 }) => {
@@ -64,6 +55,8 @@ export function useBulkDeleteSlo() {
             values: { count: items.length },
           })
         );
+
+        onConfirm?.();
       },
     }
   );
diff --git a/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_purge_rollup_data.ts b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_purge_rollup_data.ts
new file mode 100644
index 0000000000000..7476cb8c4e06d
--- /dev/null
+++ b/x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_purge_rollup_data.ts
@@ -0,0 +1,57 @@
+/*
+ * 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 { BulkPurgeRollupInput, BulkPurgeRollupResponse } from '@kbn/slo-schema';
+import { useMutation } from '@tanstack/react-query';
+
+import { useKibana } from '../../../hooks/use_kibana';
+import { usePluginContext } from '../../../hooks/use_plugin_context';
+
+type ServerError = IHttpFetchError<ResponseErrorBody>;
+
+export function useBulkPurgeRollupData({ onConfirm }: { onConfirm?: () => void } = {}) {
+  const {
+    notifications: { toasts },
+  } = useKibana().services;
+  const { sloClient } = usePluginContext();
+
+  return useMutation<BulkPurgeRollupResponse, ServerError, BulkPurgeRollupInput>(
+    ['bulkPurgeRollupData'],
+    ({ purgePolicy, force, list }) => {
+      return sloClient.fetch('POST /api/observability/slos/_bulk_purge_rollup 2023-10-31', {
+        params: {
+          body: {
+            purgePolicy,
+            force,
+            list,
+          },
+        },
+      });
+    },
+    {
+      onError: (error, { list }) => {
+        toasts.addError(new Error(error.body?.message ?? error.message), {
+          title: i18n.translate('xpack.slo.bulkPurge.errorNotification', {
+            defaultMessage: 'Failed to schedule bulk purge of rollup data for {count} SLOs',
+            values: { count: list.length },
+          }),
+        });
+      },
+      onSuccess: (_, { list }) => {
+        toasts.addSuccess(
+          i18n.translate('xpack.slo.bulkPurge.successNotification', {
+            defaultMessage: 'Bulk purge of rollup data for {count} SLOs scheduled',
+            values: { count: list.length },
+          })
+        );
+        onConfirm?.();
+      },
+    }
+  );
+}


Reviews:
user: login: kdelemme
body: some copy to improve
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T19:45:52Z
user: login: kdelemme
body: Thanks for the labels change 👍🏻 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-23T12:55:27Z

Review comments:
diff_hunk: @@ -0,0 +1,68 @@
+/*
+ * 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 { BulkPurgeRollupInput, BulkPurgeRollupResponse } from '@kbn/slo-schema';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+
+import { useKibana } from '../../../hooks/use_kibana';
+import { usePluginContext } from '../../../hooks/use_plugin_context';
+
+type ServerError = IHttpFetchError<ResponseErrorBody>;
+
+export function useBulkPurgeRollupData() {
+  const {
+    notifications: { toasts },
+  } = useKibana().services;
+  const { sloClient } = usePluginContext();
+  const queryClient = useQueryClient();
+
+  return useMutation<BulkPurgeRollupResponse, ServerError, BulkPurgeRollupInput>(
+    ['bulkPurgeRollupData'],
+    ({ purgePolicy, force, list }) => {
+      try {
+        return sloClient.fetch('POST /api/observability/slos/_bulk_purge_rollup 2023-10-31', {
+          params: {
+            body: {
+              purgePolicy,
+              force,
+              list,
+            },
+          },
+        });
+      } catch (error) {
+        return Promise.reject(
+          i18n.translate('xpack.slo.bulkPurge.errorMessage', {
+            defaultMessage:
+              'Failed to bulk purge of rollup data for {count} SLOs, something went wrong: {error}',
+            values: { error: String(error), count: list.length },
+          })
+        );
+      }
+    },
path: x-pack/solutions/observability/plugins/slo/public/pages/slo_management/hooks/use_bulk_purge_rollup_data.ts
user: login: kdelemme
body: You probably took the inspiration from the bulk delete hook (my bad), but i think we can remove the try catch here, and just return the api call, the onError callback will take care of any errors.

Can you do the same in the bulk delete hook please?
created_at: 2025-05-22T18:52:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: Use the non async mutate function. 
created_at: 2025-05-22T19:00:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: I think we can be more descriptive for the user, something like "Purge data older than a specific duration" or something like that. wdyt?
created_at: 2025-05-22T19:01:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Fixed date/time',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: I think we can be more descriptive for the user, something like "Purge data older than a specific datetime" or something like that. wdyt?
created_at: 2025-05-22T19:02:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Fixed date/time',
+              }),
+            },
+          ]}
+          idSelected={purgeType}
+          onChange={(val: string) => {
+            setPurgeType(val);
+          }}
+          name="radio group"
+        />
+      </EuiFormRow>
+      {purgeType === 'fixed_age' ? (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeAgeLabel', {
+            defaultMessage: 'Purge age',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: maybe: "Purge data older than"
created_at: 2025-05-22T19:09:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Fixed date/time',
+              }),
+            },
+          ]}
+          idSelected={purgeType}
+          onChange={(val: string) => {
+            setPurgeType(val);
+          }}
+          name="radio group"
+        />
+      </EuiFormRow>
+      {purgeType === 'fixed_age' ? (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeAgeLabel', {
+            defaultMessage: 'Purge age',
+          })}
+        >
+          <EuiRadioGroup
+            data-test-subj="sloBulkPurgeConfirmationModalPurgeAge"
+            options={[
+              {
+                id: '7d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last7DaysLabel', {
+                  defaultMessage: 'Last 7 days',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: "7 days"
created_at: 2025-05-22T19:09:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Fixed date/time',
+              }),
+            },
+          ]}
+          idSelected={purgeType}
+          onChange={(val: string) => {
+            setPurgeType(val);
+          }}
+          name="radio group"
+        />
+      </EuiFormRow>
+      {purgeType === 'fixed_age' ? (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeAgeLabel', {
+            defaultMessage: 'Purge age',
+          })}
+        >
+          <EuiRadioGroup
+            data-test-subj="sloBulkPurgeConfirmationModalPurgeAge"
+            options={[
+              {
+                id: '7d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last7DaysLabel', {
+                  defaultMessage: 'Last 7 days',
+                }),
+              },
+              {
+                id: '30d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last30DaysLabel', {
+                  defaultMessage: 'Last 30 days',
+                }),
+              },
+              {
+                id: '90d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last90DaysLabel', {
+                  defaultMessage: 'Last 90 days',
+                }),
+              },
+            ]}
+            idSelected={age}
+            onChange={setAge}
+          />
+        </EuiFormRow>
+      ) : (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeDateLabel', {
+            defaultMessage: 'Purge date',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: "Purge data older than
created_at: 2025-05-22T19:10:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,166 @@
+/*
+ * 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 {
+  EuiCheckbox,
+  EuiConfirmModal,
+  EuiDatePicker,
+  EuiFormRow,
+  EuiRadioGroup,
+  EuiSpacer,
+  useGeneratedHtmlId,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { SLODefinitionResponse } from '@kbn/slo-schema';
+import React from 'react';
+import moment, { Moment } from 'moment';
+import { useBulkPurgeRollupData } from '../../../pages/slo_management/hooks/use_bulk_purge_rollup_data';
+
+export interface Props {
+  onCancel: () => void;
+  onConfirm: () => void;
+  items: SLODefinitionResponse[];
+}
+
+export function SloBulkPurgeConfirmationModal({ items, onCancel, onConfirm }: Props) {
+  const { mutateAsync: bulkPurge } = useBulkPurgeRollupData();
+
+  const [purgeDate, setPurgeDate] = React.useState<Moment | null>(moment());
+  const [purgeType, setPurgeType] = React.useState<string>('fixed_age');
+  const [forcePurge, setForgePurge] = React.useState<boolean>(false);
+  const [age, setAge] = React.useState('7d');
+
+  const basicCheckboxId = useGeneratedHtmlId({ prefix: 'basicCheckbox' });
+
+  return (
+    <EuiConfirmModal
+      buttonColor="danger"
+      data-test-subj="sloBulkPurgeConfirmationModal"
+      title={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.title', {
+        defaultMessage: 'Purge {count} SLOs',
+        values: { count: items.length },
+      })}
+      cancelButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.cancelButtonLabel', {
+        defaultMessage: 'Cancel',
+      })}
+      confirmButtonText={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.confirmButtonLabel', {
+        defaultMessage: 'Purge',
+      })}
+      onCancel={onCancel}
+      onConfirm={async () => {
+        await bulkPurge({
+          list: items.map(({ id }) => id),
+          purgePolicy:
+            purgeType === 'fixed_age'
+              ? {
+                  purgeType: 'fixed_age',
+                  age,
+                }
+              : {
+                  purgeType: 'fixed_time',
+                  timestamp: purgeDate!.toISOString(),
+                },
+          force: forcePurge,
+        })
+          .then(() => {
+            onConfirm();
+          })
+          .catch((error) => {
+            console.error('error', error);
+          });
+      }}
+    >
+      {i18n.translate('xpack.slo.bulkPurgeConfirmationModal.descriptionText', {
+        defaultMessage:
+          'Rollup data for {count} SLOs will be purged according to the policy provided below.',
+        values: { count: items.length },
+      })}
+      <EuiSpacer size="m" />
+      <EuiFormRow
+        label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeTypeLabel', {
+          defaultMessage: 'Purge type',
+        })}
+      >
+        <EuiRadioGroup
+          data-test-subj="sloBulkPurgeConfirmationModalPurgeType"
+          options={[
+            {
+              id: 'fixed_age',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedAgeLabel', {
+                defaultMessage: 'Fixed age',
+              }),
+            },
+            {
+              id: 'fixed_time',
+              label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.fixedDateTimeLabel', {
+                defaultMessage: 'Fixed date/time',
+              }),
+            },
+          ]}
+          idSelected={purgeType}
+          onChange={(val: string) => {
+            setPurgeType(val);
+          }}
+          name="radio group"
+        />
+      </EuiFormRow>
+      {purgeType === 'fixed_age' ? (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeAgeLabel', {
+            defaultMessage: 'Purge age',
+          })}
+        >
+          <EuiRadioGroup
+            data-test-subj="sloBulkPurgeConfirmationModalPurgeAge"
+            options={[
+              {
+                id: '7d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last7DaysLabel', {
+                  defaultMessage: 'Last 7 days',
+                }),
+              },
+              {
+                id: '30d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last30DaysLabel', {
+                  defaultMessage: 'Last 30 days',
+                }),
+              },
+              {
+                id: '90d',
+                label: i18n.translate('xpack.slo.bulkPurgeConfirmationModal.last90DaysLabel', {
+                  defaultMessage: 'Last 90 days',
+                }),
+              },
+            ]}
+            idSelected={age}
+            onChange={setAge}
+          />
+        </EuiFormRow>
+      ) : (
+        <EuiFormRow
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.purgeDateLabel', {
+            defaultMessage: 'Purge date',
+          })}
+        >
+          <EuiDatePicker showTimeSelect selected={purgeDate} onChange={setPurgeDate} />
+        </EuiFormRow>
+      )}
+      <EuiFormRow>
+        <EuiCheckbox
+          id={basicCheckboxId}
+          checked={forcePurge}
+          onChange={(e) => {
+            setForgePurge(e.target.checked);
+          }}
+          label={i18n.translate('xpack.slo.bulkPurgeConfirmationModal.forcePurge', {
+            defaultMessage: 'Ignore purge policy restrictions',
path: x-pack/solutions/observability/plugins/slo/public/components/slo/bulk_purge_confirmation_modal/bulk_purge_confirmation_modal.tsx
user: login: kdelemme
body: "Ignore purge policy restrictions (can delete data belonging to the active SLO window)"
created_at: 2025-05-22T19:11:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -22,12 +23,12 @@ interface BaseAction {
 }
 
 interface SingleAction extends BaseAction {
-  type: 'clone' | 'delete' | 'reset' | 'enable' | 'disable';
+  type: 'clone' | 'delete' | 'reset' | 'enable' | 'disable' | 'purge';
path: x-pack/solutions/observability/plugins/slo/public/context/action_modal.tsx
user: login: kdelemme
body: we don't have a single purge action yet implemented, let's add this when we do implement it
created_at: 2025-05-22T19:13:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,31 +5,99 @@
  * 2.0.
  */
 
-import { EuiButtonEmpty } from '@elastic/eui';
+import { EuiButtonEmpty, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import { SLODefinitionResponse } from '@kbn/slo-schema';
 import React from 'react';
 import { useActionModal } from '../../../context/action_modal';
 
-export function SloManagementBulkActions({ items }: { items: SLODefinitionResponse[] }) {
+export function SloManagementBulkActions({
+  items,
+  setSelectedItems,
+}: {
+  items: SLODefinitionResponse[];
+  setSelectedItems: Function;
+}) {
   const { triggerAction } = useActionModal();
+  const [isOpen, setIsOpen] = React.useState(false);
+
+  const resetSelectedItems = () => {
+    setSelectedItems([]);
+  };
+
+  const panels = [
+    {
+      id: 0,
+      hasFocus: false,
+      items: [
+        {
+          'data-test-subj': 'sloSloManagementTableBulkDeleteButton',
+          onClick: () => {
+            triggerAction({ items, type: 'bulk_delete' });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel',
+            {
+              defaultMessage: 'Delete',
+            }
+          ),
+        },
+        {
+          'data-test-subj': 'sloSloManagementTableBulkPurgeButton',
+          onClick: () => {
+            triggerAction({
+              items,
+              type: 'bulk_purge',
+              onConfirm() {
+                resetSelectedItems();
+              },
+            });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkPurgeButtonLabel',
+            {
+              defaultMessage: 'Purge Rollup Data',
+            }
+          ),
+        },
+      ],
+    },
+  ];
 
   return (
-    <EuiButtonEmpty
-      data-test-subj="sloSloManagementTableBulkDeleteButton"
-      disabled={items.length === 0}
-      onClick={() => {
-        triggerAction({ items, type: 'bulk_delete' });
-      }}
-      size="xs"
-      css={{ blockSize: '0px' }}
+    <EuiPopover
+      id={`popover-slo-management-bulk-actions`}
+      panelPaddingSize="none"
+      button={
+        <EuiButtonEmpty
+          data-test-subj="sloManagementTableBulkMenuButton"
+          css={{ blockSize: '0px', marginBottom: '5px' }}
+          size="xs"
+          onClick={() => setIsOpen(!isOpen)}
+        >
+          {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkMenuLabel', {
+            defaultMessage: 'Perform bulk action on {count} SLOs',
+            values: {
+              count: items.length,
+            },
+          })}{' '}
+          <EuiIcon type="arrowDown" size="s" />
+        </EuiButtonEmpty>
+      }
+      isOpen={isOpen}
+      closePopover={() => setIsOpen(false)}
+      anchorPosition="downLeft"
     >
-      {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel', {
-        defaultMessage: 'Delete {count} SLOs',
-        values: {
-          count: items.length,
-        },
-      })}
-    </EuiButtonEmpty>
+      <EuiContextMenu
+        initialPanelId={0}
+        size="s"
+        panels={panels}
+        className="actDetailsCollapsedItemActions"
+        data-test-subj="detailsCollapsedActionPanel"
+        data-testid="detailsCollapsedActionPanel"
path: x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx
user: login: kdelemme
body: do we need data-testid? we already have `data-test-subj`
created_at: 2025-05-22T19:17:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -5,31 +5,99 @@
  * 2.0.
  */
 
-import { EuiButtonEmpty } from '@elastic/eui';
+import { EuiButtonEmpty, EuiContextMenu, EuiIcon, EuiPopover } from '@elastic/eui';
 import { i18n } from '@kbn/i18n';
 import { SLODefinitionResponse } from '@kbn/slo-schema';
 import React from 'react';
 import { useActionModal } from '../../../context/action_modal';
 
-export function SloManagementBulkActions({ items }: { items: SLODefinitionResponse[] }) {
+export function SloManagementBulkActions({
+  items,
+  setSelectedItems,
+}: {
+  items: SLODefinitionResponse[];
+  setSelectedItems: Function;
+}) {
   const { triggerAction } = useActionModal();
+  const [isOpen, setIsOpen] = React.useState(false);
+
+  const resetSelectedItems = () => {
+    setSelectedItems([]);
+  };
+
+  const panels = [
+    {
+      id: 0,
+      hasFocus: false,
+      items: [
+        {
+          'data-test-subj': 'sloSloManagementTableBulkDeleteButton',
+          onClick: () => {
+            triggerAction({ items, type: 'bulk_delete' });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel',
+            {
+              defaultMessage: 'Delete',
+            }
+          ),
+        },
+        {
+          'data-test-subj': 'sloSloManagementTableBulkPurgeButton',
+          onClick: () => {
+            triggerAction({
+              items,
+              type: 'bulk_purge',
+              onConfirm() {
+                resetSelectedItems();
+              },
+            });
+            setIsOpen(false);
+          },
+          name: i18n.translate(
+            'xpack.slo.sloManagementTable.sloSloManagementTableBulkPurgeButtonLabel',
+            {
+              defaultMessage: 'Purge Rollup Data',
+            }
+          ),
+        },
+      ],
+    },
+  ];
 
   return (
-    <EuiButtonEmpty
-      data-test-subj="sloSloManagementTableBulkDeleteButton"
-      disabled={items.length === 0}
-      onClick={() => {
-        triggerAction({ items, type: 'bulk_delete' });
-      }}
-      size="xs"
-      css={{ blockSize: '0px' }}
+    <EuiPopover
+      id={`popover-slo-management-bulk-actions`}
+      panelPaddingSize="none"
+      button={
+        <EuiButtonEmpty
+          data-test-subj="sloManagementTableBulkMenuButton"
+          css={{ blockSize: '0px', marginBottom: '5px' }}
+          size="xs"
+          onClick={() => setIsOpen(!isOpen)}
+        >
+          {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkMenuLabel', {
+            defaultMessage: 'Perform bulk action on {count} SLOs',
+            values: {
+              count: items.length,
+            },
+          })}{' '}
+          <EuiIcon type="arrowDown" size="s" />
+        </EuiButtonEmpty>
+      }
+      isOpen={isOpen}
+      closePopover={() => setIsOpen(false)}
+      anchorPosition="downLeft"
     >
-      {i18n.translate('xpack.slo.sloManagementTable.sloSloManagementTableBulkDeleteButtonLabel', {
-        defaultMessage: 'Delete {count} SLOs',
-        values: {
-          count: items.length,
-        },
-      })}
-    </EuiButtonEmpty>
+      <EuiContextMenu
+        initialPanelId={0}
+        size="s"
+        panels={panels}
+        className="actDetailsCollapsedItemActions"
path: x-pack/solutions/observability/plugins/slo/public/pages/slo_management/components/slo_management_bulk_actions.tsx
user: login: kdelemme
body: what is this className? I think we can remove
created_at: 2025-05-22T19:17:49Z
author_association: CONTRIBUTOR

PR comments:
user: login: elasticmachine
created_at: 2025-05-09T18:46:51Z
body: Pinging @elastic/obs-ux-management-team (Team:obs-ux-management)
author_association: CONTRIBUTOR
user: login: elasticmachine
created_at: 2025-05-22T21:59:38Z
body: ## :broken_heart: Build Failed
* [Buildkite Build](https://buildkite.com/elastic/kibana-pull-request/builds/301894)
* Commit: b8c031f9382800b222eb4472af5f4fc5895662c4

### Failed CI Steps
* [Quick Checks](https://buildkite.com/elastic/kibana-pull-request/builds/301894#0196f9f0-5d33-4a61-8510-030559ec0629)

### History
* :yellow_heart: [Build #301388](https://buildkite.com/elastic/kibana-pull-request/builds/301388) was flaky ded96420668679b46f2384df45bce839f08b7e09
* :green_heart: [Build #300728](https://buildkite.com/elastic/kibana-pull-request/builds/300728) succeeded 059484484736757aab856cb82b060da12afc205a
* :broken_heart: [Build #300600](https://buildkite.com/elastic/kibana-pull-request/builds/300600) failed ec1ee80011ff1012341297ba2af3e27e853a731c
* :yellow_heart: [Build #300237](https://buildkite.com/elastic/kibana-pull-request/builds/300237) was flaky 24c6131f2f50e5cb0bf7ab1a060e962d04a7e0c3
* :broken_heart: [Build #300219](https://buildkite.com/elastic/kibana-pull-request/builds/300219) failed 7a46bb1b85ba62c26b4dd617e316f1a267cd99e7

<!--buildkite-pr-comment-kibana-pull-request
{"builds":[{"buildStatus":{"state":"failed","success":false,"hasRetries":false,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/301894","number":301894,"commit":"b8c031f9382800b222eb4472af5f4fc5895662c4"},{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/301388","number":301388,"commit":"ded96420668679b46f2384df45bce839f08b7e09"},{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":false},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/300728","number":300728,"commit":"059484484736757aab856cb82b060da12afc205a"},{"buildStatus":{"state":"failed","success":false,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/300600","number":300600,"commit":"ec1ee80011ff1012341297ba2af3e27e853a731c"},{"buildStatus":{"state":"passed","success":true,"hasRetries":true,"hasNonPreemptionRetries":true},"url":"https://buildkite.com/elastic/kibana-pull-request/builds/300237","number":300237,"commit":"24c6131f2f50e5cb0bf7ab1a060e962d04a7e0c3"}],"number":301894}
buildkite-pr-comment-->
author_association: CONTRIBUTOR
user: login: kibanamachine
created_at: 2025-05-23T13:34:03Z
body: Starting backport for target branches: 8.19

https://github.com/elastic/kibana/actions/runs/15211570817
author_association: CONTRIBUTOR
user: login: kibanamachine
created_at: 2025-05-23T13:40:20Z
body: ## 💚 All backports created successfully

| Status | Branch | Result |
|:------:|:------:|:------|
|✅|8.19|[<img src="https://img.shields.io/github/pulls/detail/state/elastic/kibana/221376">](https://github.com/elastic/kibana/pull/221376)|

Note: Successful backport PRs will be merged automatically after passing CI.

### Questions ?
Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport)

<!--- Backport version: 9.6.6 -->
author_association: CONTRIBUTOR