Skip to content

Commit

Permalink
Merge pull request #6058 from marshmalien/5890-jt-completed-jobs-list
Browse files Browse the repository at this point in the history
Add Completed Job list tab to multiple resources

Reviewed-by: Alex Corey <Alex.swansboro@gmail.com>
             https://github.com/AlexSCorey
  • Loading branch information
softwarefactory-project-zuul[bot] committed Feb 27, 2020
2 parents 258689a + 37a33f9 commit 5ca73f1
Show file tree
Hide file tree
Showing 22 changed files with 336 additions and 278 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ 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 {
AdHocCommandsAPI,
InventoryUpdatesAPI,
JobsAPI,
ProjectUpdatesAPI,
SystemJobsAPI,
UnifiedJobsAPI,
WorkflowJobsAPI,
} from '@api';
import { Card } from '@patternfly/react-core';
import AlertModal from '@components/AlertModal';
import DatalistToolbar from '@components/DataListToolbar';
import ErrorDetail from '@components/ErrorDetail';
Expand All @@ -21,21 +12,28 @@ import PaginatedDataList, {
} from '@components/PaginatedDataList';
import useRequest, { useDeleteItems } from '@util/useRequest';
import { getQSConfig, parseQueryString } from '@util/qs';

import JobListItem from './JobListItem';
import {
AdHocCommandsAPI,
InventoryUpdatesAPI,
JobsAPI,
ProjectUpdatesAPI,
SystemJobsAPI,
UnifiedJobsAPI,
WorkflowJobsAPI,
} from '@api';

const QS_CONFIG = getQSConfig(
'job',
{
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();

Expand All @@ -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,
Expand Down Expand Up @@ -119,7 +119,7 @@ function JobList({ i18n }) {
};

return (
<PageSection>
<>
<Card>
<PaginatedDataList
contentError={contentError}
Expand Down Expand Up @@ -225,9 +225,8 @@ function JobList({ i18n }) {
renderItem={job => (
<JobListItem
key={job.id}
value={job.name}
job={job}
detailUrl={`${location.pathname}/${job}/${job.id}`}
showTypeColumn={showTypeColumn}
onSelect={() => handleSelect(job)}
isSelected={selected.some(row => row.id === job.id)}
/>
Expand All @@ -243,7 +242,7 @@ function JobList({ i18n }) {
{i18n._(t`Failed to delete one or more jobs.`)}
<ErrorDetail error={deletionError} />
</AlertModal>
</PageSection>
</>
);
}

Expand Down
90 changes: 90 additions & 0 deletions awx/ui_next/src/components/JobList/JobListItem.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<DataListItem aria-labelledby={labelId} id={`${job.id}`}>
<DataListItemRow>
<DataListCheck
id={`select-job-${job.id}`}
checked={isSelected}
onChange={onSelect}
aria-labelledby={labelId}
/>
<DataListItemCells
dataListCells={[
<DataListCell key="status" isFilled={false}>
{job.status && <StatusIcon status={job.status} />}
</DataListCell>,
<DataListCell key="name">
<span>
<Link to={`/jobs/${JOB_TYPE_URL_SEGMENTS[job.type]}/${job.id}`}>
<b>
{job.id} &mdash; {job.name}
</b>
</Link>
</span>
</DataListCell>,
...(showTypeColumn
? [
<DataListCell key="type" aria-label="type">
{toTitleCase(job.type)}
</DataListCell>,
]
: []),
<DataListCell key="finished">
{formatDateString(job.finished)}
</DataListCell>,
]}
/>
{job.type !== 'system_job' &&
job.summary_fields?.user_capabilities?.start && (
<DataListAction
aria-label="actions"
aria-labelledby={labelId}
id={labelId}
>
<Tooltip content={i18n._(t`Relaunch Job`)} position="top">
<LaunchButton resource={job}>
{({ handleRelaunch }) => (
<Button variant="plain" onClick={handleRelaunch}>
<RocketIcon />
</Button>
)}
</LaunchButton>
</Tooltip>
</DataListAction>
)}
</DataListItemRow>
</DataListItem>
);
}

export { JobListItem as _JobListItem };
export default withI18n()(JobListItem);
82 changes: 82 additions & 0 deletions awx/ui_next/src/components/JobList/JobListItem.test.jsx
Original file line number Diff line number Diff line change
@@ -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('<JobListItem />', () => {
let wrapper;

beforeEach(() => {
const history = createMemoryHistory({
initialEntries: ['/jobs'],
});
wrapper = mountWithContexts(
<JobListItem job={mockJob} isSelected onSelect={() => {}} />,
{ 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(
<JobListItem
job={{
...mockJob,
summary_fields: { user_capabilities: { start: false } },
}}
detailUrl={`/jobs/playbook/${mockJob.id}`}
onSelect={() => {}}
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(
<JobListItem
job={mockJob}
showTypeColumn
isSelected
onSelect={() => {}}
/>
);
expect(wrapper.find('DataListCell[aria-label="type"]').length).toBe(1);
});
});
1 change: 1 addition & 0 deletions awx/ui_next/src/components/JobList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './JobList';
14 changes: 9 additions & 5 deletions awx/ui_next/src/screens/Host/Host.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand Down Expand Up @@ -181,11 +181,15 @@ function Host({ inventory, i18n, setBreadcrumb }) {
render={() => <HostGroups host={host} />}
/>
)}
{host && (
{host?.id && (
<Route
path="/hosts/:id/completed_jobs"
render={() => <HostCompletedJobs host={host} />}
/>
path={[
'/hosts/:id/completed_jobs',
'/inventories/inventory/:id/hosts/:hostId/completed_jobs',
]}
>
<JobList defaultParams={{ job__hosts: host.id }} />
</Route>
)}
<Route
key="not-found"
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion awx/ui_next/src/screens/Host/HostCompletedJobs/index.js

This file was deleted.

13 changes: 7 additions & 6 deletions awx/ui_next/src/screens/Inventory/Inventories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
};
Expand Down
Loading

0 comments on commit 5ca73f1

Please sign in to comment.