adds counts for inventory groups and hosts that are related to an inventory source

This commit is contained in:
Alex Corey 2021-03-17 13:25:42 -04:00
parent be148b5fd4
commit 652e7a500b
10 changed files with 102 additions and 36 deletions

View File

@ -22,6 +22,14 @@ class InventorySources extends LaunchUpdateMixin(
});
}
readGroups(id) {
return this.http.get(`${this.baseUrl}${id}/groups/`);
}
readHosts(id) {
return this.http.get(`${this.baseUrl}${id}/hosts/`);
}
destroyGroups(id) {
return this.http.delete(`${this.baseUrl}${id}/groups/`);
}

View File

@ -32,7 +32,10 @@ function DeleteButton({
const [isOpen, setIsOpen] = useState(false);
const [deleteMessageError, setDeleteMessageError] = useState();
const [deleteDetails, setDeleteDetails] = useState({});
const [isLoading, setIsLoading] = useState(false);
const toggleModal = async isModalOpen => {
setIsLoading(true);
if (deleteDetailsRequests?.length && isModalOpen) {
const { results, error } = await getRelatedResourceDeleteCounts(
deleteDetailsRequests
@ -43,6 +46,7 @@ function DeleteButton({
setDeleteDetails(results);
}
}
setIsLoading(false);
setIsOpen(isModalOpen);
};
@ -66,10 +70,13 @@ function DeleteButton({
<Tooltip content={disabledTooltip} position="top">
<div>
<Button
isLoading={isLoading}
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
variant={variant || 'secondary'}
aria-label={i18n._(t`Delete`)}
isDisabled={isDisabled}
onClick={() => toggleModal(true)}
ouiaId={ouiaId}
>
{children || i18n._(t`Delete`)}
</Button>
@ -77,6 +84,8 @@ function DeleteButton({
</Tooltip>
) : (
<Button
isLoading={isLoading}
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
variant={variant || 'secondary'}
aria-label={i18n._(t`Delete`)}
isDisabled={isDisabled}

View File

@ -101,6 +101,7 @@ function ToolbarDeleteButton({
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
const [isModalOpen, setIsModalOpen] = useState(false);
const [deleteDetails, setDeleteDetails] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [deleteMessageError, setDeleteMessageError] = useState();
const handleDelete = () => {
@ -109,6 +110,7 @@ function ToolbarDeleteButton({
};
const toggleModal = async isOpen => {
setIsLoading(true);
setDeleteDetails(null);
if (
isOpen &&
@ -125,7 +127,7 @@ function ToolbarDeleteButton({
setDeleteDetails(results);
}
}
setIsLoading(false);
setIsModalOpen(isOpen);
};
@ -221,8 +223,12 @@ function ToolbarDeleteButton({
<DropdownItem
key="add"
isDisabled={isDisabled}
isLoading={isLoading}
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
component="button"
onClick={() => toggleModal(true)}
onClick={() => {
toggleModal(true);
}}
>
{i18n._(t`Delete`)}
</DropdownItem>
@ -232,6 +238,8 @@ function ToolbarDeleteButton({
<div>
<Button
variant="secondary"
isLoading={isLoading}
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
aria-label={i18n._(t`Delete`)}
onClick={() => toggleModal(true)}
isDisabled={isDisabled}

View File

@ -75,6 +75,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
<Button
aria-label="Delete"
isDisabled={true}
isLoading={false}
onClick={[Function]}
variant="secondary"
>
@ -93,13 +94,14 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
<Button
aria-label="Delete"
isDisabled={true}
isLoading={false}
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-disabled pf-m-progress"
data-ouia-component-id="OUIA-Generated-Button-secondary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}

View File

@ -6,10 +6,22 @@ import {
waitForElement,
} from '../../../../testUtils/enzymeHelpers';
import { ExecutionEnvironmentsAPI } from '../../../api';
import {
ExecutionEnvironmentsAPI,
InventorySourcesAPI,
WorkflowJobTemplateNodesAPI,
OrganizationsAPI,
ProjectsAPI,
UnifiedJobTemplatesAPI,
} from '../../../api';
import ExecutionEnvironmentList from './ExecutionEnvironmentList';
jest.mock('../../../api');
jest.mock('../../../api/models/ExecutionEnvironments');
jest.mock('../../../api/models/UnifiedJobTemplates');
jest.mock('../../../api/models/Projects');
jest.mock('../../../api/models/Organizations');
jest.mock('../../../api/models/InventorySources');
jest.mock('../../../api/models/WorkflowJobTemplateNodes');
const executionEnvironments = {
data: {
@ -43,6 +55,16 @@ describe('<ExecutionEnvironmentList/>', () => {
beforeEach(() => {
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
InventorySourcesAPI.read.mockResolvedValue({
data: { results: [{ id: 10000000 }] },
});
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
OrganizationsAPI.read.mockResolvedValue({ data: { count: 0 } });
UnifiedJobTemplatesAPI.read.mockResolvedValue({ data: { count: 0 } });
ProjectsAPI.read.mockResolvedValue({ data: { count: 0 } });
});
afterEach(() => {
@ -143,6 +165,12 @@ describe('<ExecutionEnvironmentList/>', () => {
wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
);
wrapper.update();
await waitForElement(
wrapper,
'Button[aria-label="confirm delete"]',
el => el.length > 0
);
await act(async () =>
wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
);

View File

@ -99,7 +99,8 @@ function InventorySourceDetail({ inventorySource, i18n }) {
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
inventorySource.inventory,
i18n
i18n,
inventorySource
);
const VERBOSITY = {
@ -289,7 +290,7 @@ function InventorySourceDetail({ inventorySource, i18n }) {
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?`
t`This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?`
)}
>
{i18n._(t`Delete`)}

View File

@ -120,7 +120,7 @@ describe('InventorySourceDetail', () => {
});
expect(
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
).toHaveLength(3);
});
test('should hide expected action buttons for users without permissions', async () => {

View File

@ -146,7 +146,8 @@ function InventorySourceList({ i18n }) {
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
id,
i18n
i18n,
selected[0]
);
return (
<>
@ -182,7 +183,7 @@ function InventorySourceList({ i18n }) {
pluralizedItemName={i18n._(t`Inventory Sources`)}
deleteDetailsRequests={deleteDetailsRequests}
deleteMessage={i18n._(
'{numItemsToDelete, plural, one {This inventory source is currently being used by workflow job template nodes. Are you sure you want to delete it?} other {Deleting these inventory sources could impact workflow job template nodes that rely on them. Are you sure you want to delete anyway?}}',
'{numItemsToDelete, plural, one {This inventory source is currently being used by other resources that rely on it. Are you sure you want to delete it?} other {Deleting these inventory sources could impact other resources that rely on them. Are you sure you want to delete anyway?}}',
{ numItemsToDelete: selected.length }
)}
/>,

View File

@ -65,11 +65,13 @@ describe('<InventorySourceList />', () => {
beforeEach(async () => {
debug = global.console.debug; // eslint-disable-line prefer-destructuring
global.console.debug = () => {};
InventoriesAPI.readSources.mockResolvedValue(sources);
InventoriesAPI.updateSources.mockResolvedValue({
data: [{ inventory_source: 1 }],
});
InventorySourcesAPI.readGroups.mockResolvedValue({ data: { count: 0 } });
InventorySourcesAPI.readHosts.mockResolvedValue({ data: { count: 0 } });
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
InventoriesAPI.readSources.mockResolvedValue(sources);
InventorySourcesAPI.readOptions.mockResolvedValue({
data: {
actions: {
@ -131,7 +133,7 @@ describe('<InventorySourceList />', () => {
test('should have proper number of delete detail requests', async () => {
expect(
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
).toHaveLength(1);
).toHaveLength(3);
});
test('source data should render properly', async () => {

View File

@ -112,25 +112,24 @@ export const relatedResourceDeleteRequests = {
},
],
inventorySource: (inventoryId, i18n) => [
inventorySource: (inventoryId, i18n, inventorySource) => [
{
request: async () => {
try {
const { data } = await InventoriesAPI.updateSources(inventoryId);
let total = 0;
await Promise.all(
data.map(async datum => {
const {
data: { count },
} = await WorkflowJobTemplateNodesAPI.read({
const results = await Promise.all(
data.map(async datum =>
WorkflowJobTemplateNodesAPI.read({
unified_job_template: datum.inventory_source,
});
if (count > 0) {
total += count;
}
})
})
)
);
console.log(total, 'total');
const total = results.reduce(
({ data: { count: acc } }, { data: { count: cur } }) => acc + cur,
{ data: { count: 0 } }
);
return { data: { count: total } };
} catch (err) {
throw new Error(err);
@ -138,6 +137,14 @@ export const relatedResourceDeleteRequests = {
},
label: i18n._(t`Workflow Job Template Nodes`),
},
{
request: async () => InventorySourcesAPI.readGroups(inventorySource.id),
label: i18n._(t`Groups`),
},
{
request: async () => InventorySourcesAPI.readHosts(inventorySource.id),
label: i18n._(t`Hosts`),
},
],
project: (selected, i18n) => [
@ -255,18 +262,18 @@ export const relatedResourceDeleteRequests = {
} = await InventorySourcesAPI.read({
execution_environment: selected.id,
});
let total = 0;
await Promise.all(
results.map(async result => {
const {
data: { count },
} = await WorkflowJobTemplateNodesAPI.read({
const responses = await Promise.all(
results.map(result =>
WorkflowJobTemplateNodesAPI.read({
unified_job_template: result.id,
});
if (count > 0) {
total += count;
}
})
})
)
);
const total = responses.reduce(
({ data: { count: acc } }, { data: { count: cur } }) => acc + cur,
{ data: { count: 0 } }
);
return { data: { count: total } };
} catch (err) {