Skip to content

Commit

Permalink
[SR] Delete and execute SLM policies (#41934) (#42412)
Browse files Browse the repository at this point in the history
* Add single and bulk policy delete

* Add policy execution

* Remove early destructuring of provider request responses

* Address PR feedback

* Adjust policy requests for useRequest changes

* Fix policy reload
  • Loading branch information
jen-huang authored Aug 1, 2019
1 parent edcce0c commit 8622e7c
Show file tree
Hide file tree
Showing 15 changed files with 702 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ export { SectionError } from './section_error';
export { SectionLoading } from './section_loading';
export { SnapshotDeleteProvider } from './snapshot_delete_provider';
export { RestoreSnapshotForm } from './restore_snapshot_form';
export { PolicyExecuteProvider } from './policy_execute_provider';
export { PolicyDeleteProvider } from './policy_delete_provider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, useRef, useState } from 'react';
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
import { useAppDependencies } from '../index';
import { deletePolicies } from '../services/http';

interface Props {
children: (deletePolicy: DeletePolicy) => React.ReactElement;
}

export type DeletePolicy = (names: string[], onSuccess?: OnSuccessCallback) => void;

type OnSuccessCallback = (policiesDeleted: string[]) => void;

export const PolicyDeleteProvider: React.FunctionComponent<Props> = ({ children }) => {
const {
core: {
i18n,
notification: { toastNotifications },
},
} = useAppDependencies();
const { FormattedMessage } = i18n;
const [policyNames, setPolicyNames] = useState<string[]>([]);
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const onSuccessCallback = useRef<OnSuccessCallback | null>(null);

const deletePolicyPrompt: DeletePolicy = (names, onSuccess = () => undefined) => {
if (!names || !names.length) {
throw new Error('No policy names specified for deletion');
}
setIsModalOpen(true);
setPolicyNames(names);
onSuccessCallback.current = onSuccess;
};

const closeModal = () => {
setIsModalOpen(false);
setPolicyNames([]);
};

const deletePolicy = () => {
const policiesToDelete = [...policyNames];
deletePolicies(policiesToDelete).then(({ data, error }) => {
const { itemsDeleted, errors } = data || { itemsDeleted: undefined, errors: undefined };

// Surface success notifications
if (itemsDeleted && itemsDeleted.length) {
const hasMultipleSuccesses = itemsDeleted.length > 1;
const successMessage = hasMultipleSuccesses
? i18n.translate('xpack.snapshotRestore.deletePolicy.successMultipleNotificationTitle', {
defaultMessage: 'Deleted {count} policies',
values: { count: itemsDeleted.length },
})
: i18n.translate('xpack.snapshotRestore.deletePolicy.successSingleNotificationTitle', {
defaultMessage: "Deleted policy '{name}'",
values: { name: itemsDeleted[0] },
});
toastNotifications.addSuccess(successMessage);
if (onSuccessCallback.current) {
onSuccessCallback.current([...itemsDeleted]);
}
}

// Surface error notifications
// `error` is generic server error
// `data.errors` are specific errors with removing particular policy(ies)
if (error || (errors && errors.length)) {
const hasMultipleErrors =
(errors && errors.length > 1) || (error && policiesToDelete.length > 1);
const errorMessage = hasMultipleErrors
? i18n.translate('xpack.snapshotRestore.deletePolicy.errorMultipleNotificationTitle', {
defaultMessage: 'Error deleting {count} policies',
values: {
count: (errors && errors.length) || policiesToDelete.length,
},
})
: i18n.translate('xpack.snapshotRestore.deletePolicy.errorSingleNotificationTitle', {
defaultMessage: "Error deleting policy '{name}'",
values: { name: (errors && errors[0].name) || policiesToDelete[0] },
});
toastNotifications.addDanger(errorMessage);
}
});
closeModal();
};

const renderModal = () => {
if (!isModalOpen) {
return null;
}

const isSingle = policyNames.length === 1;

return (
<EuiOverlayMask>
<EuiConfirmModal
title={
isSingle ? (
<FormattedMessage
id="xpack.snapshotRestore.deletePolicy.confirmModal.deleteSingleTitle"
defaultMessage="Delete policy '{name}'?"
values={{ name: policyNames[0] }}
/>
) : (
<FormattedMessage
id="xpack.snapshotRestore.deletePolicy.confirmModal.deleteMultipleTitle"
defaultMessage="Delete {count} policies?"
values={{ count: policyNames.length }}
/>
)
}
onCancel={closeModal}
onConfirm={deletePolicy}
cancelButtonText={
<FormattedMessage
id="xpack.snapshotRestore.deletePolicy.confirmModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
}
confirmButtonText={
<FormattedMessage
id="xpack.snapshotRestore.deletePolicy.confirmModal.confirmButtonLabel"
defaultMessage="Delete {count, plural, one {policy} other {policies}}"
values={{ count: policyNames.length }}
/>
}
buttonColor="danger"
data-test-subj="srdeletePolicyConfirmationModal"
>
{!isSingle ? (
<Fragment>
<p>
<FormattedMessage
id="xpack.snapshotRestore.deletePolicy.confirmModal.deleteMultipleListDescription"
defaultMessage="You are about to delete these policies:"
/>
</p>
<ul>
{policyNames.map(name => (
<li key={name}>{name}</li>
))}
</ul>
</Fragment>
) : null}
</EuiConfirmModal>
</EuiOverlayMask>
);
};

return (
<Fragment>
{children(deletePolicyPrompt)}
{renderModal()}
</Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import React, { Fragment, useRef, useState } from 'react';
import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui';
import { useAppDependencies } from '../index';
import { executePolicy as executePolicyRequest } from '../services/http';

interface Props {
children: (executePolicy: ExecutePolicy) => React.ReactElement;
}

export type ExecutePolicy = (name: string, onSuccess?: OnSuccessCallback) => void;

type OnSuccessCallback = () => void;

export const PolicyExecuteProvider: React.FunctionComponent<Props> = ({ children }) => {
const {
core: {
i18n,
notification: { toastNotifications },
},
} = useAppDependencies();
const { FormattedMessage } = i18n;
const [policyName, setPolicyName] = useState<string>('');
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const onSuccessCallback = useRef<OnSuccessCallback | null>(null);

const executePolicyPrompt: ExecutePolicy = (name, onSuccess = () => undefined) => {
if (!name || !name.length) {
throw new Error('No policy name specified for execution');
}
setIsModalOpen(true);
setPolicyName(name);
onSuccessCallback.current = onSuccess;
};

const closeModal = () => {
setIsModalOpen(false);
setPolicyName('');
};
const executePolicy = () => {
executePolicyRequest(policyName).then(({ data, error }) => {
const { snapshotName } = data || { snapshotName: undefined };

// Surface success notification
if (snapshotName) {
const successMessage = i18n.translate(
'xpack.snapshotRestore.executePolicy.successNotificationTitle',
{
defaultMessage: "Policy '{name}' is running",
values: { name: policyName },
}
);
toastNotifications.addSuccess(successMessage);
if (onSuccessCallback.current) {
onSuccessCallback.current();
}
}

// Surface error notifications
if (error) {
const errorMessage = i18n.translate(
'xpack.snapshotRestore.executePolicy.errorNotificationTitle',
{
defaultMessage: "Error running policy '{name}'",
values: { name: policyName },
}
);
toastNotifications.addDanger(errorMessage);
}
});
closeModal();
};

const renderModal = () => {
if (!isModalOpen) {
return null;
}

return (
<EuiOverlayMask>
<EuiConfirmModal
title={
<FormattedMessage
id="xpack.snapshotRestore.executePolicy.confirmModal.executePolicyTitle"
defaultMessage="Run policy '{name}'?"
values={{ name: policyName }}
/>
}
onCancel={closeModal}
onConfirm={executePolicy}
cancelButtonText={
<FormattedMessage
id="xpack.snapshotRestore.executePolicy.confirmModal.cancelButtonLabel"
defaultMessage="Cancel"
/>
}
confirmButtonText={
<FormattedMessage
id="xpack.snapshotRestore.executePolicy.confirmModal.confirmButtonLabel"
defaultMessage="Run"
/>
}
data-test-subj="srExecutePolicyConfirmationModal"
>
<p>
<FormattedMessage
id="xpack.snapshotRestore.executePolicy.confirmModal.executeDescription"
defaultMessage="A snapshot will be taken immediately using this policy configuration."
/>
</p>
</EuiConfirmModal>
</EuiOverlayMask>
);
};

return (
<Fragment>
{children(executePolicyPrompt)}
{renderModal()}
</Fragment>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export const RepositoryDeleteProvider: React.FunctionComponent<Props> = ({ child

const deleteRepository = () => {
const repositoriesToDelete = [...repositoryNames];
deleteRepositories(repositoriesToDelete).then(({ data: { itemsDeleted, errors }, error }) => {
deleteRepositories(repositoriesToDelete).then(({ data, error }) => {
const { itemsDeleted, errors } = data || { itemsDeleted: undefined, errors: undefined };

// Surface success notifications
if (itemsDeleted && itemsDeleted.length) {
const hasMultipleSuccesses = itemsDeleted.length > 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ export const SnapshotDeleteProvider: React.FunctionComponent<Props> = ({ childre
const deleteSnapshot = () => {
const snapshotsToDelete = [...snapshotIds];
setIsDeleting(true);
deleteSnapshots(snapshotsToDelete).then(({ data: { itemsDeleted, errors }, error }) => {
deleteSnapshots(snapshotsToDelete).then(({ data, error }) => {
const { itemsDeleted, errors } = data || { itemsDeleted: undefined, errors: undefined };

// Wait until request is done to close modal; deleting snapshots take longer due to their sequential nature
closeModal();
setIsDeleting(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,6 @@ export const UIM_POLICY_LIST_LOAD = 'policy_list_load';
export const UIM_POLICY_SHOW_DETAILS_CLICK = 'policy_show_details_click';
export const UIM_POLICY_DETAIL_PANEL_SUMMARY_TAB = 'policy_detail_panel_summary_tab';
export const UIM_POLICY_DETAIL_PANEL_HISTORY_TAB = 'policy_detail_panel_last_success_tab';
export const UIM_POLICY_EXECUTE = 'policy_execute';
export const UIM_POLICY_DELETE = 'policy_delete';
export const UIM_POLICY_DELETE_MANY = 'policy_delete_many';
Loading

0 comments on commit 8622e7c

Please sign in to comment.