From f3410f651775517a80f8b259c2339d4574d0176f Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 18 Jan 2021 10:22:53 -0800 Subject: [PATCH 1/7] convert TemplateList to tables --- .../Template/TemplateList/TemplateList.jsx | 22 +- .../TemplateList/TemplateListItem.jsx | 203 ++++++++---------- 2 files changed, 105 insertions(+), 120 deletions(-) diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx index 68efedd2de..82a2dffcf4 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx @@ -11,9 +11,11 @@ import { import AlertModal from '../../../components/AlertModal'; import DatalistToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; -import PaginatedDataList, { - ToolbarDeleteButton, -} from '../../../components/PaginatedDataList'; +import { ToolbarDeleteButton } from '../../../components/PaginatedDataList'; +import PaginatedTable, { + HeaderRow, + HeaderCell, +} from '../../../components/PaginatedTable'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; import { getQSConfig, parseQueryString } from '../../../util/qs'; import useWsTemplates from '../../../util/useWsTemplates'; @@ -167,7 +169,7 @@ function TemplateList({ i18n }) { return ( - + {i18n._(t`Name`)} + {i18n._(t`Type`)} + + {i18n._(t`Recent Jobs`)} + + + } renderToolbar={props => ( )} - renderItem={template => ( + renderRow={(template, index) => ( handleSelect(template)} isSelected={selected.some(row => row.id === template.id)} fetchTemplates={fetchTemplates} + rowIndex={index} /> )} emptyStateControls={(canAddJT || canAddWFJT) && addButton} diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index 057178f656..6481f7072d 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -1,15 +1,8 @@ import 'styled-components/macro'; import React, { useState, useCallback } from 'react'; import { Link } from 'react-router-dom'; -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 { withI18n } from '@lingui/react'; import { @@ -18,9 +11,7 @@ import { ProjectDiagramIcon, RocketIcon, } from '@patternfly/react-icons'; -import styled from 'styled-components'; -import DataListCell from '../../../components/DataListCell'; - +import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; import { timeOfDay } from '../../../util/dates'; import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../../api'; @@ -29,13 +20,6 @@ import Sparkline from '../../../components/Sparkline'; import { toTitleCase } from '../../../util/strings'; import CopyButton from '../../../components/CopyButton'; -const DataListAction = styled(_DataListAction)` - align-items: center; - display: grid; - grid-gap: 16px; - grid-template-columns: repeat(4, 40px); -`; - function TemplateListItem({ i18n, template, @@ -43,6 +27,7 @@ function TemplateListItem({ onSelect, detailUrl, fetchTemplates, + rowIndex, }) { const [isDisabled, setIsDisabled] = useState(false); const labelId = `check-action-${template.id}`; @@ -74,108 +59,96 @@ function TemplateListItem({ (!template.summary_fields.inventory && !template.ask_inventory_on_launch)); return ( - - - - - - - {template.name} - - - {missingResourceIcon && ( - - - - - - )} - , - - {toTitleCase(template.type)} - , - - - , - ]} - /> - + + + + {template.name} + + {missingResourceIcon && ( + + + + + + )} + + {toTitleCase(template.type)} + + + + + - {template.type === 'workflow_job_template' && ( - + + + + + {({ handleLaunch }) => ( - - )} - {template.summary_fields.user_capabilities.start && ( - - - {({ handleLaunch }) => ( - - )} - - - )} - {template.summary_fields.user_capabilities.edit && ( - - - - )} - {template.summary_fields.user_capabilities.copy && ( - - )} - - - + )} + + + + + + + + + + ); } From ad71dc3e98789c9fea34fd7f5221012ce2323a0e Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 19 Jan 2021 15:00:41 -0800 Subject: [PATCH 2/7] add expandable row details to template list --- .../Template/TemplateList/TemplateList.jsx | 30 +- .../TemplateList/TemplateListItem.jsx | 295 ++++++++++++------ 2 files changed, 204 insertions(+), 121 deletions(-) diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx index 82a2dffcf4..1380291a26 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx @@ -208,40 +208,14 @@ function TemplateList({ i18n }) { key: 'modified_by__username__icontains', }, ]} - toolbarSortColumns={[ - { - name: i18n._(t`Inventory`), - key: 'job_template__inventory__id', - }, - { - name: i18n._(t`Last Job Run`), - key: 'last_job_run', - }, - { - name: i18n._(t`Modified`), - key: 'modified', - }, - { - name: i18n._(t`Name`), - key: 'name', - }, - { - name: i18n._(t`Project`), - key: 'jobtemplate__project__id', - }, - { - name: i18n._(t`Type`), - key: 'type', - }, - ]} toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys} headerRow={ - + {i18n._(t`Name`)} {i18n._(t`Type`)} - {i18n._(t`Recent Jobs`)} + {i18n._(t`Last Run`)} } diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index 6481f7072d..29f94f78e6 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -1,8 +1,8 @@ import 'styled-components/macro'; import React, { useState, useCallback } from 'react'; import { Link } from 'react-router-dom'; -import { Button, Tooltip } from '@patternfly/react-core'; -import { Tr, Td } from '@patternfly/react-table'; +import { Button, Tooltip, Chip } from '@patternfly/react-core'; +import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; import { t } from '@lingui/macro'; import { withI18n } from '@lingui/react'; import { @@ -12,6 +12,13 @@ import { RocketIcon, } from '@patternfly/react-icons'; import { ActionsTd, ActionItem } from '../../../components/PaginatedTable'; +import { + DetailList, + Detail, + DeletedDetail, +} from '../../../components/DetailList'; +import ChipGroup from '../../../components/ChipGroup'; +import CredentialChip from '../../../components/CredentialChip'; import { timeOfDay } from '../../../util/dates'; import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../../api'; @@ -29,6 +36,7 @@ function TemplateListItem({ fetchTemplates, rowIndex, }) { + const [isExpanded, setIsExpanded] = useState(false); const [isDisabled, setIsDisabled] = useState(false); const labelId = `check-action-${template.id}`; @@ -53,102 +61,203 @@ function TemplateListItem({ setIsDisabled(false); }, []); + const { + summary_fields: summaryFields, + ask_inventory_on_launch: askInventoryOnLaunch, + } = template; + const missingResourceIcon = template.type === 'job_template' && - (!template.summary_fields.project || - (!template.summary_fields.inventory && - !template.ask_inventory_on_launch)); - return ( - - - - - {template.name} + (!summaryFields.project || + (!summaryFields.inventory && !askInventoryOnLaunch)); + + const inventoryValue = (kind, id) => { + const inventorykind = kind === 'smart' ? 'smart_inventory' : 'inventory'; + + return askInventoryOnLaunch ? ( + <> + + {summaryFields.inventory.name} - {missingResourceIcon && ( - - - - - - )} - - {toTitleCase(template.type)} - - - - - - - - - - {({ handleLaunch }) => ( - - )} - - - - - - - - - - + + + + + {({ handleLaunch }) => ( + + )} + + + + + + + + + + + + + + + + } + /> + {summaryFields.credentials && summaryFields.credentials.length && ( + + {summaryFields.credentials.map(c => ( + + ))} + + } + /> + )} + {summaryFields.inventory ? ( + + ) : ( + !askInventoryOnLaunch && ( + + ) + )} + {summaryFields.labels && summaryFields.labels.results.length > 0 && ( + + {summaryFields.labels.results.map(l => ( + + {l.name} + + ))} + + } + /> + )} + {summaryFields.project ? ( + + {summaryFields.project.name} + + } + /> + ) : ( + + )} + + + + + ); } From 78ef11d5589b4f2977e4bd471a5bc2b9300c261f Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 19 Jan 2021 15:14:12 -0800 Subject: [PATCH 3/7] update Template list tests --- .../TemplateList/TemplateListItem.jsx | 10 +- .../TemplateList/TemplateListItem.test.jsx | 376 ++++++++++-------- 2 files changed, 220 insertions(+), 166 deletions(-) diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx index 29f94f78e6..b836ffef64 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -171,13 +171,11 @@ function TemplateListItem({ - + - - + + ', () => { test('launch button shown to users with start capabilities', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('LaunchButton').exists()).toBeTruthy(); }); test('launch button hidden from users without start capabilities', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('LaunchButton').exists()).toBeFalsy(); }); test('edit button shown to users with edit capabilities', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); }); test('edit button hidden from users without edit capabilities', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); }); test('missing resource icon is shown.', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(true); }); test('missing resource icon is not shown when there is a project and an inventory.', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); }); test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); }); test('missing resource icon is not shown type is workflow_job_template', () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); }); @@ -171,19 +203,23 @@ describe('', () => { initialEntries: ['/templates'], }); const wrapper = mountWithContexts( - , + + + + +
, { context: { router: { history } } } ); wrapper.find('Link').simulate('click', { button: 0 }); @@ -195,11 +231,15 @@ describe('', () => { JobTemplatesAPI.copy.mockResolvedValue(); const wrapper = mountWithContexts( - + + + + +
); await act(async () => wrapper.find('Button[aria-label="Copy"]').prop('onClick')() @@ -212,11 +252,15 @@ describe('', () => { JobTemplatesAPI.copy.mockRejectedValue(new Error()); const wrapper = mountWithContexts( - + + + + +
); await act(async () => wrapper.find('Button[aria-label="Copy"]').prop('onClick')() @@ -228,39 +272,51 @@ describe('', () => { test('should not render copy button', async () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('CopyButton').length).toBe(0); }); test('should render visualizer button for workflow', async () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ProjectDiagramIcon').length).toBe(1); }); test('should not render visualizer button for job template', async () => { const wrapper = mountWithContexts( - + + + + +
); expect(wrapper.find('ProjectDiagramIcon').length).toBe(0); }); From 03eb9bafb7fdc4e51d2dfeff4798912c4407588a Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Thu, 28 Jan 2021 14:28:08 -0800 Subject: [PATCH 4/7] add missing template list data; add ids to relevant page elements --- .../src/components/CopyButton/CopyButton.jsx | 4 ++- .../src/components/DetailList/Detail.jsx | 3 ++ .../src/components/JobList/JobList.jsx | 1 + .../ResourceAccessListItem.test.jsx.snap | 8 +++++ .../Template/TemplateList/TemplateList.jsx | 3 +- .../TemplateList/TemplateListItem.jsx | 32 +++++++++++++++---- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/awx/ui_next/src/components/CopyButton/CopyButton.jsx b/awx/ui_next/src/components/CopyButton/CopyButton.jsx index fe4580f4bf..30927f22c5 100644 --- a/awx/ui_next/src/components/CopyButton/CopyButton.jsx +++ b/awx/ui_next/src/components/CopyButton/CopyButton.jsx @@ -10,12 +10,13 @@ import AlertModal from '../AlertModal'; import ErrorDetail from '../ErrorDetail'; function CopyButton({ - i18n, + id, copyItem, isDisabled, onCopyStart, onCopyFinish, helperText, + i18n, }) { const { isLoading, error: copyError, request: copyItemToAPI } = useRequest( copyItem @@ -34,6 +35,7 @@ function CopyButton({ <> - - )} - {template.summary_fields.user_capabilities.start && ( - - - {({ handleLaunch }) => ( - - )} - - - )} - {template.summary_fields.user_capabilities.edit && ( - - - - )} - {template.summary_fields.user_capabilities.copy && ( - - )} - - - - ); -} - -export { DashboardTemplateListItem as _TemplateListItem }; -export default withI18n()(DashboardTemplateListItem); diff --git a/awx/ui_next/src/screens/Dashboard/shared/DashboardTemplateListItem.test.jsx b/awx/ui_next/src/screens/Dashboard/shared/DashboardTemplateListItem.test.jsx deleted file mode 100644 index 571ef260c0..0000000000 --- a/awx/ui_next/src/screens/Dashboard/shared/DashboardTemplateListItem.test.jsx +++ /dev/null @@ -1,268 +0,0 @@ -import React from 'react'; -import { createMemoryHistory } from 'history'; -import { act } from 'react-dom/test-utils'; - -import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; -import { JobTemplatesAPI } from '../../../api'; - -import mockJobTemplateData from './data.job_template.json'; -import DashboardTemplateListItem from './DashboardTemplateListItem'; - -jest.mock('../../../api'); - -describe('', () => { - test('launch button shown to users with start capabilities', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('LaunchButton').exists()).toBeTruthy(); - }); - test('launch button hidden from users without start capabilities', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('LaunchButton').exists()).toBeFalsy(); - }); - test('edit button shown to users with edit capabilities', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('PencilAltIcon').exists()).toBeTruthy(); - }); - test('edit button hidden from users without edit capabilities', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('PencilAltIcon').exists()).toBeFalsy(); - }); - test('missing resource icon is shown.', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(true); - }); - test('missing resource icon is not shown when there is a project and an inventory.', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); - }); - test('missing resource icon is not shown when inventory is prompt_on_launch, and a project', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); - }); - test('missing resource icon is not shown type is workflow_job_template', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ExclamationTriangleIcon').exists()).toBe(false); - }); - test('clicking on template from templates list navigates properly', () => { - const history = createMemoryHistory({ - initialEntries: ['/templates'], - }); - const wrapper = mountWithContexts( - , - { context: { router: { history } } } - ); - wrapper.find('Link').simulate('click', { button: 0 }); - expect(history.location.pathname).toEqual( - '/templates/job_template/1/details' - ); - }); - test('should call api to copy template', async () => { - JobTemplatesAPI.copy.mockResolvedValue(); - - const wrapper = mountWithContexts( - - ); - await act(async () => - wrapper.find('Button[aria-label="Copy"]').prop('onClick')() - ); - expect(JobTemplatesAPI.copy).toHaveBeenCalled(); - jest.clearAllMocks(); - }); - - test('should render proper alert modal on copy error', async () => { - JobTemplatesAPI.copy.mockRejectedValue(new Error()); - - const wrapper = mountWithContexts( - - ); - await act(async () => - wrapper.find('Button[aria-label="Copy"]').prop('onClick')() - ); - wrapper.update(); - expect(wrapper.find('Modal').prop('isOpen')).toBe(true); - jest.clearAllMocks(); - }); - - test('should not render copy button', async () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('CopyButton').length).toBe(0); - }); - - test('should render visualizer button for workflow', async () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ProjectDiagramIcon').length).toBe(1); - }); - - test('should not render visualizer button for job template', async () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('ProjectDiagramIcon').length).toBe(0); - }); -}); diff --git a/awx/ui_next/src/screens/Template/Templates.jsx b/awx/ui_next/src/screens/Template/Templates.jsx index f3905608cc..c5c5e1335f 100644 --- a/awx/ui_next/src/screens/Template/Templates.jsx +++ b/awx/ui_next/src/screens/Template/Templates.jsx @@ -5,7 +5,7 @@ import { Route, withRouter, Switch } from 'react-router-dom'; import { PageSection } from '@patternfly/react-core'; import ScreenHeader from '../../components/ScreenHeader/ScreenHeader'; -import { TemplateList } from './TemplateList'; +import TemplateList from '../../components/TemplateList'; import Template from './Template'; import WorkflowJobTemplate from './WorkflowJobTemplate'; import JobTemplateAdd from './JobTemplateAdd'; From 27b0f874cc8795fcfc7117e627b39bd38a00c73b Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Mon, 1 Feb 2021 09:31:47 -0800 Subject: [PATCH 6/7] remove unused var --- awx/ui_next/src/components/DetailList/Detail.jsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/awx/ui_next/src/components/DetailList/Detail.jsx b/awx/ui_next/src/components/DetailList/Detail.jsx index dc434a1fe8..b58f09a8dc 100644 --- a/awx/ui_next/src/components/DetailList/Detail.jsx +++ b/awx/ui_next/src/components/DetailList/Detail.jsx @@ -34,7 +34,6 @@ const DetailValue = styled( `; const Detail = ({ - id, label, value, fullWidth, @@ -78,7 +77,6 @@ const Detail = ({ ); }; Detail.propTypes = { - id: string, label: node.isRequired, value: node, fullWidth: bool, @@ -86,7 +84,6 @@ Detail.propTypes = { helpText: string, }; Detail.defaultProps = { - id: null, value: null, fullWidth: false, alwaysVisible: false, From 186aa6cc996d1a9a6d1ead3ee515d47fdd00fdcc Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Tue, 2 Feb 2021 11:59:17 -0800 Subject: [PATCH 7/7] update snapshot --- .../__snapshots__/ResourceAccessListItem.test.jsx.snap | 8 -------- 1 file changed, 8 deletions(-) diff --git a/awx/ui_next/src/components/ResourceAccessList/__snapshots__/ResourceAccessListItem.test.jsx.snap b/awx/ui_next/src/components/ResourceAccessList/__snapshots__/ResourceAccessListItem.test.jsx.snap index 039bf50bd7..355a9396e9 100644 --- a/awx/ui_next/src/components/ResourceAccessList/__snapshots__/ResourceAccessListItem.test.jsx.snap +++ b/awx/ui_next/src/components/ResourceAccessList/__snapshots__/ResourceAccessListItem.test.jsx.snap @@ -77,7 +77,6 @@ exports[` initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Name" value="jane brown" /> @@ -91,7 +90,6 @@ exports[` initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Team Roles" value={ initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Name" value="jane brown" /> @@ -159,7 +156,6 @@ exports[` initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Team Roles" value={ initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Name" value="jane brown" /> @@ -250,7 +245,6 @@ exports[` initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Team Roles" value={ initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Name" value="jane brown" > @@ -687,7 +680,6 @@ exports[` initially renders succesfully 1`] = ` alwaysVisible={false} fullWidth={false} helpText={null} - id={null} label="Team Roles" value={