From 0b633c2b6c564810a4c95fea4c18978fd3d55605 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Fri, 22 Jan 2021 12:50:23 -0800 Subject: [PATCH] convert project list to PaginatedTable --- .../Project/ProjectList/ProjectList.jsx | 36 +- .../Project/ProjectList/ProjectListItem.jsx | 203 ++++----- .../ProjectList/ProjectListItem.test.jsx | 390 ++++++++++-------- 3 files changed, 319 insertions(+), 310 deletions(-) diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index a1f6d36f41..acb473a34d 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -9,10 +9,14 @@ import useRequest, { useDeleteItems } from '../../../util/useRequest'; import AlertModal from '../../../components/AlertModal'; import DataListToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; -import PaginatedDataList, { +import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; import useWsProjects from './useWsProjects'; import { getQSConfig, parseQueryString } from '../../../util/qs'; @@ -116,7 +120,7 @@ function ProjectList({ i18n }) { - + {i18n._(t`Name`)} + {i18n._(t`Status`)} + {i18n._(t`Type`)} + {i18n._(t`Revision`)} + {i18n._(t`Actions`)} + + } renderToolbar={props => ( )} - renderItem={o => ( + renderRow={(project, index) => ( row.id === o.id)} - onSelect={() => handleSelect(o)} + key={project.id} + project={project} + detailUrl={`${match.url}/${project.id}`} + isSelected={selected.some(row => row.id === project.id)} + onSelect={() => handleSelect(project)} + rowIndex={index} /> )} emptyStateControls={ diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 1382df00ca..88c72e7f89 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -2,37 +2,22 @@ import 'styled-components/macro'; import React, { Fragment, useState, useCallback } from 'react'; import { string, bool, func } from 'prop-types'; import { withI18n } from '@lingui/react'; -import { - Button, - DataListAction as _DataListAction, - DataListCheck, - DataListItem, - DataListItemRow, - DataListItemCells, - Tooltip, -} from '@patternfly/react-core'; - +import { Button, Tooltip } from '@patternfly/react-core'; +import { Tr, Td } from '@patternfly/react-table'; import { t } from '@lingui/macro'; import { Link } from 'react-router-dom'; import { PencilAltIcon } from '@patternfly/react-icons'; import styled from 'styled-components'; +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; import { formatDateString, timeOfDay } from '../../../util/dates'; import { ProjectsAPI } from '../../../api'; import ClipboardCopyButton from '../../../components/ClipboardCopyButton'; -import StatusIcon from '../../../components/StatusIcon'; -import DataListCell from '../../../components/DataListCell'; +import StatusLabel from '../../../components/StatusLabel'; import { toTitleCase } from '../../../util/strings'; import CopyButton from '../../../components/CopyButton'; import ProjectSyncButton from '../shared/ProjectSyncButton'; import { Project } from '../../../types'; -const DataListAction = styled(_DataListAction)` - align-items: center; - display: grid; - grid-gap: 16px; - grid-template-columns: repeat(3, 40px); -`; - const Label = styled.span` color: var(--pf-global--disabled-color--100); `; @@ -42,8 +27,9 @@ function ProjectListItem({ isSelected, onSelect, detailUrl, - i18n, fetchProjects, + rowIndex, + i18n, }) { const [isDisabled, setIsDisabled] = useState(false); ProjectListItem.propTypes = { @@ -88,106 +74,89 @@ function ProjectListItem({ }, []); const labelId = `check-action-${project.id}`; + return ( - - - + + + + {project.name} + + + + {project.summary_fields.last_job && ( + + + + + + )} + + + {project.scm_type === '' + ? i18n._(t`Manual`) + : toTitleCase(project.scm_type)} + + + {project.scm_revision.substring(0, 7)} + {!project.scm_revision && ( + + )} + - - {project.summary_fields.last_job && ( - - - - - - )} - , - - - {project.name} - - , - - {project.scm_type === '' - ? i18n._(t`Manual`) - : toTitleCase(project.scm_type)} - , - - {project.scm_revision.substring(0, 7)} - {!project.scm_revision && ( - - )} - - , - ]} - /> - + + - {project.summary_fields.user_capabilities.start && ( - - - - )} - {project.summary_fields.user_capabilities.edit ? ( - - - - ) : ( - '' - )} - {project.summary_fields.user_capabilities.copy && ( - - )} - - - + + + + + + + + + + ); } export default withI18n()(ProjectListItem); diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx index 7866015703..21f96efc4d 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.test.jsx @@ -10,112 +10,128 @@ jest.mock('../../../api/models/Projects'); describe('', () => { test('launch button shown to users with start capabilities', () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - start: true, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + start: true, + }, + }, + }} + /> + +
); expect(wrapper.find('ProjectSyncButton').exists()).toBeTruthy(); }); test('launch button hidden from users without start capabilities', () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - start: false, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + start: false, + }, + }, + }} + /> + +
); expect(wrapper.find('ProjectSyncButton').exists()).toBeFalsy(); }); test('edit button shown to users with edit capabilities', () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: true, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: true, + }, + }, + }} + /> + +
); expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); }); test('edit button hidden from users without edit capabilities', () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: false, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: false, + }, + }, + }} + /> + +
); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); }); @@ -123,29 +139,33 @@ describe('', () => { test('should call api to copy project', async () => { ProjectsAPI.copy.mockResolvedValue(); const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: false, - copy: true, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: false, + copy: true, + }, + }, + }} + /> + +
); await act(async () => @@ -159,29 +179,33 @@ describe('', () => { ProjectsAPI.copy.mockRejectedValue(new Error()); const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: false, - copy: true, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: false, + copy: true, + }, + }, + }} + /> + +
); await act(async () => wrapper.find('Button[aria-label="Copy"]').prop('onClick')() @@ -192,56 +216,64 @@ describe('', () => { }); test('should not render copy button', async () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: false, - copy: false, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '7788f7erga0jijodfgsjisiodf98sdga9hg9a98gaf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: false, + copy: false, + }, + }, + }} + /> + +
); expect(wrapper.find('CopyButton').length).toBe(0); }); test('should render disabled copy to clipboard button', () => { const wrapper = mountWithContexts( - {}} - project={{ - id: 1, - name: 'Project 1', - url: '/api/v2/projects/1', - type: 'project', - scm_type: 'git', - scm_revision: '', - summary_fields: { - last_job: { - id: 9000, - status: 'successful', - }, - user_capabilities: { - edit: true, - }, - }, - }} - /> + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: '', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: true, + }, + }, + }} + /> + +
); expect( wrapper.find('span[aria-label="copy to clipboard disabled"]').text()