From 37a33f931a295938c01b1dcff19930e73438bfa9 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 25 Feb 2020 14:19:42 -0500 Subject: [PATCH] Add completed jobs subtab to all resources * Resources include: Host, InventoryHost, Inventory, SmartInventory, Template, and WFTemplate * Move JobList into top-level shared component directory --- .../Job => components}/JobList/JobList.jsx | 39 +++-- .../JobList/JobList.test.jsx | 0 .../src/components/JobList/JobListItem.jsx | 90 ++++++++++ .../components/JobList/JobListItem.test.jsx | 82 +++++++++ awx/ui_next/src/components/JobList/index.js | 1 + awx/ui_next/src/screens/Host/Host.jsx | 14 +- .../HostCompletedJobs/HostCompletedJobs.jsx | 10 -- .../screens/Host/HostCompletedJobs/index.js | 1 - .../src/screens/Inventory/Inventories.jsx | 13 +- .../src/screens/Inventory/Inventory.jsx | 25 ++- .../InventoryCompletedJobs.jsx | 10 -- .../Inventory/InventoryCompletedJobs/index.js | 1 - .../src/screens/Inventory/SmartInventory.jsx | 17 +- .../SmartInventoryCompletedJobs.jsx | 10 -- .../SmartInventoryCompletedJobs/index.js | 1 - .../src/screens/Job/JobList/JobListItem.jsx | 82 --------- .../screens/Job/JobList/JobListItem.test.jsx | 32 ---- awx/ui_next/src/screens/Job/JobList/index.js | 2 - awx/ui_next/src/screens/Job/Jobs.jsx | 157 +++++++++--------- awx/ui_next/src/screens/Template/Template.jsx | 11 +- .../src/screens/Template/Templates.jsx | 5 +- .../screens/Template/WorkflowJobTemplate.jsx | 11 ++ 22 files changed, 336 insertions(+), 278 deletions(-) rename awx/ui_next/src/{screens/Job => components}/JobList/JobList.jsx (94%) rename awx/ui_next/src/{screens/Job => components}/JobList/JobList.test.jsx (100%) create mode 100644 awx/ui_next/src/components/JobList/JobListItem.jsx create mode 100644 awx/ui_next/src/components/JobList/JobListItem.test.jsx create mode 100644 awx/ui_next/src/components/JobList/index.js delete mode 100644 awx/ui_next/src/screens/Host/HostCompletedJobs/HostCompletedJobs.jsx delete mode 100644 awx/ui_next/src/screens/Host/HostCompletedJobs/index.js delete mode 100644 awx/ui_next/src/screens/Inventory/InventoryCompletedJobs/InventoryCompletedJobs.jsx delete mode 100644 awx/ui_next/src/screens/Inventory/InventoryCompletedJobs/index.js delete mode 100644 awx/ui_next/src/screens/Inventory/SmartInventoryCompletedJobs/SmartInventoryCompletedJobs.jsx delete mode 100644 awx/ui_next/src/screens/Inventory/SmartInventoryCompletedJobs/index.js delete mode 100644 awx/ui_next/src/screens/Job/JobList/JobListItem.jsx delete mode 100644 awx/ui_next/src/screens/Job/JobList/JobListItem.test.jsx delete mode 100644 awx/ui_next/src/screens/Job/JobList/index.js diff --git a/awx/ui_next/src/screens/Job/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx similarity index 94% rename from awx/ui_next/src/screens/Job/JobList/JobList.jsx rename to awx/ui_next/src/components/JobList/JobList.jsx index 94bb99b73e..f03fa5ac89 100644 --- a/awx/ui_next/src/screens/Job/JobList/JobList.jsx +++ b/awx/ui_next/src/components/JobList/JobList.jsx @@ -2,8 +2,17 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; -import { Card, PageSection } from '@patternfly/react-core'; +import { Card } from '@patternfly/react-core'; +import AlertModal from '@components/AlertModal'; +import DatalistToolbar from '@components/DataListToolbar'; +import ErrorDetail from '@components/ErrorDetail'; +import PaginatedDataList, { + ToolbarDeleteButton, +} from '@components/PaginatedDataList'; +import useRequest, { useDeleteItems } from '@util/useRequest'; +import { getQSConfig, parseQueryString } from '@util/qs'; +import JobListItem from './JobListItem'; import { AdHocCommandsAPI, InventoryUpdatesAPI, @@ -13,16 +22,6 @@ import { UnifiedJobsAPI, WorkflowJobsAPI, } from '@api'; -import AlertModal from '@components/AlertModal'; -import DatalistToolbar from '@components/DataListToolbar'; -import ErrorDetail from '@components/ErrorDetail'; -import PaginatedDataList, { - ToolbarDeleteButton, -} from '@components/PaginatedDataList'; -import useRequest, { useDeleteItems } from '@util/useRequest'; -import { getQSConfig, parseQueryString } from '@util/qs'; - -import JobListItem from './JobListItem'; const QS_CONFIG = getQSConfig( 'job', @@ -30,12 +29,11 @@ const QS_CONFIG = getQSConfig( page: 1, page_size: 20, order_by: '-finished', - not__launch_type: 'sync', }, - ['page', 'page_size', 'id'] + ['page', 'page_size'] ); -function JobList({ i18n }) { +function JobList({ i18n, defaultParams, showTypeColumn = false }) { const [selected, setSelected] = useState([]); const location = useLocation(); @@ -47,14 +45,16 @@ function JobList({ i18n }) { } = useRequest( useCallback(async () => { const params = parseQueryString(QS_CONFIG, location.search); + const { data: { count, results }, - } = await UnifiedJobsAPI.read(params); + } = await UnifiedJobsAPI.read({ ...params, ...defaultParams }); + return { itemCount: count, jobs: results, }; - }, [location]), + }, [location]), // eslint-disable-line react-hooks/exhaustive-deps { jobs: [], itemCount: 0, @@ -119,7 +119,7 @@ function JobList({ i18n }) { }; return ( - + <> ( handleSelect(job)} isSelected={selected.some(row => row.id === job.id)} /> @@ -243,7 +242,7 @@ function JobList({ i18n }) { {i18n._(t`Failed to delete one or more jobs.`)} - + ); } diff --git a/awx/ui_next/src/screens/Job/JobList/JobList.test.jsx b/awx/ui_next/src/components/JobList/JobList.test.jsx similarity index 100% rename from awx/ui_next/src/screens/Job/JobList/JobList.test.jsx rename to awx/ui_next/src/components/JobList/JobList.test.jsx diff --git a/awx/ui_next/src/components/JobList/JobListItem.jsx b/awx/ui_next/src/components/JobList/JobListItem.jsx new file mode 100644 index 0000000000..a577c605b8 --- /dev/null +++ b/awx/ui_next/src/components/JobList/JobListItem.jsx @@ -0,0 +1,90 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Button, + DataListAction, + DataListCell, + DataListCheck, + DataListItem, + DataListItemRow, + DataListItemCells, + Tooltip, +} from '@patternfly/react-core'; +import { RocketIcon } from '@patternfly/react-icons'; +import LaunchButton from '@components/LaunchButton'; +import StatusIcon from '@components/StatusIcon'; +import { toTitleCase } from '@util/strings'; +import { formatDateString } from '@util/dates'; +import { JOB_TYPE_URL_SEGMENTS } from '@constants'; + +function JobListItem({ + i18n, + job, + isSelected, + onSelect, + showTypeColumn = false, +}) { + const labelId = `check-action-${job.id}`; + + return ( + + + + + {job.status && } + , + + + + + {job.id} — {job.name} + + + + , + ...(showTypeColumn + ? [ + + {toTitleCase(job.type)} + , + ] + : []), + + {formatDateString(job.finished)} + , + ]} + /> + {job.type !== 'system_job' && + job.summary_fields?.user_capabilities?.start && ( + + + + {({ handleRelaunch }) => ( + + )} + + + + )} + + + ); +} + +export { JobListItem as _JobListItem }; +export default withI18n()(JobListItem); diff --git a/awx/ui_next/src/components/JobList/JobListItem.test.jsx b/awx/ui_next/src/components/JobList/JobListItem.test.jsx new file mode 100644 index 0000000000..d087b23b95 --- /dev/null +++ b/awx/ui_next/src/components/JobList/JobListItem.test.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { createMemoryHistory } from 'history'; + +import { mountWithContexts } from '@testUtils/enzymeHelpers'; + +import JobListItem from './JobListItem'; + +const mockJob = { + id: 123, + type: 'job', + url: '/api/v2/jobs/123/', + summary_fields: { + user_capabilities: { + delete: true, + start: true, + }, + }, + created: '2019-08-08T19:24:05.344276Z', + modified: '2019-08-08T19:24:18.162949Z', + name: 'Demo Job Template', + job_type: 'run', + started: '2019-08-08T19:24:18.329589Z', + finished: '2019-08-08T19:24:50.119995Z', + status: 'successful', +}; + +describe('', () => { + let wrapper; + + beforeEach(() => { + const history = createMemoryHistory({ + initialEntries: ['/jobs'], + }); + wrapper = mountWithContexts( + {}} />, + { context: { router: { history } } } + ); + }); + + afterEach(() => { + wrapper.unmount(); + }); + + test('initially renders successfully', () => { + expect(wrapper.find('JobListItem').length).toBe(1); + }); + + test('launch button shown to users with launch capabilities', () => { + expect(wrapper.find('LaunchButton').length).toBe(1); + }); + + test('launch button hidden from users without launch capabilities', () => { + wrapper = mountWithContexts( + {}} + isSelected={false} + /> + ); + expect(wrapper.find('LaunchButton').length).toBe(0); + }); + + test('should hide type column when showTypeColumn is false', () => { + expect(wrapper.find('DataListCell[aria-label="type"]').length).toBe(0); + }); + + test('should show type column when showTypeColumn is true', () => { + wrapper = mountWithContexts( + {}} + /> + ); + expect(wrapper.find('DataListCell[aria-label="type"]').length).toBe(1); + }); +}); diff --git a/awx/ui_next/src/components/JobList/index.js b/awx/ui_next/src/components/JobList/index.js new file mode 100644 index 0000000000..35e78bb71e --- /dev/null +++ b/awx/ui_next/src/components/JobList/index.js @@ -0,0 +1 @@ +export { default } from './JobList'; diff --git a/awx/ui_next/src/screens/Host/Host.jsx b/awx/ui_next/src/screens/Host/Host.jsx index 57670658bc..37096fc024 100644 --- a/awx/ui_next/src/screens/Host/Host.jsx +++ b/awx/ui_next/src/screens/Host/Host.jsx @@ -17,11 +17,11 @@ import CardCloseButton from '@components/CardCloseButton'; import RoutedTabs from '@components/RoutedTabs'; import ContentError from '@components/ContentError'; import ContentLoading from '@components/ContentLoading'; +import JobList from '@components/JobList'; import HostFacts from './HostFacts'; import HostDetail from './HostDetail'; import HostEdit from './HostEdit'; import HostGroups from './HostGroups'; -import HostCompletedJobs from './HostCompletedJobs'; import { HostsAPI } from '@api'; function Host({ inventory, i18n, setBreadcrumb }) { @@ -181,11 +181,15 @@ function Host({ inventory, i18n, setBreadcrumb }) { render={() => } /> )} - {host && ( + {host?.id && ( } - /> + path={[ + '/hosts/:id/completed_jobs', + '/inventories/inventory/:id/hosts/:hostId/completed_jobs', + ]} + > + + )} Coming soon :); - } -} - -export default HostCompletedJobs; diff --git a/awx/ui_next/src/screens/Host/HostCompletedJobs/index.js b/awx/ui_next/src/screens/Host/HostCompletedJobs/index.js deleted file mode 100644 index 9dbe868803..0000000000 --- a/awx/ui_next/src/screens/Host/HostCompletedJobs/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './HostCompletedJobs'; diff --git a/awx/ui_next/src/screens/Inventory/Inventories.jsx b/awx/ui_next/src/screens/Inventory/Inventories.jsx index 0df1e9370d..d7361690f6 100644 --- a/awx/ui_next/src/screens/Inventory/Inventories.jsx +++ b/awx/ui_next/src/screens/Inventory/Inventories.jsx @@ -64,24 +64,25 @@ class Inventories extends Component { [`/inventories/${inventoryKind}/${inventory.id}/hosts/add`]: i18n._( t`Create New Host` ), + [`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource && + nestedResource.id}`]: i18n._( + t`${nestedResource && nestedResource.name}` + ), [`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource && nestedResource.id}/edit`]: i18n._(t`Edit Details`), [`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource && nestedResource.id}/details`]: i18n._(t`Host Details`), [`/inventories/${inventoryKind}/${inventory.id}/hosts/${nestedResource && - nestedResource.id}`]: i18n._( - t`${nestedResource && nestedResource.name}` - ), - + nestedResource.id}/completed_jobs`]: i18n._(t`Completed Jobs`), [`/inventories/${inventoryKind}/${inventory.id}/groups/add`]: i18n._( t`Create New Group` ), + [`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource && + nestedResource.id}`]: `${nestedResource && nestedResource.name}`, [`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource && nestedResource.id}/edit`]: i18n._(t`Edit Details`), [`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource && nestedResource.id}/details`]: i18n._(t`Group Details`), - [`/inventories/${inventoryKind}/${inventory.id}/groups/${nestedResource && - nestedResource.id}`]: `${nestedResource && nestedResource.name}`, }; this.setState({ breadcrumbConfig }); }; diff --git a/awx/ui_next/src/screens/Inventory/Inventory.jsx b/awx/ui_next/src/screens/Inventory/Inventory.jsx index e28ffedc02..4056a418b2 100644 --- a/awx/ui_next/src/screens/Inventory/Inventory.jsx +++ b/awx/ui_next/src/screens/Inventory/Inventory.jsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { t } from '@lingui/macro'; import { withI18n } from '@lingui/react'; -import { Card, CardActions, PageSection } from '@patternfly/react-core'; import { Switch, Route, @@ -10,20 +9,21 @@ import { useLocation, useRouteMatch, } from 'react-router-dom'; + +import { Card, CardActions, PageSection } from '@patternfly/react-core'; import { TabbedCardHeader } from '@components/Card'; import CardCloseButton from '@components/CardCloseButton'; import ContentError from '@components/ContentError'; +import ContentLoading from '@components/ContentLoading'; +import JobList from '@components/JobList'; import RoutedTabs from '@components/RoutedTabs'; import { ResourceAccessList } from '@components/ResourceAccessList'; -import ContentLoading from '@components/ContentLoading'; import InventoryDetail from './InventoryDetail'; - +import InventoryEdit from './InventoryEdit'; import InventoryGroups from './InventoryGroups'; -import InventoryCompletedJobs from './InventoryCompletedJobs'; +import InventoryHosts from './InventoryHosts/InventoryHosts'; import InventorySources from './InventorySources'; import { InventoriesAPI } from '@api'; -import InventoryEdit from './InventoryEdit'; -import InventoryHosts from './InventoryHosts/InventoryHosts'; function Inventory({ i18n, setBreadcrumb }) { const [contentError, setContentError] = useState(null); @@ -172,8 +172,17 @@ function Inventory({ i18n, setBreadcrumb }) { } - />, + > + + , Coming soon :); - } -} - -export default InventoryCompletedJobs; diff --git a/awx/ui_next/src/screens/Inventory/InventoryCompletedJobs/index.js b/awx/ui_next/src/screens/Inventory/InventoryCompletedJobs/index.js deleted file mode 100644 index 17e7b7be0b..0000000000 --- a/awx/ui_next/src/screens/Inventory/InventoryCompletedJobs/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './InventoryCompletedJobs'; diff --git a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx index 50907083d1..2a6a1c8ca3 100644 --- a/awx/ui_next/src/screens/Inventory/SmartInventory.jsx +++ b/awx/ui_next/src/screens/Inventory/SmartInventory.jsx @@ -6,11 +6,11 @@ import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom'; import { TabbedCardHeader } from '@components/Card'; import CardCloseButton from '@components/CardCloseButton'; import ContentError from '@components/ContentError'; +import JobList from '@components/JobList'; import RoutedTabs from '@components/RoutedTabs'; import { ResourceAccessList } from '@components/ResourceAccessList'; import SmartInventoryDetail from './SmartInventoryDetail'; import SmartInventoryHosts from './SmartInventoryHosts'; -import SmartInventoryCompletedJobs from './SmartInventoryCompletedJobs'; import { InventoriesAPI } from '@api'; import SmartInventoryEdit from './SmartInventoryEdit'; @@ -149,10 +149,17 @@ class SmartInventory extends Component { ( - - )} - />, + > + + , Coming soon :); - } -} - -export default SmartInventoryCompletedJobs; diff --git a/awx/ui_next/src/screens/Inventory/SmartInventoryCompletedJobs/index.js b/awx/ui_next/src/screens/Inventory/SmartInventoryCompletedJobs/index.js deleted file mode 100644 index 9c7ec9bc49..0000000000 --- a/awx/ui_next/src/screens/Inventory/SmartInventoryCompletedJobs/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SmartInventoryCompletedJobs'; diff --git a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx b/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx deleted file mode 100644 index 28649f1edc..0000000000 --- a/awx/ui_next/src/screens/Job/JobList/JobListItem.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { Component } from 'react'; -import { Link } from 'react-router-dom'; -import { withI18n } from '@lingui/react'; -import { t } from '@lingui/macro'; -import { - Button, - DataListAction, - DataListCell, - DataListCheck, - DataListItem, - DataListItemRow, - DataListItemCells, - Tooltip, -} from '@patternfly/react-core'; -import { RocketIcon } from '@patternfly/react-icons'; -import LaunchButton from '@components/LaunchButton'; -import StatusIcon from '@components/StatusIcon'; -import { toTitleCase } from '@util/strings'; -import { formatDateString } from '@util/dates'; -import { JOB_TYPE_URL_SEGMENTS } from '@constants'; - -class JobListItem extends Component { - render() { - const { i18n, job, isSelected, onSelect } = this.props; - const labelId = `check-action-${job.id}`; - - return ( - - - - - {job.status && } - , - - - - - {job.id} — {job.name} - - - - , - {toTitleCase(job.type)}, - - {formatDateString(job.finished)} - , - ]} - /> - {job.type !== 'system_job' && - job.summary_fields?.user_capabilities?.start && ( - - - - {({ handleRelaunch }) => ( - - )} - - - - )} - - - ); - } -} -export { JobListItem as _JobListItem }; -export default withI18n()(JobListItem); diff --git a/awx/ui_next/src/screens/Job/JobList/JobListItem.test.jsx b/awx/ui_next/src/screens/Job/JobList/JobListItem.test.jsx deleted file mode 100644 index d29514e9d9..0000000000 --- a/awx/ui_next/src/screens/Job/JobList/JobListItem.test.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react'; -import { createMemoryHistory } from 'history'; - -import { mountWithContexts } from '@testUtils/enzymeHelpers'; - -import JobListItem from './JobListItem'; - -describe('', () => { - test('initially renders succesfully', () => { - const history = createMemoryHistory({ - initialEntries: ['/jobs'], - }); - mountWithContexts( - {}} - />, - { context: { router: { history } } } - ); - }); -}); diff --git a/awx/ui_next/src/screens/Job/JobList/index.js b/awx/ui_next/src/screens/Job/JobList/index.js deleted file mode 100644 index 5b0caebb8a..0000000000 --- a/awx/ui_next/src/screens/Job/JobList/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { default as JobList } from './JobList'; -export { default as JobListItem } from './JobListItem'; diff --git a/awx/ui_next/src/screens/Job/Jobs.jsx b/awx/ui_next/src/screens/Job/Jobs.jsx index 7c710614d8..b5008c3423 100644 --- a/awx/ui_next/src/screens/Job/Jobs.jsx +++ b/awx/ui_next/src/screens/Job/Jobs.jsx @@ -1,96 +1,89 @@ -import React, { Component, Fragment } from 'react'; -import { Route, withRouter, Switch } from 'react-router-dom'; +import React, { useState, useCallback } from 'react'; +import { + Route, + Switch, + useHistory, + useLocation, + useRouteMatch, +} from 'react-router-dom'; import { withI18n } from '@lingui/react'; import { t } from '@lingui/macro'; +import { PageSection } from '@patternfly/react-core'; import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs'; import Job from './Job'; import JobTypeRedirect from './JobTypeRedirect'; -import JobList from './JobList/JobList'; +import JobList from '@components/JobList'; import { JOB_TYPE_URL_SEGMENTS } from '@constants'; -class Jobs extends Component { - constructor(props) { - super(props); +function Jobs({ i18n }) { + const history = useHistory(); + const location = useLocation(); + const match = useRouteMatch(); + const [breadcrumbConfig, setBreadcrumbConfig] = useState({ + '/jobs': i18n._(t`Jobs`), + }); - const { i18n } = props; + const buildBreadcrumbConfig = useCallback( + job => { + if (!job) { + return; + } - this.state = { - breadcrumbConfig: { + const type = JOB_TYPE_URL_SEGMENTS[job.type]; + setBreadcrumbConfig({ '/jobs': i18n._(t`Jobs`), - }, - }; - } + [`/jobs/${type}/${job.id}`]: `${job.name}`, + [`/jobs/${type}/${job.id}/output`]: i18n._(t`Output`), + [`/jobs/${type}/${job.id}/details`]: i18n._(t`Details`), + }); + }, + [i18n] + ); - setBreadcrumbConfig = job => { - const { i18n } = this.props; - - if (!job) { - return; - } - - const type = JOB_TYPE_URL_SEGMENTS[job.type]; - const breadcrumbConfig = { - '/jobs': i18n._(t`Jobs`), - [`/jobs/${type}/${job.id}`]: `${job.name}`, - [`/jobs/${type}/${job.id}/output`]: i18n._(t`Output`), - [`/jobs/${type}/${job.id}/details`]: i18n._(t`Details`), - }; - - this.setState({ breadcrumbConfig }); - }; - - render() { - const { match, history, location } = this.props; - const { breadcrumbConfig } = this.state; - - return ( - - - - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - ( - - )} - /> - - - ); - } + return ( + <> + + + + + + + + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + ); } export { Jobs as _Jobs }; -export default withI18n()(withRouter(Jobs)); +export default withI18n()(Jobs); diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx index c8863edb1e..b46365aae4 100644 --- a/awx/ui_next/src/screens/Template/Template.jsx +++ b/awx/ui_next/src/screens/Template/Template.jsx @@ -3,15 +3,17 @@ import { t } from '@lingui/macro'; import { withI18n } from '@lingui/react'; import { Card, CardActions, PageSection } from '@patternfly/react-core'; import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom'; + import { TabbedCardHeader } from '@components/Card'; import CardCloseButton from '@components/CardCloseButton'; import ContentError from '@components/ContentError'; +import JobList from '@components/JobList'; import NotificationList from '@components/NotificationList'; import RoutedTabs from '@components/RoutedTabs'; import { ResourceAccessList } from '@components/ResourceAccessList'; import JobTemplateDetail from './JobTemplateDetail'; -import { JobTemplatesAPI, OrganizationsAPI } from '@api'; import JobTemplateEdit from './JobTemplateEdit'; +import { JobTemplatesAPI, OrganizationsAPI } from '@api'; class Template extends Component { constructor(props) { @@ -109,7 +111,7 @@ class Template extends Component { }, { name: i18n._(t`Completed Jobs`), - link: '/home', + link: `${match.url}/completed_jobs`, }, { name: i18n._(t`Survey`), @@ -203,6 +205,11 @@ class Template extends Component { )} /> )} + {template?.id && ( + + + + )} { @@ -151,6 +153,15 @@ class WorkflowJobTemplate extends Component { )} /> )} + {template?.id && ( + + + + )}