mirror of
https://github.com/ansible/awx.git
synced 2026-02-23 14:05:59 -03:30
adds counts for inventory groups and hosts that are related to an inventory source
This commit is contained in:
@@ -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) {
|
destroyGroups(id) {
|
||||||
return this.http.delete(`${this.baseUrl}${id}/groups/`);
|
return this.http.delete(`${this.baseUrl}${id}/groups/`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ function DeleteButton({
|
|||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [deleteMessageError, setDeleteMessageError] = useState();
|
const [deleteMessageError, setDeleteMessageError] = useState();
|
||||||
const [deleteDetails, setDeleteDetails] = useState({});
|
const [deleteDetails, setDeleteDetails] = useState({});
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const toggleModal = async isModalOpen => {
|
const toggleModal = async isModalOpen => {
|
||||||
|
setIsLoading(true);
|
||||||
if (deleteDetailsRequests?.length && isModalOpen) {
|
if (deleteDetailsRequests?.length && isModalOpen) {
|
||||||
const { results, error } = await getRelatedResourceDeleteCounts(
|
const { results, error } = await getRelatedResourceDeleteCounts(
|
||||||
deleteDetailsRequests
|
deleteDetailsRequests
|
||||||
@@ -43,6 +46,7 @@ function DeleteButton({
|
|||||||
setDeleteDetails(results);
|
setDeleteDetails(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
setIsOpen(isModalOpen);
|
setIsOpen(isModalOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,10 +70,13 @@ function DeleteButton({
|
|||||||
<Tooltip content={disabledTooltip} position="top">
|
<Tooltip content={disabledTooltip} position="top">
|
||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
|
isLoading={isLoading}
|
||||||
|
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
|
||||||
variant={variant || 'secondary'}
|
variant={variant || 'secondary'}
|
||||||
aria-label={i18n._(t`Delete`)}
|
aria-label={i18n._(t`Delete`)}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
onClick={() => toggleModal(true)}
|
onClick={() => toggleModal(true)}
|
||||||
|
ouiaId={ouiaId}
|
||||||
>
|
>
|
||||||
{children || i18n._(t`Delete`)}
|
{children || i18n._(t`Delete`)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -77,6 +84,8 @@ function DeleteButton({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
isLoading={isLoading}
|
||||||
|
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
|
||||||
variant={variant || 'secondary'}
|
variant={variant || 'secondary'}
|
||||||
aria-label={i18n._(t`Delete`)}
|
aria-label={i18n._(t`Delete`)}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ function ToolbarDeleteButton({
|
|||||||
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
const [deleteDetails, setDeleteDetails] = useState(null);
|
const [deleteDetails, setDeleteDetails] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const [deleteMessageError, setDeleteMessageError] = useState();
|
const [deleteMessageError, setDeleteMessageError] = useState();
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
@@ -109,6 +110,7 @@ function ToolbarDeleteButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const toggleModal = async isOpen => {
|
const toggleModal = async isOpen => {
|
||||||
|
setIsLoading(true);
|
||||||
setDeleteDetails(null);
|
setDeleteDetails(null);
|
||||||
if (
|
if (
|
||||||
isOpen &&
|
isOpen &&
|
||||||
@@ -125,7 +127,7 @@ function ToolbarDeleteButton({
|
|||||||
setDeleteDetails(results);
|
setDeleteDetails(results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
setIsLoading(false);
|
||||||
setIsModalOpen(isOpen);
|
setIsModalOpen(isOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -221,8 +223,12 @@ function ToolbarDeleteButton({
|
|||||||
<DropdownItem
|
<DropdownItem
|
||||||
key="add"
|
key="add"
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
isLoading={isLoading}
|
||||||
|
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
|
||||||
component="button"
|
component="button"
|
||||||
onClick={() => toggleModal(true)}
|
onClick={() => {
|
||||||
|
toggleModal(true);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{i18n._(t`Delete`)}
|
{i18n._(t`Delete`)}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
@@ -232,6 +238,8 @@ function ToolbarDeleteButton({
|
|||||||
<div>
|
<div>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
|
isLoading={isLoading}
|
||||||
|
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
|
||||||
aria-label={i18n._(t`Delete`)}
|
aria-label={i18n._(t`Delete`)}
|
||||||
onClick={() => toggleModal(true)}
|
onClick={() => toggleModal(true)}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
|
|||||||
<Button
|
<Button
|
||||||
aria-label="Delete"
|
aria-label="Delete"
|
||||||
isDisabled={true}
|
isDisabled={true}
|
||||||
|
isLoading={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
>
|
>
|
||||||
@@ -93,13 +94,14 @@ exports[`<ToolbarDeleteButton /> should render button 1`] = `
|
|||||||
<Button
|
<Button
|
||||||
aria-label="Delete"
|
aria-label="Delete"
|
||||||
isDisabled={true}
|
isDisabled={true}
|
||||||
|
isLoading={false}
|
||||||
onClick={[Function]}
|
onClick={[Function]}
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-disabled={true}
|
aria-disabled={true}
|
||||||
aria-label="Delete"
|
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-id="OUIA-Generated-Button-secondary-1"
|
||||||
data-ouia-component-type="PF4/Button"
|
data-ouia-component-type="PF4/Button"
|
||||||
data-ouia-safe={true}
|
data-ouia-safe={true}
|
||||||
|
|||||||
@@ -6,10 +6,22 @@ import {
|
|||||||
waitForElement,
|
waitForElement,
|
||||||
} from '../../../../testUtils/enzymeHelpers';
|
} from '../../../../testUtils/enzymeHelpers';
|
||||||
|
|
||||||
import { ExecutionEnvironmentsAPI } from '../../../api';
|
import {
|
||||||
|
ExecutionEnvironmentsAPI,
|
||||||
|
InventorySourcesAPI,
|
||||||
|
WorkflowJobTemplateNodesAPI,
|
||||||
|
OrganizationsAPI,
|
||||||
|
ProjectsAPI,
|
||||||
|
UnifiedJobTemplatesAPI,
|
||||||
|
} from '../../../api';
|
||||||
import ExecutionEnvironmentList from './ExecutionEnvironmentList';
|
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 = {
|
const executionEnvironments = {
|
||||||
data: {
|
data: {
|
||||||
@@ -43,6 +55,16 @@ describe('<ExecutionEnvironmentList/>', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
|
ExecutionEnvironmentsAPI.read.mockResolvedValue(executionEnvironments);
|
||||||
ExecutionEnvironmentsAPI.readOptions.mockResolvedValue(options);
|
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(() => {
|
afterEach(() => {
|
||||||
@@ -143,6 +165,12 @@ describe('<ExecutionEnvironmentList/>', () => {
|
|||||||
wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
|
wrapper.find('Button[aria-label="Delete"]').prop('onClick')()
|
||||||
);
|
);
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|
||||||
|
await waitForElement(
|
||||||
|
wrapper,
|
||||||
|
'Button[aria-label="confirm delete"]',
|
||||||
|
el => el.length > 0
|
||||||
|
);
|
||||||
await act(async () =>
|
await act(async () =>
|
||||||
wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
|
wrapper.find('Button[aria-label="confirm delete"]').prop('onClick')()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,7 +99,8 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
|||||||
|
|
||||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
||||||
inventorySource.inventory,
|
inventorySource.inventory,
|
||||||
i18n
|
i18n,
|
||||||
|
inventorySource
|
||||||
);
|
);
|
||||||
|
|
||||||
const VERBOSITY = {
|
const VERBOSITY = {
|
||||||
@@ -289,7 +290,7 @@ function InventorySourceDetail({ inventorySource, i18n }) {
|
|||||||
onConfirm={handleDelete}
|
onConfirm={handleDelete}
|
||||||
deleteDetailsRequests={deleteDetailsRequests}
|
deleteDetailsRequests={deleteDetailsRequests}
|
||||||
deleteMessage={i18n._(
|
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`)}
|
{i18n._(t`Delete`)}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ describe('InventorySourceDetail', () => {
|
|||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
|
wrapper.find('DeleteButton').prop('deleteDetailsRequests')
|
||||||
).toHaveLength(1);
|
).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should hide expected action buttons for users without permissions', async () => {
|
test('should hide expected action buttons for users without permissions', async () => {
|
||||||
|
|||||||
@@ -146,7 +146,8 @@ function InventorySourceList({ i18n }) {
|
|||||||
|
|
||||||
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
const deleteDetailsRequests = relatedResourceDeleteRequests.inventorySource(
|
||||||
id,
|
id,
|
||||||
i18n
|
i18n,
|
||||||
|
selected[0]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -182,7 +183,7 @@ function InventorySourceList({ i18n }) {
|
|||||||
pluralizedItemName={i18n._(t`Inventory Sources`)}
|
pluralizedItemName={i18n._(t`Inventory Sources`)}
|
||||||
deleteDetailsRequests={deleteDetailsRequests}
|
deleteDetailsRequests={deleteDetailsRequests}
|
||||||
deleteMessage={i18n._(
|
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 }
|
{ numItemsToDelete: selected.length }
|
||||||
)}
|
)}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@@ -65,11 +65,13 @@ describe('<InventorySourceList />', () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
debug = global.console.debug; // eslint-disable-line prefer-destructuring
|
debug = global.console.debug; // eslint-disable-line prefer-destructuring
|
||||||
global.console.debug = () => {};
|
global.console.debug = () => {};
|
||||||
|
InventoriesAPI.readSources.mockResolvedValue(sources);
|
||||||
InventoriesAPI.updateSources.mockResolvedValue({
|
InventoriesAPI.updateSources.mockResolvedValue({
|
||||||
data: [{ inventory_source: 1 }],
|
data: [{ inventory_source: 1 }],
|
||||||
});
|
});
|
||||||
|
InventorySourcesAPI.readGroups.mockResolvedValue({ data: { count: 0 } });
|
||||||
|
InventorySourcesAPI.readHosts.mockResolvedValue({ data: { count: 0 } });
|
||||||
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
|
WorkflowJobTemplateNodesAPI.read.mockResolvedValue({ data: { count: 0 } });
|
||||||
InventoriesAPI.readSources.mockResolvedValue(sources);
|
|
||||||
InventorySourcesAPI.readOptions.mockResolvedValue({
|
InventorySourcesAPI.readOptions.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
actions: {
|
actions: {
|
||||||
@@ -131,7 +133,7 @@ describe('<InventorySourceList />', () => {
|
|||||||
test('should have proper number of delete detail requests', async () => {
|
test('should have proper number of delete detail requests', async () => {
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
|
wrapper.find('ToolbarDeleteButton').prop('deleteDetailsRequests')
|
||||||
).toHaveLength(1);
|
).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('source data should render properly', async () => {
|
test('source data should render properly', async () => {
|
||||||
|
|||||||
@@ -112,25 +112,24 @@ export const relatedResourceDeleteRequests = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
inventorySource: (inventoryId, i18n) => [
|
inventorySource: (inventoryId, i18n, inventorySource) => [
|
||||||
{
|
{
|
||||||
request: async () => {
|
request: async () => {
|
||||||
try {
|
try {
|
||||||
const { data } = await InventoriesAPI.updateSources(inventoryId);
|
const { data } = await InventoriesAPI.updateSources(inventoryId);
|
||||||
let total = 0;
|
|
||||||
await Promise.all(
|
const results = await Promise.all(
|
||||||
data.map(async datum => {
|
data.map(async datum =>
|
||||||
const {
|
WorkflowJobTemplateNodesAPI.read({
|
||||||
data: { count },
|
|
||||||
} = await WorkflowJobTemplateNodesAPI.read({
|
|
||||||
unified_job_template: datum.inventory_source,
|
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 } };
|
return { data: { count: total } };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(err);
|
throw new Error(err);
|
||||||
@@ -138,6 +137,14 @@ export const relatedResourceDeleteRequests = {
|
|||||||
},
|
},
|
||||||
label: i18n._(t`Workflow Job Template Nodes`),
|
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) => [
|
project: (selected, i18n) => [
|
||||||
@@ -255,18 +262,18 @@ export const relatedResourceDeleteRequests = {
|
|||||||
} = await InventorySourcesAPI.read({
|
} = await InventorySourcesAPI.read({
|
||||||
execution_environment: selected.id,
|
execution_environment: selected.id,
|
||||||
});
|
});
|
||||||
let total = 0;
|
|
||||||
await Promise.all(
|
const responses = await Promise.all(
|
||||||
results.map(async result => {
|
results.map(result =>
|
||||||
const {
|
WorkflowJobTemplateNodesAPI.read({
|
||||||
data: { count },
|
|
||||||
} = await WorkflowJobTemplateNodesAPI.read({
|
|
||||||
unified_job_template: result.id,
|
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 } };
|
return { data: { count: total } };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user