mirror of
https://github.com/ansible/awx.git
synced 2026-03-07 11:41:08 -03:30
Apply radio selection to ALL selected groups in modal
* Use semantic html to describe modal list * Move nested try/catch block * Remove deprecated type fields * If delete fails, keep selected list checked
This commit is contained in:
@@ -131,46 +131,37 @@ function InventoryGroups({ i18n, location, match }) {
|
|||||||
return i18n._(t`Select a row to delete`);
|
return i18n._(t`Select a row to delete`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const promoteGroups = list => {
|
const handleDelete = async option => {
|
||||||
const promotePromises = Object.keys(list)
|
|
||||||
.filter(groupId => list[groupId] === 'promote')
|
|
||||||
.map(groupId => InventoriesAPI.promoteGroup(inventoryId, +groupId));
|
|
||||||
|
|
||||||
return Promise.all(promotePromises);
|
|
||||||
};
|
|
||||||
|
|
||||||
const deleteGroups = list => {
|
|
||||||
const deletePromises = Object.keys(list)
|
|
||||||
.filter(groupId => list[groupId] === 'delete')
|
|
||||||
.map(groupId => GroupsAPI.destroy(+groupId));
|
|
||||||
|
|
||||||
return Promise.all(deletePromises);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDelete = async list => {
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all([promoteGroups(list), deleteGroups(list)]);
|
/* eslint-disable no-await-in-loop, no-restricted-syntax */
|
||||||
|
/* Delete groups sequentially to avoid api integrity errors */
|
||||||
|
for (const group of selected) {
|
||||||
|
if (option === 'delete') {
|
||||||
|
await GroupsAPI.destroy(+group.id);
|
||||||
|
} else if (option === 'promote') {
|
||||||
|
await InventoriesAPI.promoteGroup(inventoryId, +group.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* eslint-enable no-await-in-loop, no-restricted-syntax */
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setDeletionError(error);
|
setDeletionError(error);
|
||||||
} finally {
|
|
||||||
toggleModal();
|
|
||||||
setSelected([]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
data: { count, results },
|
|
||||||
} = await fetchGroups(inventoryId, location.search);
|
|
||||||
|
|
||||||
setGroups(results);
|
|
||||||
setGroupCount(count);
|
|
||||||
} catch (error) {
|
|
||||||
setContentError(error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleModal();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: { count, results },
|
||||||
|
} = await fetchGroups(inventoryId, location.search);
|
||||||
|
setGroups(results);
|
||||||
|
setGroupCount(count);
|
||||||
|
} catch (error) {
|
||||||
|
setContentError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const canAdd =
|
const canAdd =
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { MemoryRouter, Route } from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
|
import { createMemoryHistory } from 'history';
|
||||||
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
|
||||||
import { InventoriesAPI, GroupsAPI } from '@api';
|
import { InventoriesAPI, GroupsAPI } from '@api';
|
||||||
import InventoryGroups from './InventoryGroups';
|
import InventoryGroups from './InventoryGroups';
|
||||||
@@ -67,15 +68,20 @@ describe('<InventoryGroups />', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const history = createMemoryHistory({
|
||||||
|
initialEntries: ['/inventories/inventory/3/groups'],
|
||||||
|
});
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<MemoryRouter initialEntries={['/inventories/inventory/3/groups']}>
|
<Route
|
||||||
<Route
|
path="/inventories/inventory/:id/groups"
|
||||||
path="/inventories/inventory/:id/groups"
|
component={() => <InventoryGroups />}
|
||||||
component={() => <InventoryGroups />}
|
/>,
|
||||||
/>
|
{
|
||||||
</MemoryRouter>
|
context: {
|
||||||
|
router: { history, route: { location: history.location } },
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
await waitForElement(wrapper, 'ContentLoading', el => el.length === 0);
|
||||||
@@ -194,6 +200,10 @@ describe('<InventoryGroups />', () => {
|
|||||||
'InventoryGroupsDeleteModal',
|
'InventoryGroupsDeleteModal',
|
||||||
el => el.props().isModalOpen === true
|
el => el.props().isModalOpen === true
|
||||||
);
|
);
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('Radio[id="radio-delete"]').invoke('onChange')();
|
||||||
|
});
|
||||||
|
wrapper.update();
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper
|
wrapper
|
||||||
.find('ModalBoxFooter Button[aria-label="Delete"]')
|
.find('ModalBoxFooter Button[aria-label="Delete"]')
|
||||||
|
|||||||
@@ -1,38 +1,18 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { func, bool, arrayOf, object } from 'prop-types';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Button, Radio } from '@patternfly/react-core';
|
import { Button, Radio } from '@patternfly/react-core';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const ListItem = styled.div`
|
const ListItem = styled.li`
|
||||||
padding: 24px 1px;
|
display: flex;
|
||||||
|
font-weight: 600;
|
||||||
dl {
|
color: var(--pf-global--danger-color--100);
|
||||||
display: flex;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
dt {
|
|
||||||
color: var(--pf-global--danger-color--100);
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.pf-c-radio {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ContentWrapper = styled.div`
|
|
||||||
${ListItem} + ${ListItem} {
|
|
||||||
border-top-width: 1px;
|
|
||||||
border-top-style: solid;
|
|
||||||
border-top-color: #d7d7d7;
|
|
||||||
}
|
|
||||||
${ListItem}:last-child {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const InventoryGroupsDeleteModal = ({
|
const InventoryGroupsDeleteModal = ({
|
||||||
onClose,
|
onClose,
|
||||||
onDelete,
|
onDelete,
|
||||||
@@ -40,65 +20,7 @@ const InventoryGroupsDeleteModal = ({
|
|||||||
groups,
|
groups,
|
||||||
i18n,
|
i18n,
|
||||||
}) => {
|
}) => {
|
||||||
const [deleteList, setDeleteList] = useState([]);
|
const [radioOption, setRadioOption] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const groupIds = groups.reduce((obj, group) => {
|
|
||||||
if (group.total_groups > 0 || group.total_hosts > 0) {
|
|
||||||
return { ...obj, [group.id]: null };
|
|
||||||
}
|
|
||||||
return { ...obj, [group.id]: 'delete' };
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
setDeleteList(groupIds);
|
|
||||||
}, [groups]);
|
|
||||||
|
|
||||||
const handleChange = (groupId, radioOption) => {
|
|
||||||
setDeleteList({ ...deleteList, [groupId]: radioOption });
|
|
||||||
};
|
|
||||||
|
|
||||||
const content = groups
|
|
||||||
.map(group => {
|
|
||||||
if (group.total_groups > 0 || group.total_hosts > 0) {
|
|
||||||
return (
|
|
||||||
<ListItem key={group.id}>
|
|
||||||
<dl>
|
|
||||||
<dt>{group.name}</dt>
|
|
||||||
<dd>
|
|
||||||
{i18n._(
|
|
||||||
t`(${group.total_groups} Groups and ${group.total_hosts} Hosts)`
|
|
||||||
)}
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<Radio
|
|
||||||
key="radio-delete"
|
|
||||||
label={i18n._(t`Delete All Groups and Hosts`)}
|
|
||||||
id={`radio-delete-${group.id}`}
|
|
||||||
name={`radio-${group.id}`}
|
|
||||||
onChange={() => handleChange(group.id, 'delete')}
|
|
||||||
/>
|
|
||||||
<Radio
|
|
||||||
key="radio-promote"
|
|
||||||
label={i18n._(t`Promote Child Groups and Hosts`)}
|
|
||||||
id={`radio-promote-${group.id}`}
|
|
||||||
name={`radio-${group.id}`}
|
|
||||||
onChange={() => handleChange(group.id, 'promote')}
|
|
||||||
/>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ListItem key={group.id}>
|
|
||||||
<dl>
|
|
||||||
<dt>{group.name}</dt>
|
|
||||||
<dd>{i18n._(t`(No Child Groups or Hosts)`)}</dd>
|
|
||||||
</dl>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.reduce((array, el) => {
|
|
||||||
return array.concat(el);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return ReactDOM.createPortal(
|
return ReactDOM.createPortal(
|
||||||
<AlertModal
|
<AlertModal
|
||||||
@@ -111,12 +33,10 @@ const InventoryGroupsDeleteModal = ({
|
|||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Button
|
||||||
aria-label={i18n._(t`Delete`)}
|
aria-label={i18n._(t`Delete`)}
|
||||||
onClick={() => onDelete(deleteList)}
|
onClick={() => onDelete(radioOption)}
|
||||||
variant="danger"
|
variant="danger"
|
||||||
key="delete"
|
key="delete"
|
||||||
isDisabled={Object.keys(deleteList).some(
|
isDisabled={radioOption === null}
|
||||||
group => deleteList[group] === null
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
{i18n._(t`Delete`)}
|
{i18n._(t`Delete`)}
|
||||||
</Button>,
|
</Button>,
|
||||||
@@ -135,10 +55,43 @@ const InventoryGroupsDeleteModal = ({
|
|||||||
groups.length > 1 ? i18n._(t`groups`) : i18n._(t`group`)
|
groups.length > 1 ? i18n._(t`groups`) : i18n._(t`group`)
|
||||||
} below?`
|
} below?`
|
||||||
)}
|
)}
|
||||||
<ContentWrapper>{content}</ContentWrapper>
|
<div css="padding: 24px 0;">
|
||||||
|
{groups.map(group => {
|
||||||
|
return <ListItem key={group.id}>{group.name}</ListItem>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div css="padding-left: 1px;">
|
||||||
|
<Radio
|
||||||
|
id="radio-delete"
|
||||||
|
key="radio-delete"
|
||||||
|
label={i18n._(t`Delete All Groups and Hosts`)}
|
||||||
|
name="option"
|
||||||
|
onChange={() => setRadioOption('delete')}
|
||||||
|
/>
|
||||||
|
<Radio
|
||||||
|
css="margin-top: 5px;"
|
||||||
|
id="radio-promote"
|
||||||
|
key="radio-promote"
|
||||||
|
label={i18n._(t`Promote Child Groups and Hosts`)}
|
||||||
|
name="option"
|
||||||
|
onChange={() => setRadioOption('promote')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</AlertModal>,
|
</AlertModal>,
|
||||||
document.body
|
document.body
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
InventoryGroupsDeleteModal.propTypes = {
|
||||||
|
onClose: func.isRequired,
|
||||||
|
onDelete: func.isRequired,
|
||||||
|
isModalOpen: bool,
|
||||||
|
groups: arrayOf(object),
|
||||||
|
};
|
||||||
|
|
||||||
|
InventoryGroupsDeleteModal.defaultProps = {
|
||||||
|
isModalOpen: false,
|
||||||
|
groups: [],
|
||||||
|
};
|
||||||
|
|
||||||
export default withI18n()(InventoryGroupsDeleteModal);
|
export default withI18n()(InventoryGroupsDeleteModal);
|
||||||
|
|||||||
@@ -199,8 +199,6 @@ export const Host = shape({
|
|||||||
enabled: bool,
|
enabled: bool,
|
||||||
instance_id: string,
|
instance_id: string,
|
||||||
variables: string,
|
variables: string,
|
||||||
has_active_failures: bool,
|
|
||||||
has_inventory_sources: bool,
|
|
||||||
last_job: number,
|
last_job: number,
|
||||||
last_job_host_summary: number,
|
last_job_host_summary: number,
|
||||||
});
|
});
|
||||||
@@ -242,10 +240,4 @@ export const Group = shape({
|
|||||||
description: string,
|
description: string,
|
||||||
inventory: number,
|
inventory: number,
|
||||||
variables: string,
|
variables: string,
|
||||||
has_active_failures: bool,
|
|
||||||
total_hosts: number,
|
|
||||||
hosts_with_active_failures: number,
|
|
||||||
total_groups: number,
|
|
||||||
groups_with_active_failures: number,
|
|
||||||
has_inventory_sources: bool,
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user