From df77147d65bf13cfe3c338d10036cce9ba6b2c15 Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Fri, 21 Feb 2020 12:33:53 -0500 Subject: [PATCH] WIP --- awx/ui_next/src/screens/Project/Project.jsx | 4 +- .../ProjectJobTemplates.jsx | 9 - .../Project/ProjectJobTemplates/index.js | 1 - .../ProjectJobTemplatesList.jsx | 271 ++++++++++++++++++ .../Project/ProjectJobTemplatesList/index.js | 4 + .../TemplateList/TemplateListItem.jsx | 8 +- 6 files changed, 281 insertions(+), 16 deletions(-) delete mode 100644 awx/ui_next/src/screens/Project/ProjectJobTemplates/ProjectJobTemplates.jsx delete mode 100644 awx/ui_next/src/screens/Project/ProjectJobTemplates/index.js create mode 100644 awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx create mode 100644 awx/ui_next/src/screens/Project/ProjectJobTemplatesList/index.js diff --git a/awx/ui_next/src/screens/Project/Project.jsx b/awx/ui_next/src/screens/Project/Project.jsx index a6a2cf16b0..c721e376f0 100644 --- a/awx/ui_next/src/screens/Project/Project.jsx +++ b/awx/ui_next/src/screens/Project/Project.jsx @@ -11,7 +11,7 @@ import NotificationList from '@components/NotificationList'; import { ResourceAccessList } from '@components/ResourceAccessList'; import ProjectDetail from './ProjectDetail'; import ProjectEdit from './ProjectEdit'; -import ProjectJobTemplates from './ProjectJobTemplates'; +import ProjectJobTemplatesList from './ProjectJobTemplatesList'; import ProjectSchedules from './ProjectSchedules'; import { OrganizationsAPI, ProjectsAPI } from '@api'; @@ -225,7 +225,7 @@ class Project extends Component { ( - + )} /> ; -} - -export default withRouter(ProjectJobTemplates); diff --git a/awx/ui_next/src/screens/Project/ProjectJobTemplates/index.js b/awx/ui_next/src/screens/Project/ProjectJobTemplates/index.js deleted file mode 100644 index 7652ab295d..0000000000 --- a/awx/ui_next/src/screens/Project/ProjectJobTemplates/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ProjectJobTemplates'; diff --git a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx new file mode 100644 index 0000000000..5f36cd7938 --- /dev/null +++ b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/ProjectJobTemplatesList.jsx @@ -0,0 +1,271 @@ +import React, { useEffect, useState } from 'react'; +import { useParams, useLocation } 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 AlertModal from '@components/AlertModal'; +import DatalistToolbar from '@components/DataListToolbar'; +import ErrorDetail from '@components/ErrorDetail'; +import PaginatedDataList, { + ToolbarDeleteButton, +} from '@components/PaginatedDataList'; +import { getQSConfig, parseQueryString } from '@util/qs'; + +import AddDropDownButton from '@components/AddDropDownButton'; +import ProjectTemplatesListItem from '../../Template/TemplateList/TemplateListItem'; + +// 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 [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); + } + }; + loadTemplates(); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [pathname, search, count, projectId] + ); + + const handleSelectAll = isSelected => { + const selectedItems = isSelected ? [...templates] : []; + setSelected(selectedItems); + }; + + 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 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); + } + }; + + const canAddJT = + jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST'); + const canAddWFJT = + wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST'); + const addButtonOptions = []; + if (canAddJT) { + addButtonOptions.push({ + label: i18n._(t`Template`), + url: `/templates/job_template/add/`, + }); + } + if (canAddWFJT) { + addButtonOptions.push({ + label: i18n._(t`Workflow Template`), + url: `/templates/workflow_job_template/add/`, + }); + } + const isAllSelected = + selected.length === templates.length && selected.length > 0; + const addButton = ( + + ); + return ( + <> + + ( + , + (canAddJT || canAddWFJT) && addButton, + ]} + /> + )} + renderItem={template => ( + handleSelect(template)} + isSelected={selected.some(row => row.id === template.id)} + /> + )} + emptyStateControls={(canAddJT || canAddWFJT) && addButton} + /> + + setDeletionError(null)} + > + {i18n._(t`Failed to delete one or more templates.`)} + + + + ); +} + +export default withI18n()(ProjectJobTemplatesList); diff --git a/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/index.js b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/index.js new file mode 100644 index 0000000000..7aac97295e --- /dev/null +++ b/awx/ui_next/src/screens/Project/ProjectJobTemplatesList/index.js @@ -0,0 +1,4 @@ +export { + default +} +from './ProjectJobTemplatesList'; diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index f08a0ccb06..86c506912e 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -40,11 +40,11 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) { (!template.summary_fields.inventory && !template.ask_inventory_on_launch)); - const location = useLocation(); + // const location = useLocation(); - if (location.pathname.startsWith('/projects')) { - detailUrl = `/templates/job_template/${template.id}/details`; - } + // if (location.pathname.startsWith('/projects')) { + // detailUrl = `/templates/job_template/${template.id}/details`; + // } return (