From d8452e1259347cd9efeb3ae34e936bc3106f24b5 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Tue, 18 Jun 2019 13:44:26 -0400 Subject: [PATCH] job list skeleton --- src/api/index.js | 3 + src/api/models/UnifiedJobs.js | 10 ++ src/screens/Job/Job.jsx | 11 +- src/screens/Job/JobList/JobList.jsx | 182 ++++++++++++++++++++++++ src/screens/Job/JobList/JobListItem.jsx | 62 ++++++++ src/screens/Job/JobList/index.js | 3 + src/screens/Job/Jobs.jsx | 15 +- 7 files changed, 280 insertions(+), 6 deletions(-) create mode 100644 src/api/models/UnifiedJobs.js create mode 100644 src/screens/Job/JobList/JobList.jsx create mode 100644 src/screens/Job/JobList/JobListItem.jsx create mode 100644 src/screens/Job/JobList/index.js diff --git a/src/api/index.js b/src/api/index.js index 57429507bb..c87e753f2b 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -7,6 +7,7 @@ import Organizations from './models/Organizations'; import Root from './models/Root'; import Teams from './models/Teams'; import UnifiedJobTemplates from './models/UnifiedJobTemplates'; +import UnifiedJobs from './models/UnifiedJobs'; import Users from './models/Users'; import WorkflowJobTemplates from './models/WorkflowJobTemplates'; @@ -19,6 +20,7 @@ const OrganizationsAPI = new Organizations(); const RootAPI = new Root(); const TeamsAPI = new Teams(); const UnifiedJobTemplatesAPI = new UnifiedJobTemplates(); +const UnifiedJobsAPI = new UnifiedJobs(); const UsersAPI = new Users(); const WorkflowJobTemplatesAPI = new WorkflowJobTemplates(); @@ -32,6 +34,7 @@ export { RootAPI, TeamsAPI, UnifiedJobTemplatesAPI, + UnifiedJobsAPI, UsersAPI, WorkflowJobTemplatesAPI }; diff --git a/src/api/models/UnifiedJobs.js b/src/api/models/UnifiedJobs.js new file mode 100644 index 0000000000..0ad142201c --- /dev/null +++ b/src/api/models/UnifiedJobs.js @@ -0,0 +1,10 @@ +import Base from '../Base'; + +class UnifiedJobs extends Base { + constructor (http) { + super(http); + this.baseUrl = '/api/v2/unified_jobs/'; + } +} + +export default UnifiedJobs; diff --git a/src/screens/Job/Job.jsx b/src/screens/Job/Job.jsx index ea3af16373..a64be2a894 100644 --- a/src/screens/Job/Job.jsx +++ b/src/screens/Job/Job.jsx @@ -13,7 +13,7 @@ import RoutedTabs from '@components/RoutedTabs'; import JobDetail from './JobDetail'; import JobOutput from './JobOutput'; -export class Job extends Component { +class Job extends Component { constructor (props) { super(props); @@ -24,22 +24,22 @@ export class Job extends Component { isInitialized: false }; - this.fetchJob = this.fetchJob.bind(this); + this.loadJob = this.loadJob.bind(this); } async componentDidMount () { - await this.fetchJob(); + await this.loadJob(); this.setState({ isInitialized: true }); } async componentDidUpdate (prevProps) { const { location } = this.props; if (location !== prevProps.location) { - await this.fetchJob(); + await this.loadJob(); } } - async fetchJob () { + async loadJob () { const { match, setBreadcrumb, @@ -153,3 +153,4 @@ export class Job extends Component { } export default withI18n()(withRouter(Job)); +export { Job as _Job }; diff --git a/src/screens/Job/JobList/JobList.jsx b/src/screens/Job/JobList/JobList.jsx new file mode 100644 index 0000000000..598546d178 --- /dev/null +++ b/src/screens/Job/JobList/JobList.jsx @@ -0,0 +1,182 @@ +import React, { Component } from 'react'; +import { withRouter } from 'react-router-dom'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; +import { + Card, + PageSection, + PageSectionVariants, +} from '@patternfly/react-core'; + +import { UnifiedJobsAPI } from '@api'; +import AlertModal from '@components/AlertModal'; +import DatalistToolbar from '@components/DataListToolbar'; +import PaginatedDataList, { + ToolbarDeleteButton +} from '@components/PaginatedDataList'; +import { getQSConfig, parseNamespacedQueryString } from '@util/qs'; + +import JobListItem from './JobListItem'; + +const QS_CONFIG = getQSConfig('job', { + page: 1, + page_size: 20, + order_by: '-finished', + not__launch_type: 'sync', +}); + +class JobsList extends Component { + constructor (props) { + super(props); + + this.state = { + hasContentLoading: true, + hasContentError: false, + deletionError: false, + selected: [], + jobs: [], + itemCount: 0, + }; + this.loadJobs = this.loadJobs.bind(this); + this.handleSelectAll = this.handleSelectAll.bind(this); + this.handleSelect = this.handleSelect.bind(this); + this.handleDelete = this.handleDelete.bind(this); + this.handleDeleteErrorClose = this.handleDeleteErrorClose.bind(this); + } + + componentDidMount () { + this.loadJobs(); + } + + componentDidUpdate (prevProps) { + const { location } = this.props; + if (location !== prevProps.location) { + this.loadJobs(); + } + } + + handleDeleteErrorClose () { + this.setState({ deletionError: false }); + } + + handleSelectAll (isSelected) { + const { jobs } = this.state; + const selected = isSelected ? [...jobs] : []; + this.setState({ selected }); + } + + handleSelect (item) { + const { selected } = this.state; + if (selected.some(s => s.id === item.id)) { + this.setState({ selected: selected.filter(s => s.id !== item.id) }); + } else { + this.setState({ selected: selected.concat(item) }); + } + } + + async handleDelete () { + const { selected } = this.state; + this.setState({ hasContentLoading: true, deletionError: false }); + try { + await Promise.all(selected.map(({ id }) => UnifiedJobsAPI.destroy(id))); + } catch (err) { + this.setState({ deletionError: true }); + } finally { + await this.loadJobs(); + } + } + + async loadJobs () { + const { location } = this.props; + const params = parseNamespacedQueryString(QS_CONFIG, location.search); + + this.setState({ hasContentError: false, hasContentLoading: true }); + try { + const { data: { count, results } } = await UnifiedJobsAPI.read(params); + this.setState({ + itemCount: count, + jobs: results, + selected: [], + }); + } catch (err) { + this.setState({ hasContentError: true }); + } finally { + this.setState({ hasContentLoading: false }); + } + } + + render () { + const { + hasContentError, + hasContentLoading, + deletionError, + jobs, + itemCount, + selected, + } = this.state; + const { + match, + i18n + } = this.props; + const { medium } = PageSectionVariants; + const isAllSelected = selected.length === jobs.length; + const itemName = i18n._(t`Job`); + return ( + + + ( + + ]} + /> + )} + renderItem={(job) => ( + this.handleSelect(job)} + isSelected={selected.some(row => row.id === job.id)} + /> + )} + /> + + + {i18n._(t`Failed to delete one or more jobs.`)} + + + ); + } +} + +export { JobsList as _JobsList }; +export default withI18n()(withRouter(JobsList)); diff --git a/src/screens/Job/JobList/JobListItem.jsx b/src/screens/Job/JobList/JobListItem.jsx new file mode 100644 index 0000000000..3ec61565ce --- /dev/null +++ b/src/screens/Job/JobList/JobListItem.jsx @@ -0,0 +1,62 @@ +import React, { Component } from 'react'; +import { Link } from 'react-router-dom'; +import { + DataListItem, + DataListItemRow, + DataListItemCells, + DataListCheck, + DataListCell as PFDataListCell, +} from '@patternfly/react-core'; +import styled from 'styled-components'; + +import VerticalSeparator from '@components/VerticalSeparator'; +import { toTitleCase } from '@util/strings'; + +const DataListCell = styled(PFDataListCell)` + display: flex; + align-items: center; + @media screen and (min-width: 768px) { + padding-bottom: 0; + } +`; + +class JobListItem extends Component { + render () { + const { + job, + isSelected, + onSelect, + } = this.props; + + return ( + + + + + + + + {job.name} + + + , + {toTitleCase(job.type)}, + {job.finished}, + ]} + /> + + + ); + } +} +export { JobListItem as _JobListItem }; +export default JobListItem; diff --git a/src/screens/Job/JobList/index.js b/src/screens/Job/JobList/index.js new file mode 100644 index 0000000000..cf71a63a21 --- /dev/null +++ b/src/screens/Job/JobList/index.js @@ -0,0 +1,3 @@ +export { default as JobList } from './JobList'; +export { default as JobListItem } from './JobListItem'; + diff --git a/src/screens/Job/Jobs.jsx b/src/screens/Job/Jobs.jsx index f7a61b82c0..6f4a1208a8 100644 --- a/src/screens/Job/Jobs.jsx +++ b/src/screens/Job/Jobs.jsx @@ -5,7 +5,8 @@ import { t } from '@lingui/macro'; import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs'; -import { Job } from '.'; +import Job from './Job'; +import JobList from './JobList/JobList'; class Jobs extends Component { constructor (props) { @@ -47,6 +48,17 @@ class Jobs extends Component { breadcrumbConfig={breadcrumbConfig} /> + ( + + )} + /> ( @@ -63,4 +75,5 @@ class Jobs extends Component { } } +export { Jobs as _Jobs }; export default withI18n()(withRouter(Jobs));