mirror of
https://github.com/ansible/awx.git
synced 2026-01-16 04:10:44 -03:30
Adds delete warnings on the lists and detail pages that have resources that are used by other resources
This commit is contained in:
parent
14f42af700
commit
7e7bb5261b
@ -78,6 +78,12 @@ class Inventories extends InstanceGroupsMixin(Base) {
|
||||
});
|
||||
}
|
||||
|
||||
updateSources(inventoryId) {
|
||||
return this.http.get(
|
||||
`${this.baseUrl}${inventoryId}/update_inventory_sources/`
|
||||
);
|
||||
}
|
||||
|
||||
async readSourceDetail(inventoryId, sourceId) {
|
||||
const {
|
||||
data: { results },
|
||||
|
||||
@ -77,7 +77,7 @@ function AlertModal({
|
||||
aria-label={label || i18n._(t`Alert modal`)}
|
||||
aria-labelledby="alert-modal-header-label"
|
||||
isOpen={Boolean(isOpen)}
|
||||
variant="small"
|
||||
variant="medium"
|
||||
title={title}
|
||||
{...props}
|
||||
>
|
||||
|
||||
@ -2,9 +2,20 @@ import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button } from '@patternfly/react-core';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Badge, Alert } from '@patternfly/react-core';
|
||||
import AlertModal from '../AlertModal';
|
||||
import { getRelatedResourceDeleteCounts } from '../../util/getRelatedResourceDeleteDetails';
|
||||
import ErrorDetail from '../ErrorDetail';
|
||||
|
||||
const WarningMessage = styled(Alert)`
|
||||
margin-top: 10px;
|
||||
`;
|
||||
const DetailsWrapper = styled.span`
|
||||
:not(:first-of-type) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
function DeleteButton({
|
||||
onConfirm,
|
||||
modalTitle,
|
||||
@ -14,51 +25,101 @@ function DeleteButton({
|
||||
children,
|
||||
isDisabled,
|
||||
ouiaId,
|
||||
deleteMessage,
|
||||
deleteDetailsRequests,
|
||||
}) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [deleteMessageError, setDeleteMessageError] = useState();
|
||||
const [deleteDetails, setDeleteDetails] = useState({});
|
||||
|
||||
const toggleModal = async isModalOpen => {
|
||||
if (deleteDetailsRequests?.length && isModalOpen) {
|
||||
const { results, error } = await getRelatedResourceDeleteCounts(
|
||||
deleteDetailsRequests
|
||||
);
|
||||
if (error) {
|
||||
setDeleteMessageError(error);
|
||||
} else {
|
||||
setDeleteDetails(results);
|
||||
}
|
||||
}
|
||||
setIsOpen(isModalOpen);
|
||||
};
|
||||
|
||||
if (deleteMessageError) {
|
||||
return (
|
||||
<AlertModal
|
||||
isOpen={deleteMessageError}
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={() => {
|
||||
toggleModal(false);
|
||||
setDeleteMessageError();
|
||||
}}
|
||||
>
|
||||
<ErrorDetail error={deleteMessageError} />
|
||||
</AlertModal>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
variant={variant || 'secondary'}
|
||||
aria-label={i18n._(t`Delete`)}
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => setIsOpen(true)}
|
||||
ouiaId={ouiaId}
|
||||
onClick={() => toggleModal(true)}
|
||||
>
|
||||
{children || i18n._(t`Delete`)}
|
||||
</Button>
|
||||
<AlertModal
|
||||
isOpen={isOpen}
|
||||
title={modalTitle}
|
||||
variant="danger"
|
||||
onClose={() => setIsOpen(false)}
|
||||
actions={[
|
||||
<Button
|
||||
ouiaId="delete-modal-confirm"
|
||||
key="delete"
|
||||
variant="danger"
|
||||
aria-label={i18n._(t`Delete`)}
|
||||
isDisabled={isDisabled}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>,
|
||||
<Button
|
||||
ouiaId="delete-modal-cancel"
|
||||
key="cancel"
|
||||
variant="link"
|
||||
aria-label={i18n._(t`Cancel`)}
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{i18n._(t`Are you sure you want to delete:`)}
|
||||
<br />
|
||||
<strong>{name}</strong>
|
||||
</AlertModal>
|
||||
{!deleteMessageError && (
|
||||
<AlertModal
|
||||
isOpen={isOpen}
|
||||
title={modalTitle}
|
||||
variant="danger"
|
||||
onClose={() => toggleModal(false)}
|
||||
actions={[
|
||||
<Button
|
||||
ouiaId="delete-modal-confirm"
|
||||
key="delete"
|
||||
variant="danger"
|
||||
aria-label={i18n._(t`Confirm Delete`)}
|
||||
isDisabled={isDisabled}
|
||||
onClick={onConfirm}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>,
|
||||
<Button
|
||||
ouiaId="delete-modal-cancel"
|
||||
key="cancel"
|
||||
variant="link"
|
||||
aria-label={i18n._(t`Cancel`)}
|
||||
onClick={() => toggleModal(false)}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>,
|
||||
]}
|
||||
>
|
||||
{i18n._(t`Are you sure you want to delete:`)}
|
||||
<br />
|
||||
<strong>{name}</strong>
|
||||
{Object.values(deleteDetails).length > 0 && (
|
||||
<WarningMessage
|
||||
variant="warning"
|
||||
isInline
|
||||
title={
|
||||
<div>
|
||||
<div aria-label={deleteMessage}>{deleteMessage}</div>
|
||||
<br />
|
||||
{Object.entries(deleteDetails).map(([key, value]) => (
|
||||
<DetailsWrapper aria-label={`${key}: ${value}`} key={key}>
|
||||
<span>{key}</span> <Badge>{value}</Badge>
|
||||
</DetailsWrapper>
|
||||
))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</AlertModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
113
awx/ui_next/src/components/DeleteButton/DeleteButton.test.jsx
Normal file
113
awx/ui_next/src/components/DeleteButton/DeleteButton.test.jsx
Normal file
@ -0,0 +1,113 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../testUtils/enzymeHelpers';
|
||||
import { CredentialsAPI } from '../../api';
|
||||
import DeleteButton from './DeleteButton';
|
||||
|
||||
jest.mock('../../api');
|
||||
|
||||
describe('<DeleteButton />', () => {
|
||||
test('should render button', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<DeleteButton onConfirm={() => {}} name="Foo" />
|
||||
);
|
||||
expect(wrapper.find('button')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should open confirmation modal', async () => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<DeleteButton
|
||||
onConfirm={() => {}}
|
||||
name="Foo"
|
||||
deleteDetailsRequests={[
|
||||
{
|
||||
label: 'job',
|
||||
request: CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 1 },
|
||||
}),
|
||||
},
|
||||
]}
|
||||
deleteMessage="Delete this?"
|
||||
warningMessage="Are you sure to want to delete this"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
// expect(wrapper.find('Modal')).toHaveLength(0);
|
||||
await act(async () => {
|
||||
wrapper.find('button').prop('onClick')();
|
||||
});
|
||||
|
||||
await waitForElement(wrapper, 'Modal', el => el.length > 0);
|
||||
expect(wrapper.find('Modal')).toHaveLength(1);
|
||||
|
||||
expect(wrapper.find('div[aria-label="Delete this?"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should invoke onConfirm prop', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
const wrapper = mountWithContexts(
|
||||
<DeleteButton
|
||||
onConfirm={onConfirm}
|
||||
itemsToDelete="foo"
|
||||
deleteDetailsRequests={[
|
||||
{
|
||||
label: 'job',
|
||||
request: CredentialsAPI.read.mockResolvedValue({
|
||||
data: { count: 1 },
|
||||
}),
|
||||
},
|
||||
]}
|
||||
deleteMessage="Delete this?"
|
||||
/>
|
||||
);
|
||||
await act(async () => wrapper.find('button').simulate('click'));
|
||||
wrapper.update();
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('ModalBoxFooter button[aria-label="Confirm Delete"]')
|
||||
.simulate('click')
|
||||
);
|
||||
wrapper.update();
|
||||
expect(onConfirm).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should show delete details error', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<DeleteButton
|
||||
onConfirm={onConfirm}
|
||||
itemsToDelete="foo"
|
||||
deleteDetailsRequests={[
|
||||
{
|
||||
label: 'job',
|
||||
request: CredentialsAPI.read.mockRejectedValue(
|
||||
new Error({
|
||||
response: {
|
||||
config: {
|
||||
method: 'get',
|
||||
url: '/api/v2/credentals',
|
||||
},
|
||||
data: 'An error occurred',
|
||||
status: 403,
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
await act(async () => wrapper.find('button').simulate('click'));
|
||||
wrapper.update();
|
||||
|
||||
expect(wrapper.find('AlertModal[title="Error!"]')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -10,15 +10,29 @@ import {
|
||||
checkPropTypes,
|
||||
} from 'prop-types';
|
||||
import styled from 'styled-components';
|
||||
import { Alert, Button, DropdownItem, Tooltip } from '@patternfly/react-core';
|
||||
import {
|
||||
Alert,
|
||||
Badge,
|
||||
Button,
|
||||
DropdownItem,
|
||||
Tooltip,
|
||||
} from '@patternfly/react-core';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import AlertModal from '../AlertModal';
|
||||
import { KebabifiedContext } from '../../contexts/Kebabified';
|
||||
import { getRelatedResourceDeleteCounts } from '../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
import ErrorDetail from '../ErrorDetail';
|
||||
|
||||
const WarningMessage = styled(Alert)`
|
||||
margin-top: 10px;
|
||||
`;
|
||||
const DetailsWrapper = styled.span`
|
||||
:not(:first-of-type) {
|
||||
padding-left: 10px;
|
||||
}
|
||||
`;
|
||||
|
||||
const requiredField = props => {
|
||||
const { name, username, image } = props;
|
||||
@ -77,20 +91,40 @@ function ToolbarDeleteButton({
|
||||
pluralizedItemName,
|
||||
errorMessage,
|
||||
onDelete,
|
||||
deleteDetailsRequests,
|
||||
warningMessage,
|
||||
deleteMessage,
|
||||
i18n,
|
||||
cannotDelete,
|
||||
}) {
|
||||
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [deleteDetails, setDeleteDetails] = useState({});
|
||||
|
||||
const deleteMessages = [warningMessage, deleteMessage].filter(
|
||||
message => message
|
||||
);
|
||||
|
||||
const [deleteMessageError, setDeleteMessageError] = useState();
|
||||
const handleDelete = () => {
|
||||
onDelete();
|
||||
toggleModal();
|
||||
};
|
||||
|
||||
const toggleModal = () => {
|
||||
setIsModalOpen(!isModalOpen);
|
||||
const toggleModal = async isOpen => {
|
||||
if (itemsToDelete.length === 1 && deleteDetailsRequests?.length > 0) {
|
||||
const { results, error } = await getRelatedResourceDeleteCounts(
|
||||
deleteDetailsRequests
|
||||
);
|
||||
|
||||
if (error) {
|
||||
setDeleteMessageError(error);
|
||||
} else {
|
||||
setDeleteDetails(results);
|
||||
}
|
||||
}
|
||||
|
||||
setIsModalOpen(isOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@ -126,40 +160,55 @@ function ToolbarDeleteButton({
|
||||
const isDisabled =
|
||||
itemsToDelete.length === 0 || itemsToDelete.some(cannotDelete);
|
||||
|
||||
// NOTE: Once PF supports tooltips on disabled elements,
|
||||
// we can delete the extra <div> around the <DeleteButton> below.
|
||||
// See: https://github.com/patternfly/patternfly-react/issues/1894
|
||||
if (deleteMessageError) {
|
||||
return (
|
||||
<AlertModal
|
||||
isOpen={deleteMessageError}
|
||||
title={i18n._(t`Error!`)}
|
||||
onClose={() => {
|
||||
toggleModal(false);
|
||||
setDeleteMessageError();
|
||||
}}
|
||||
>
|
||||
<ErrorDetail error={deleteMessageError} />
|
||||
</AlertModal>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isKebabified ? (
|
||||
<DropdownItem
|
||||
key="add"
|
||||
isDisabled={isDisabled}
|
||||
component="button"
|
||||
onClick={toggleModal}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DropdownItem>
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<DropdownItem
|
||||
key="add"
|
||||
isDisabled={isDisabled}
|
||||
component="button"
|
||||
onClick={() => toggleModal(true)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DropdownItem>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Tooltip content={renderTooltip()} position="top">
|
||||
<div>
|
||||
<Button
|
||||
variant="secondary"
|
||||
aria-label={i18n._(t`Delete`)}
|
||||
onClick={toggleModal}
|
||||
isDisabled={isDisabled}
|
||||
onClick={() => toggleModal(true)}
|
||||
isAriaDisabled={isDisabled}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</Button>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isModalOpen && (
|
||||
|
||||
{isModalOpen && !deleteMessageError && (
|
||||
<AlertModal
|
||||
variant="danger"
|
||||
title={modalTitle}
|
||||
isOpen={isModalOpen}
|
||||
onClose={toggleModal}
|
||||
onClose={() => toggleModal(false)}
|
||||
actions={[
|
||||
<Button
|
||||
key="delete"
|
||||
@ -173,7 +222,7 @@ function ToolbarDeleteButton({
|
||||
key="cancel"
|
||||
variant="link"
|
||||
aria-label={i18n._(t`cancel delete`)}
|
||||
onClick={toggleModal}
|
||||
onClick={() => toggleModal(false)}
|
||||
>
|
||||
{i18n._(t`Cancel`)}
|
||||
</Button>,
|
||||
@ -186,8 +235,43 @@ function ToolbarDeleteButton({
|
||||
<br />
|
||||
</span>
|
||||
))}
|
||||
{warningMessage && (
|
||||
<WarningMessage variant="warning" isInline title={warningMessage} />
|
||||
{itemsToDelete.length === 1 &&
|
||||
Object.values(deleteDetails).length > 0 && (
|
||||
<WarningMessage
|
||||
variant="warning"
|
||||
isInline
|
||||
title={
|
||||
<div>
|
||||
{deleteMessages.map(message => (
|
||||
<div aria-label={message} key={message}>
|
||||
{message}
|
||||
</div>
|
||||
))}
|
||||
{itemsToDelete.length === 1 && (
|
||||
<>
|
||||
<br />
|
||||
{Object.entries(deleteDetails).map(([key, value]) => (
|
||||
<DetailsWrapper
|
||||
key={key}
|
||||
aria-label={`${key}: ${value}`}
|
||||
>
|
||||
<span>{key}</span> <Badge>{value}</Badge>
|
||||
</DetailsWrapper>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{itemsToDelete.length > 1 && (
|
||||
<WarningMessage
|
||||
variant="warning"
|
||||
isInline
|
||||
title={deleteMessages.map(message => (
|
||||
<div>{message}</div>
|
||||
))}
|
||||
/>
|
||||
)}
|
||||
</AlertModal>
|
||||
)}
|
||||
|
||||
@ -1,7 +1,14 @@
|
||||
import React from 'react';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import {
|
||||
mountWithContexts,
|
||||
waitForElement,
|
||||
} from '../../../testUtils/enzymeHelpers';
|
||||
import { CredentialsAPI } from '../../api';
|
||||
import ToolbarDeleteButton from './ToolbarDeleteButton';
|
||||
|
||||
jest.mock('../../api');
|
||||
|
||||
const itemA = {
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
@ -19,6 +26,15 @@ const itemC = {
|
||||
};
|
||||
|
||||
describe('<ToolbarDeleteButton />', () => {
|
||||
let deleteDetailsRequests;
|
||||
beforeEach(() => {
|
||||
deleteDetailsRequests = [
|
||||
{
|
||||
label: 'job',
|
||||
request: CredentialsAPI.read.mockResolvedValue({ data: { count: 1 } }),
|
||||
},
|
||||
];
|
||||
});
|
||||
test('should render button', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ToolbarDeleteButton onDelete={() => {}} itemsToDelete={[]} />
|
||||
@ -27,14 +43,27 @@ describe('<ToolbarDeleteButton />', () => {
|
||||
expect(wrapper.find('ToolbarDeleteButton')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should open confirmation modal', () => {
|
||||
const wrapper = mountWithContexts(
|
||||
<ToolbarDeleteButton onDelete={() => {}} itemsToDelete={[itemA]} />
|
||||
);
|
||||
test('should open confirmation modal', async () => {
|
||||
let wrapper;
|
||||
await act(async () => {
|
||||
wrapper = mountWithContexts(
|
||||
<ToolbarDeleteButton
|
||||
onDelete={() => {}}
|
||||
itemsToDelete={[itemA]}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage="Delete this?"
|
||||
warningMessage="Are you sure to want to delete this"
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(wrapper.find('Modal')).toHaveLength(0);
|
||||
wrapper.find('button').simulate('click');
|
||||
wrapper.update();
|
||||
await act(async () => {
|
||||
wrapper.find('button').prop('onClick')();
|
||||
});
|
||||
await waitForElement(wrapper, 'Modal', el => el.length > 0);
|
||||
expect(wrapper.find('Modal')).toHaveLength(1);
|
||||
expect(wrapper.find('div[aria-label="Delete this?"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
test('should invoke onDelete prop', () => {
|
||||
|
||||
@ -74,7 +74,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
|
||||
>
|
||||
<Button
|
||||
aria-label="Delete"
|
||||
isDisabled={true}
|
||||
isAriaDisabled={true}
|
||||
onClick={[Function]}
|
||||
variant="secondary"
|
||||
>
|
||||
@ -92,19 +92,20 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
|
||||
>
|
||||
<Button
|
||||
aria-label="Delete"
|
||||
isDisabled={true}
|
||||
isAriaDisabled={true}
|
||||
onClick={[Function]}
|
||||
variant="secondary"
|
||||
>
|
||||
<button
|
||||
aria-disabled={true}
|
||||
aria-label="Delete"
|
||||
className="pf-c-button pf-m-secondary pf-m-disabled"
|
||||
className="pf-c-button pf-m-secondary pf-m-aria-disabled"
|
||||
data-ouia-component-id="OUIA-Generated-Button-secondary-1"
|
||||
data-ouia-component-type="PF4/Button"
|
||||
data-ouia-safe={true}
|
||||
disabled={true}
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
onKeyPress={[Function]}
|
||||
role={null}
|
||||
tabIndex={null}
|
||||
type="button"
|
||||
|
||||
@ -18,6 +18,7 @@ import { getQSConfig, parseQueryString } from '../../util/qs';
|
||||
import useWsTemplates from '../../util/useWsTemplates';
|
||||
import AddDropDownButton from '../AddDropDownButton';
|
||||
import TemplateListItem from './TemplateListItem';
|
||||
import { deleteRequests } from '../../util/getDeleteDetails';
|
||||
|
||||
function TemplateList({ defaultParams, i18n }) {
|
||||
// The type value in const qsConfig below does not have a space between job_template and
|
||||
@ -168,6 +169,8 @@ function TemplateList({ defaultParams, i18n }) {
|
||||
<AddDropDownButton key="add" dropdownItems={addDropDownButton} />
|
||||
);
|
||||
|
||||
const deleteDetailsRequests = deleteRequests.template(selected[0], i18n);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Card>
|
||||
@ -236,6 +239,11 @@ function TemplateList({ defaultParams, i18n }) {
|
||||
onDelete={handleTemplateDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Templates`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This template is currently being used by some workflow nodes. Are you sure you want to delete it?} other {Deleting these templates could impact some workflow nodes that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -22,6 +22,7 @@ import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import { CredentialsAPI, CredentialTypesAPI } from '../../../api';
|
||||
import { Credential } from '../../../types';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
const PluginInputMetadata = styled(CodeEditor)`
|
||||
grid-column: 1 / -1;
|
||||
@ -183,6 +184,11 @@ function CredentialDetail({ i18n, credential }) {
|
||||
fetchDetails();
|
||||
}, [fetchDetails]);
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.credential(
|
||||
credential,
|
||||
i18n
|
||||
);
|
||||
|
||||
if (hasContentLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@ -270,9 +276,14 @@ function CredentialDetail({ i18n, credential }) {
|
||||
{user_capabilities.delete && (
|
||||
<DeleteButton
|
||||
name={name}
|
||||
itemToDelete={credential}
|
||||
modalTitle={i18n._(t`Delete Credential`)}
|
||||
onConfirm={deleteCredential}
|
||||
isLoading={isLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This credential is currently being used by other resources. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { withI18n } from '@lingui/react';
|
||||
import { t } from '@lingui/macro';
|
||||
import { Card, PageSection } from '@patternfly/react-core';
|
||||
import { CredentialsAPI } from '../../../api';
|
||||
import useSelected from '../../../util/useSelected';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import DataListToolbar from '../../../components/DataListToolbar';
|
||||
@ -18,6 +19,7 @@ import PaginatedTable, {
|
||||
import useRequest, { useDeleteItems } from '../../../util/useRequest';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import CredentialListItem from './CredentialListItem';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
const QS_CONFIG = getQSConfig('credential', {
|
||||
page: 1,
|
||||
@ -26,9 +28,7 @@ const QS_CONFIG = getQSConfig('credential', {
|
||||
});
|
||||
|
||||
function CredentialList({ i18n }) {
|
||||
const [selected, setSelected] = useState([]);
|
||||
const location = useLocation();
|
||||
|
||||
const {
|
||||
result: {
|
||||
credentials,
|
||||
@ -77,8 +77,10 @@ function CredentialList({ i18n }) {
|
||||
fetchCredentials();
|
||||
}, [fetchCredentials]);
|
||||
|
||||
const isAllSelected =
|
||||
selected.length > 0 && selected.length === credentials.length;
|
||||
const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
|
||||
credentials
|
||||
);
|
||||
|
||||
const {
|
||||
isLoading: isDeleteLoading,
|
||||
deleteItems: deleteCredentials,
|
||||
@ -100,21 +102,12 @@ function CredentialList({ i18n }) {
|
||||
setSelected([]);
|
||||
};
|
||||
|
||||
const handleSelectAll = isSelected => {
|
||||
setSelected(isSelected ? [...credentials] : []);
|
||||
};
|
||||
|
||||
const handleSelect = row => {
|
||||
if (selected.some(s => s.id === row.id)) {
|
||||
setSelected(selected.filter(s => s.id !== row.id));
|
||||
} else {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
|
||||
const canAdd =
|
||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.credential(
|
||||
selected[0],
|
||||
i18n
|
||||
);
|
||||
return (
|
||||
<PageSection>
|
||||
<Card>
|
||||
@ -169,7 +162,9 @@ function CredentialList({ i18n }) {
|
||||
{...props}
|
||||
showSelectAll
|
||||
isAllSelected={isAllSelected}
|
||||
onSelectAll={handleSelectAll}
|
||||
onSelectAll={isSelected =>
|
||||
setSelected(isSelected ? [...credentials] : [])
|
||||
}
|
||||
qsConfig={QS_CONFIG}
|
||||
additionalControls={[
|
||||
...(canAdd
|
||||
@ -180,6 +175,11 @@ function CredentialList({ i18n }) {
|
||||
onDelete={handleDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Credentials`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This credential is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these credentials could impact other resources that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { CredentialTypesAPI } from '../../../api';
|
||||
import { jsonToYaml } from '../../../util/yaml';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
function CredentialTypeDetails({ credentialType, i18n }) {
|
||||
const { id, name, description, injectors, inputs } = credentialType;
|
||||
@ -32,6 +33,11 @@ function CredentialTypeDetails({ credentialType, i18n }) {
|
||||
}, [id, history])
|
||||
);
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.credentialType(
|
||||
credentialType,
|
||||
i18n
|
||||
);
|
||||
|
||||
const { error, dismissError } = useDismissableError(deleteError);
|
||||
|
||||
return (
|
||||
@ -83,6 +89,10 @@ function CredentialTypeDetails({ credentialType, i18n }) {
|
||||
modalTitle={i18n._(t`Delete credential type`)}
|
||||
onConfirm={deleteCredentialType}
|
||||
isDisabled={isLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This credential type is currently being used by some credentials. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -19,7 +19,7 @@ import PaginatedTable, {
|
||||
import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import AlertModal from '../../../components/AlertModal';
|
||||
import DatalistToolbar from '../../../components/DataListToolbar';
|
||||
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
import CredentialTypeListItem from './CredentialTypeListItem';
|
||||
|
||||
const QS_CONFIG = getQSConfig('credential-type', {
|
||||
@ -106,6 +106,11 @@ function CredentialTypeList({ i18n }) {
|
||||
|
||||
const canAdd = actions && actions.POST;
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.credentialType(
|
||||
selected[0],
|
||||
i18n
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageSection>
|
||||
@ -162,6 +167,11 @@ function CredentialTypeList({ i18n }) {
|
||||
onDelete={handleDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Credential Types`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This credential type is currently being used by some credentials. Are you sure you want to delete it?} other {Deleting these credential types could impact other credentials that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -19,6 +19,7 @@ import ChipGroup from '../../../components/ChipGroup';
|
||||
import { InventoriesAPI } from '../../../api';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { Inventory } from '../../../types';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
function InventoryDetail({ inventory, i18n }) {
|
||||
const history = useHistory();
|
||||
@ -54,6 +55,11 @@ function InventoryDetail({ inventory, i18n }) {
|
||||
user_capabilities: userCapabilities,
|
||||
} = inventory.summary_fields;
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventory(
|
||||
inventory,
|
||||
i18n
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@ -126,6 +132,10 @@ function InventoryDetail({ inventory, i18n }) {
|
||||
name={inventory.name}
|
||||
modalTitle={i18n._(t`Delete Inventory`)}
|
||||
onConfirm={deleteInventory}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This inventory is currently being used by other resources. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -17,6 +17,7 @@ import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import useWsInventories from './useWsInventories';
|
||||
import AddDropDownButton from '../../../components/AddDropDownButton';
|
||||
import InventoryListItem from './InventoryListItem';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
const QS_CONFIG = getQSConfig('inventory', {
|
||||
page: 1,
|
||||
@ -126,6 +127,12 @@ function InventoryList({ i18n }) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventory(
|
||||
selected[0],
|
||||
i18n
|
||||
);
|
||||
|
||||
const addInventory = i18n._(t`Add inventory`);
|
||||
const addSmartInventory = i18n._(t`Add smart inventory`);
|
||||
const addButton = (
|
||||
@ -216,6 +223,11 @@ function InventoryList({ i18n }) {
|
||||
'{numItemsToDelete, plural, one {The inventory will be in a pending status until the final delete is processed.} other {The inventories will be in a pending status until the final delete is processed.}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This inventory is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these inventories could impact other resources that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -22,6 +22,7 @@ import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import Popover from '../../../components/Popover';
|
||||
import useRequest from '../../../util/useRequest';
|
||||
import { InventorySourcesAPI } from '../../../api';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
function InventorySourceDetail({ inventorySource, i18n }) {
|
||||
const {
|
||||
@ -96,6 +97,11 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
||||
inventorySource.inventory,
|
||||
i18n
|
||||
);
|
||||
|
||||
const VERBOSITY = {
|
||||
0: i18n._(t`0 (Warning)`),
|
||||
1: i18n._(t`1 (Info)`),
|
||||
@ -281,6 +287,10 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
||||
name={name}
|
||||
modalTitle={i18n._(t`Delete inventory source`)}
|
||||
onConfirm={handleDelete}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This inventory source is currently being used some workflow job template nodes. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -20,6 +20,7 @@ import AlertModal from '../../../components/AlertModal/AlertModal';
|
||||
import ErrorDetail from '../../../components/ErrorDetail/ErrorDetail';
|
||||
import InventorySourceListItem from './InventorySourceListItem';
|
||||
import useWsInventorySources from './useWsInventorySources';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
const QS_CONFIG = getQSConfig('inventory', {
|
||||
not__source: '',
|
||||
@ -142,6 +143,11 @@ function InventorySourceList({ i18n }) {
|
||||
sourceChoicesOptions &&
|
||||
Object.prototype.hasOwnProperty.call(sourceChoicesOptions, 'POST');
|
||||
const listUrl = `/inventories/${inventoryType}/${id}/sources/`;
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
||||
id,
|
||||
i18n
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<PaginatedDataList
|
||||
@ -174,6 +180,11 @@ function InventorySourceList({ i18n }) {
|
||||
onDelete={handleDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Inventory Sources`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This inventory source is currently being used workflow job template nodes. Are you sure you want to delete it?} other {Deleting these inventory sources could impact some workflow job template nodes that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
...(canSyncSources
|
||||
? [
|
||||
|
||||
@ -20,6 +20,7 @@ import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { useConfig } from '../../../contexts/Config';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
function OrganizationDetail({ i18n, organization }) {
|
||||
const {
|
||||
@ -71,6 +72,11 @@ function OrganizationDetail({ i18n, organization }) {
|
||||
|
||||
const { error, dismissError } = useDismissableError(deleteError);
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.organization(
|
||||
organization,
|
||||
i18n
|
||||
);
|
||||
|
||||
if (hasContentLoading) {
|
||||
return <ContentLoading />;
|
||||
}
|
||||
@ -157,6 +163,10 @@ function OrganizationDetail({ i18n, organization }) {
|
||||
modalTitle={i18n._(t`Delete Organization`)}
|
||||
onConfirm={deleteOrganization}
|
||||
isDisabled={isLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This organization is currently being used some credentials. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -19,6 +19,7 @@ import PaginatedTable, {
|
||||
} from '../../../components/PaginatedTable';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
import OrganizationListItem from './OrganizationListItem';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
const QS_CONFIG = getQSConfig('organization', {
|
||||
page: 1,
|
||||
@ -116,6 +117,10 @@ function OrganizationsList({ i18n }) {
|
||||
setSelected(selected.concat(row));
|
||||
}
|
||||
};
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.organization(
|
||||
selected[0],
|
||||
i18n
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -173,6 +178,11 @@ function OrganizationsList({ i18n }) {
|
||||
onDelete={handleOrgDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Organizations`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This organization is currently being used some credentials. Are you sure you want to delete it?} other {Deleting these organizations could impact some credentials that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -20,6 +20,7 @@ import CredentialChip from '../../../components/CredentialChip';
|
||||
import { ProjectsAPI } from '../../../api';
|
||||
import { toTitleCase } from '../../../util/strings';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
import ProjectSyncButton from '../shared/ProjectSyncButton';
|
||||
|
||||
function ProjectDetail({ project, i18n }) {
|
||||
@ -52,7 +53,10 @@ function ProjectDetail({ project, i18n }) {
|
||||
);
|
||||
|
||||
const { error, dismissError } = useDismissableError(deleteError);
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.project(
|
||||
project,
|
||||
i18n
|
||||
);
|
||||
let optionsList = '';
|
||||
if (
|
||||
scm_clean ||
|
||||
@ -171,6 +175,10 @@ function ProjectDetail({ project, i18n }) {
|
||||
modalTitle={i18n._(t`Delete Project`)}
|
||||
onConfirm={deleteProject}
|
||||
isDisabled={isLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This project is currently being used by other resources. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -18,6 +18,7 @@ import PaginatedTable, {
|
||||
HeaderCell,
|
||||
} from '../../../components/PaginatedTable';
|
||||
import useWsProjects from './useWsProjects';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
import { getQSConfig, parseQueryString } from '../../../util/qs';
|
||||
|
||||
import ProjectListItem from './ProjectListItem';
|
||||
@ -116,6 +117,11 @@ function ProjectList({ i18n }) {
|
||||
}
|
||||
};
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.project(
|
||||
selected[0],
|
||||
i18n
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageSection>
|
||||
@ -194,6 +200,11 @@ function ProjectList({ i18n }) {
|
||||
onDelete={handleProjectDelete}
|
||||
itemsToDelete={selected}
|
||||
pluralizedItemName={i18n._(t`Projects`)}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
'{numItemsToDelete, plural, one {This project is currently being used by other resources. Are you sure you want to delete it?} other {Deleting these projects could impact other resources that rely on them. Are you sure you want to delete anyway?}}',
|
||||
{ numItemsToDelete: selected.length }
|
||||
)}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
|
||||
@ -31,6 +31,7 @@ import { VariablesDetail } from '../../../components/CodeEditor';
|
||||
import { JobTemplatesAPI } from '../../../api';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
|
||||
function JobTemplateDetail({ i18n, template }) {
|
||||
const {
|
||||
@ -96,6 +97,10 @@ function JobTemplateDetail({ i18n, template }) {
|
||||
|
||||
const { error, dismissError } = useDismissableError(deleteError);
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.template(
|
||||
template,
|
||||
i18n
|
||||
);
|
||||
const canLaunch =
|
||||
summary_fields.user_capabilities && summary_fields.user_capabilities.start;
|
||||
const verbosityOptions = [
|
||||
@ -401,6 +406,10 @@ function JobTemplateDetail({ i18n, template }) {
|
||||
modalTitle={i18n._(t`Delete Job Template`)}
|
||||
onConfirm={deleteJobTemplate}
|
||||
isDisabled={isDeleteLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This job template is currently being used by other resources. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
@ -27,6 +27,7 @@ import ErrorDetail from '../../../components/ErrorDetail';
|
||||
import { LaunchButton } from '../../../components/LaunchButton';
|
||||
import Sparkline from '../../../components/Sparkline';
|
||||
import { toTitleCase } from '../../../util/strings';
|
||||
import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails';
|
||||
import useRequest, { useDismissableError } from '../../../util/useRequest';
|
||||
|
||||
function WorkflowJobTemplateDetail({ template, i18n }) {
|
||||
@ -102,6 +103,11 @@ function WorkflowJobTemplateDetail({ template, i18n }) {
|
||||
type: 'workflow_job',
|
||||
}));
|
||||
|
||||
const deleteDetailsRequests = relatedResourceDeleteRequests.template(
|
||||
template,
|
||||
i18n
|
||||
);
|
||||
|
||||
return (
|
||||
<CardBody>
|
||||
<DetailList gutter="sm">
|
||||
@ -241,6 +247,10 @@ function WorkflowJobTemplateDetail({ template, i18n }) {
|
||||
modalTitle={i18n._(t`Delete Workflow Job Template`)}
|
||||
onConfirm={deleteWorkflowJobTemplate}
|
||||
isDisabled={isLoading}
|
||||
deleteDetailsRequests={deleteDetailsRequests}
|
||||
deleteMessage={i18n._(
|
||||
t`This workflow job template is currently being used by other resources. Are you sure you want to delete it?`
|
||||
)}
|
||||
>
|
||||
{i18n._(t`Delete`)}
|
||||
</DeleteButton>
|
||||
|
||||
128
awx/ui_next/src/util/getDeleteDetails.js
Normal file
128
awx/ui_next/src/util/getDeleteDetails.js
Normal file
@ -0,0 +1,128 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import {
|
||||
CredentialsAPI,
|
||||
InventoriesAPI,
|
||||
InventorySourcesAPI,
|
||||
JobTemplatesAPI,
|
||||
ProjectsAPI,
|
||||
WorkflowJobTemplateNodesAPI,
|
||||
WorkflowJobTemplatesAPI,
|
||||
} from '../api';
|
||||
|
||||
export default async function getDeleteDetails(requests) {
|
||||
const results = {};
|
||||
let error = null;
|
||||
let hasCount = false;
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
requests.map(async ({ request, label }) => {
|
||||
const {
|
||||
data: { count },
|
||||
} = await request();
|
||||
if (count > 0) {
|
||||
results[label] = count;
|
||||
hasCount = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
return {
|
||||
results: hasCount && results,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export const deleteRequests = {
|
||||
credential: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
JobTemplatesAPI.read({
|
||||
credentials: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () => ProjectsAPI.read({ credentials: selected.id }),
|
||||
label: i18n._(t`Projects`),
|
||||
},
|
||||
{
|
||||
request: () =>
|
||||
InventoriesAPI.read({
|
||||
insights_credential: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Inventories`),
|
||||
},
|
||||
{
|
||||
request: () =>
|
||||
InventorySourcesAPI.read({
|
||||
credentials__id: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Inventory Sources`),
|
||||
},
|
||||
],
|
||||
|
||||
template: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
WorkflowJobTemplateNodesAPI.read({
|
||||
unified_job_template: selected.id,
|
||||
}),
|
||||
label: [i18n._(t`Workflow Job Template Node`)],
|
||||
},
|
||||
],
|
||||
|
||||
credentialType: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
CredentialsAPI.read({
|
||||
credential_type__id: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Credentials`),
|
||||
},
|
||||
],
|
||||
|
||||
inventory: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
JobTemplatesAPI.read({
|
||||
inventory: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () => WorkflowJobTemplatesAPI.read({ inventory: selected.id }),
|
||||
label: i18n._(t`Workflow Job Template`),
|
||||
},
|
||||
],
|
||||
|
||||
inventorySource: (inventoryId, i18n) => [
|
||||
{
|
||||
request: async () => {
|
||||
try {
|
||||
const { data } = await InventoriesAPI.updateSources(inventoryId);
|
||||
return WorkflowJobTemplateNodesAPI.read({
|
||||
unified_job_template: data[0].inventory_source,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
},
|
||||
label: i18n._(t`Workflow Job Template Node`),
|
||||
},
|
||||
],
|
||||
|
||||
organization: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
CredentialsAPI.read({
|
||||
organization: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Credential`),
|
||||
},
|
||||
],
|
||||
};
|
||||
149
awx/ui_next/src/util/getRelatedResourceDeleteDetails.js
Normal file
149
awx/ui_next/src/util/getRelatedResourceDeleteDetails.js
Normal file
@ -0,0 +1,149 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import {
|
||||
CredentialsAPI,
|
||||
InventoriesAPI,
|
||||
InventorySourcesAPI,
|
||||
JobTemplatesAPI,
|
||||
ProjectsAPI,
|
||||
WorkflowJobTemplateNodesAPI,
|
||||
WorkflowJobTemplatesAPI,
|
||||
} from '../api';
|
||||
|
||||
export async function getRelatedResourceDeleteCounts(requests) {
|
||||
const results = {};
|
||||
let error = null;
|
||||
let hasCount = false;
|
||||
|
||||
try {
|
||||
await Promise.all(
|
||||
requests.map(async ({ request, label }) => {
|
||||
const {
|
||||
data: { count },
|
||||
} = await request();
|
||||
if (count > 0) {
|
||||
results[label] = count;
|
||||
hasCount = true;
|
||||
}
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
return {
|
||||
results: hasCount && results,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export const relatedResourceDeleteRequests = {
|
||||
credential: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
JobTemplatesAPI.read({
|
||||
credentials: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () => ProjectsAPI.read({ credentials: selected.id }),
|
||||
label: i18n._(t`Projects`),
|
||||
},
|
||||
{
|
||||
request: () =>
|
||||
InventoriesAPI.read({
|
||||
insights_credential: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Inventories`),
|
||||
},
|
||||
{
|
||||
request: () =>
|
||||
InventorySourcesAPI.read({
|
||||
credentials__id: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Inventory Sources`),
|
||||
},
|
||||
],
|
||||
|
||||
project: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
JobTemplatesAPI.read({
|
||||
credentials: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () => WorkflowJobTemplatesAPI.read({ credentials: selected.id }),
|
||||
label: i18n._(t`Workflow Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () =>
|
||||
InventorySourcesAPI.read({
|
||||
credentials__id: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Inventory Sources`),
|
||||
},
|
||||
],
|
||||
|
||||
template: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
WorkflowJobTemplateNodesAPI.read({
|
||||
unified_job_template: selected.id,
|
||||
}),
|
||||
label: [i18n._(t`Workflow Job Template Node`)],
|
||||
},
|
||||
],
|
||||
|
||||
credentialType: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
CredentialsAPI.read({
|
||||
credential_type__id: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Credentials`),
|
||||
},
|
||||
],
|
||||
|
||||
inventory: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
JobTemplatesAPI.read({
|
||||
inventory: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Job Templates`),
|
||||
},
|
||||
{
|
||||
request: () => WorkflowJobTemplatesAPI.read({ inventory: selected.id }),
|
||||
label: i18n._(t`Workflow Job Template`),
|
||||
},
|
||||
],
|
||||
|
||||
inventorySource: (inventoryId, i18n) => [
|
||||
{
|
||||
request: async () => {
|
||||
try {
|
||||
const { data } = await InventoriesAPI.updateSources(inventoryId);
|
||||
return WorkflowJobTemplateNodesAPI.read({
|
||||
unified_job_template: data[0].inventory_source,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
},
|
||||
label: i18n._(t`Workflow Job Template Node`),
|
||||
},
|
||||
],
|
||||
|
||||
organization: (selected, i18n) => [
|
||||
{
|
||||
request: async () =>
|
||||
CredentialsAPI.read({
|
||||
organization: selected.id,
|
||||
}),
|
||||
label: i18n._(t`Credential`),
|
||||
},
|
||||
],
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user