diff --git a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx
index defb79bab7..2d5b7d7232 100644
--- a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx
+++ b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx
@@ -1,14 +1,9 @@
-import React, { useEffect, useState } from 'react';
-import { useParams, useLocation } from 'react-router-dom';
+import React, { useCallback, useEffect } from 'react';
+import { useLocation, useParams } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { Card } from '@patternfly/react-core';
-
-import {
- JobTemplatesAPI,
- UnifiedJobTemplatesAPI,
- WorkflowJobTemplatesAPI,
-} from '../../../api';
+import { JobTemplatesAPI } from '../../../api';
import AlertModal from '../../../components/AlertModal';
import DatalistToolbar from '../../../components/DataListToolbar';
import ErrorDetail from '../../../components/ErrorDetail';
@@ -17,144 +12,93 @@ import PaginatedDataList, {
ToolbarDeleteButton,
} from '../../../components/PaginatedDataList';
import { getQSConfig, parseQueryString } from '../../../util/qs';
+import useSelected from '../../../util/useSelected';
+import useRequest, { useDeleteItems } from '../../../util/useRequest';
import ProjectTemplatesListItem from './ProjectJobTemplatesListItem';
-// The type value in const QS_CONFIG below does not have a space between job_template and
-// workflow_job_template so the params sent to the API match what the api expects.
const QS_CONFIG = getQSConfig('template', {
page: 1,
page_size: 20,
order_by: 'name',
- type: 'job_template,workflow_job_template',
});
function ProjectJobTemplatesList({ i18n }) {
const { id: projectId } = useParams();
- const { pathname, search } = useLocation();
+ const location = useLocation();
- const [deletionError, setDeletionError] = useState(null);
- const [contentError, setContentError] = useState(null);
- const [hasContentLoading, setHasContentLoading] = useState(true);
- const [jtActions, setJTActions] = useState(null);
- const [wfjtActions, setWFJTActions] = useState(null);
- const [count, setCount] = useState(0);
- const [templates, setTemplates] = useState([]);
- const [selected, setSelected] = useState([]);
-
- useEffect(
- () => {
- const loadTemplates = async () => {
- const params = {
- ...parseQueryString(QS_CONFIG, search),
- };
-
- let jtOptionsPromise;
- if (jtActions) {
- jtOptionsPromise = Promise.resolve({
- data: { actions: jtActions },
- });
- } else {
- jtOptionsPromise = JobTemplatesAPI.readOptions();
- }
-
- let wfjtOptionsPromise;
- if (wfjtActions) {
- wfjtOptionsPromise = Promise.resolve({
- data: { actions: wfjtActions },
- });
- } else {
- wfjtOptionsPromise = WorkflowJobTemplatesAPI.readOptions();
- }
- if (pathname.startsWith('/projects') && projectId) {
- params.jobtemplate__project = projectId;
- }
-
- const promises = Promise.all([
- UnifiedJobTemplatesAPI.read(params),
- jtOptionsPromise,
- wfjtOptionsPromise,
- ]);
- setDeletionError(null);
-
- try {
- const [
- {
- data: { count: itemCount, results },
- },
- {
- data: { actions: jobTemplateActions },
- },
- {
- data: { actions: workFlowJobTemplateActions },
- },
- ] = await promises;
- setJTActions(jobTemplateActions);
- setWFJTActions(workFlowJobTemplateActions);
- setCount(itemCount);
- setTemplates(results);
- setHasContentLoading(false);
- } catch (err) {
- setContentError(err);
- }
+ const {
+ result: { jobTemplates, itemCount, actions },
+ error: contentError,
+ isLoading,
+ request: fetchTemplates,
+ } = useRequest(
+ useCallback(async () => {
+ const params = parseQueryString(QS_CONFIG, location.search);
+ params.project = projectId;
+ const [response, actionsResponse] = await Promise.all([
+ JobTemplatesAPI.read(params),
+ JobTemplatesAPI.readOptions(),
+ ]);
+ return {
+ jobTemplates: response.data.results,
+ itemCount: response.data.count,
+ actions: actionsResponse.data.actions,
};
- loadTemplates();
- },
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [pathname, search, count, projectId]
+ }, [location, projectId]),
+ {
+ jobTemplates: [],
+ itemCount: 0,
+ actions: {},
+ }
);
- const handleSelectAll = isSelected => {
- const selectedItems = isSelected ? [...templates] : [];
- setSelected(selectedItems);
- };
+ useEffect(() => {
+ fetchTemplates();
+ }, [fetchTemplates]);
- const handleSelect = template => {
- if (selected.some(s => s.id === template.id)) {
- setSelected(selected.filter(s => s.id !== template.id));
- } else {
- setSelected(selected.concat(template));
+ const { selected, isAllSelected, handleSelect, setSelected } = useSelected(
+ jobTemplates
+ );
+
+ const {
+ isLoading: isDeleteLoading,
+ deleteItems: deleteTemplates,
+ deletionError,
+ clearDeletionError,
+ } = useDeleteItems(
+ useCallback(async () => {
+ return Promise.all(
+ selected.map(template => JobTemplatesAPI.destroy(template.id))
+ );
+ }, [selected]),
+ {
+ qsConfig: QS_CONFIG,
+ allItemsSelected: isAllSelected,
+ fetchItems: fetchTemplates,
}
- };
+ );
const handleTemplateDelete = async () => {
- setHasContentLoading(true);
- try {
- await Promise.all(
- selected.map(({ type, id }) => {
- let deletePromise;
- if (type === 'job_template') {
- deletePromise = JobTemplatesAPI.destroy(id);
- } else if (type === 'workflow_job_template') {
- deletePromise = WorkflowJobTemplatesAPI.destroy(id);
- }
- return deletePromise;
- })
- );
- setCount(count - selected.length);
- } catch (err) {
- setDeletionError(err);
- }
+ await deleteTemplates();
+ setSelected([]);
};
const canAddJT =
- jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
+ actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
const addButton = (
);
- const isAllSelected =
- selected.length === templates.length && selected.length > 0;
-
return (
<>
+ setSelected(isSelected ? [...jobTemplates] : [])
+ }
qsConfig={QS_CONFIG}
additionalControls={[
...(canAddJT ? [addButton] : []),
@@ -224,7 +162,7 @@ function ProjectJobTemplatesList({ i18n }) {
key="delete"
onDelete={handleTemplateDelete}
itemsToDelete={selected}
- pluralizedItemName="Templates"
+ pluralizedItemName={i18n._(t`Job templates`)}
/>,
]}
/>
@@ -246,9 +184,9 @@ function ProjectJobTemplatesList({ i18n }) {
isOpen={deletionError}
variant="danger"
title={i18n._(t`Error!`)}
- onClose={() => setDeletionError(null)}
+ onClose={clearDeletionError}
>
- {i18n._(t`Failed to delete one or more templates.`)}
+ {i18n._(t`Failed to delete one or more job templates.`)}
>