From 66f140bb7082c389df1aba2081a4af44c03fa100 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 1 Jun 2021 11:25:27 -0400 Subject: [PATCH 1/6] Adds ability to get latest revision from project list when a project is done syncing. Automatically refresh project data on project detail when it's done syncing. --- .../src/components/CopyButton/CopyButton.jsx | 25 +++--- .../Project/ProjectDetail/ProjectDetail.jsx | 46 +++++++++- .../Project/ProjectDetail/useWsProject.js | 11 +++ .../ProjectDetail/useWsProject.test.jsx | 32 ++++++- .../Project/ProjectList/ProjectList.jsx | 88 +++++++++++++++---- .../Project/ProjectList/ProjectListItem.jsx | 85 ++++++++++++++---- .../ProjectList/ProjectListItem.test.jsx | 84 ++++++++++++++++-- .../Project/ProjectList/useWsProjects.js | 5 ++ 8 files changed, 316 insertions(+), 60 deletions(-) diff --git a/awx/ui_next/src/components/CopyButton/CopyButton.jsx b/awx/ui_next/src/components/CopyButton/CopyButton.jsx index f8a53eedf7..6923ee16f0 100644 --- a/awx/ui_next/src/components/CopyButton/CopyButton.jsx +++ b/awx/ui_next/src/components/CopyButton/CopyButton.jsx @@ -1,8 +1,6 @@ import React, { useEffect } from 'react'; - import { t } from '@lingui/macro'; import PropTypes from 'prop-types'; - import { Button } from '@patternfly/react-core'; import { CopyIcon } from '@patternfly/react-icons'; import useRequest, { useDismissableError } from '../../util/useRequest'; @@ -16,7 +14,6 @@ function CopyButton({ onCopyStart, onCopyFinish, errorMessage, - ouiaId, }) { const { isLoading, error: copyError, request: copyItemToAPI } = useRequest( @@ -44,16 +41,18 @@ function CopyButton({ > - - {errorMessage} - - + {error && ( + + {errorMessage} + + + )} ); } diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 675b39c6d0..ba87a26b20 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -1,11 +1,16 @@ import React, { Fragment, useCallback } from 'react'; import { Link, useHistory } from 'react-router-dom'; - import { t } from '@lingui/macro'; -import { Button, List, ListItem, Tooltip } from '@patternfly/react-core'; +import styled from 'styled-components'; +import { + Button, + ClipboardCopy, + List, + ListItem, + Tooltip, +} from '@patternfly/react-core'; import { Project } from '../../../types'; import { Config } from '../../../contexts/Config'; - import AlertModal from '../../../components/AlertModal'; import { CardBody, CardActionsRow } from '../../../components/Card'; import DeleteButton from '../../../components/DeleteButton'; @@ -27,6 +32,10 @@ import StatusLabel from '../../../components/StatusLabel'; import { formatDateString } from '../../../util/dates'; import useWsProject from './useWsProject'; +const Label = styled.span` + color: var(--pf-global--disabled-color--100); +`; + function ProjectDetail({ project }) { const { allow_override, @@ -42,6 +51,7 @@ function ProjectDetail({ project }) { scm_delete_on_update, scm_track_submodules, scm_refspec, + scm_revision, scm_type, scm_update_on_launch, scm_update_cache_timeout, @@ -146,6 +156,36 @@ function ProjectDetail({ project }) { label={t`Source Control Type`} value={scm_type === '' ? t`Manual` : toTitleCase(project.scm_type)} /> + { + const clipboard = event.currentTarget.parentElement; + const el = document.createElement('textarea'); + el.value = scm_revision.toString(); + clipboard.appendChild(el); + el.select(); + document.execCommand('copy'); + clipboard.removeChild(el); + }} + > + {scm_revision.substring(0, 7)} + + ) : ( + + ) + } + alwaysVisible + /> diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.js b/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.js index 6c3e582c4b..e9a9954c28 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.js +++ b/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.js @@ -1,5 +1,6 @@ import { useState, useEffect } from 'react'; import useWebsocket from '../../../util/useWebsocket'; +import { ProjectsAPI } from '../../../api'; export default function useWsProjects(initialProject) { const [project, setProject] = useState(initialProject); @@ -8,6 +9,11 @@ export default function useWsProjects(initialProject) { control: ['limit_reached_1'], }); + const refreshProject = async () => { + const { data } = await ProjectsAPI.readDetail(project.id); + setProject(data); + }; + useEffect(() => { setProject(initialProject); }, [initialProject]); @@ -22,6 +28,11 @@ export default function useWsProjects(initialProject) { return; } + if (lastMessage.finished) { + refreshProject(); + return; + } + const updatedProject = { ...project, summary_fields: { diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.test.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.test.jsx index 293f6ef986..21bcabe1e7 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.test.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/useWsProject.test.jsx @@ -2,8 +2,11 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import WS from 'jest-websocket-mock'; import { mountWithContexts } from '../../../../testUtils/enzymeHelpers'; +import { ProjectsAPI } from '../../../api'; import useWsProject from './useWsProject'; +jest.mock('../../../api/models/Projects'); + function TestInner() { return
; } @@ -15,13 +18,30 @@ function Test({ project }) { describe('useWsProject', () => { let debug; let wrapper; + beforeEach(() => { debug = global.console.debug; // eslint-disable-line prefer-destructuring global.console.debug = () => {}; + ProjectsAPI.readDetail.mockResolvedValue({ + data: { + id: 1, + summary_fields: { + last_job: { + id: 19, + name: 'Test Project', + description: '', + finished: '2021-06-01T18:43:53.332201Z', + status: 'successful', + failed: false, + }, + }, + }, + }); }); afterEach(() => { global.console.debug = debug; + jest.clearAllMocks(); }); test('should return project detail', async () => { @@ -127,14 +147,20 @@ describe('useWsProject', () => { }) ); }); + wrapper.update(); + expect(ProjectsAPI.readDetail).toHaveBeenCalledTimes(1); + expect( - wrapper.find('TestInner').prop('project').summary_fields.current_job + wrapper.find('TestInner').prop('project').summary_fields.last_job ).toEqual({ - id: 2, + id: 19, + name: 'Test Project', + description: '', + finished: '2021-06-01T18:43:53.332201Z', status: 'successful', - finished: '2020-07-02T16:28:31.839071Z', + failed: false, }); WS.clean(); }); diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index 66b9dff1f9..6ea8bfca9e 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -2,9 +2,11 @@ import React, { useEffect, useCallback } from 'react'; import { useLocation, useRouteMatch } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; import { Card, PageSection } from '@patternfly/react-core'; - import { ProjectsAPI } from '../../../api'; -import useRequest, { useDeleteItems } from '../../../util/useRequest'; +import useRequest, { + useDeleteItems, + useDismissableError, +} from '../../../util/useRequest'; import AlertModal from '../../../components/AlertModal'; import DataListToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; @@ -33,6 +35,21 @@ function ProjectList() { const location = useLocation(); const match = useRouteMatch(); + const { + request: fetchUpdatedProject, + error: fetchUpdatedProjectError, + result: updatedProject, + } = useRequest( + useCallback(async projectId => { + if (!projectId) { + return {}; + } + const { data } = await ProjectsAPI.readDetail(projectId); + return data; + }, []), + null + ); + const { result: { results, @@ -43,7 +60,8 @@ function ProjectList() { }, error: contentError, isLoading, - request: fetchProjects, + request: fetchUpdatedProjects, + setValue: setProjects, } = useRequest( useCallback(async () => { const params = parseQueryString(QS_CONFIG, location.search); @@ -73,8 +91,8 @@ function ProjectList() { ); useEffect(() => { - fetchProjects(); - }, [fetchProjects]); + fetchUpdatedProjects(); + }, [fetchUpdatedProjects]); const projects = useWsProjects(results); @@ -99,7 +117,7 @@ function ProjectList() { { qsConfig: QS_CONFIG, allItemsSelected: isAllSelected, - fetchItems: fetchProjects, + fetchItems: fetchUpdatedProjects, } ); @@ -115,6 +133,27 @@ function ProjectList() { selected[0] ); + useEffect(() => { + if (updatedProject) { + const updatedProjects = projects.map(project => + project.id === updatedProject.id ? updatedProject : project + ); + setProjects({ + results: updatedProjects, + itemCount, + actions, + relatedSearchableKeys, + searchableKeys, + }); + } + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [updatedProject]); + + const { + error: projectError, + dismissError: dismissProjectError, + } = useDismissableError(fetchUpdatedProjectError); + return ( <> @@ -207,13 +246,14 @@ function ProjectList() { )} renderRow={(project, index) => ( row.id === project.id)} onSelect={() => handleSelect(project)} rowIndex={index} + onRefreshRow={projectId => fetchUpdatedProject(projectId)} /> )} emptyStateControls={ @@ -224,16 +264,30 @@ function ProjectList() { /> - - {t`Failed to delete one or more projects.`} - - + {deletionError && ( + + {t`Failed to delete one or more projects.`} + + + )} + {projectError && ( + + {t`Failed to fetch the updated project data.`} + + + )} ); } diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 7fcf5ec5c9..e45bd4529c 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -1,20 +1,19 @@ import 'styled-components/macro'; import React, { Fragment, useState, useCallback } from 'react'; import { string, bool, func } from 'prop-types'; - -import { Button, Tooltip } from '@patternfly/react-core'; +import { Button, ClipboardCopy, Tooltip } from '@patternfly/react-core'; import { Tr, Td, ExpandableRowContent } from '@patternfly/react-table'; import { t } from '@lingui/macro'; import { Link } from 'react-router-dom'; import { PencilAltIcon, ExclamationTriangleIcon as PFExclamationTriangleIcon, + UndoIcon, } 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 { DetailList, Detail, @@ -23,6 +22,7 @@ import { import ExecutionEnvironmentDetail from '../../../components/ExecutionEnvironmentDetail'; import StatusLabel from '../../../components/StatusLabel'; import { toTitleCase } from '../../../util/strings'; +import { isJobRunning } from '../../../util/jobs'; import CopyButton from '../../../components/CopyButton'; import ProjectSyncButton from '../shared/ProjectSyncButton'; import { Project } from '../../../types'; @@ -44,6 +44,7 @@ function ProjectListItem({ detailUrl, fetchProjects, rowIndex, + onRefreshRow, }) { const [isExpanded, setIsExpanded] = useState(false); const [isDisabled, setIsDisabled] = useState(false); @@ -88,6 +89,68 @@ function ProjectListItem({ setIsDisabled(false); }, []); + const renderRevision = () => { + if (!project.summary_fields?.current_job || project.scm_revision) { + return project.scm_revision ? ( + { + const clipboard = event.currentTarget.parentElement; + const el = document.createElement('textarea'); + el.value = project.scm_revision.toString(); + clipboard.appendChild(el); + el.select(); + document.execCommand('copy'); + clipboard.removeChild(el); + }} + > + {project.scm_revision.substring(0, 7)} + + ) : ( + + ); + } + + if ( + isJobRunning(project.summary_fields.current_job.status) && + !project.scm_revision + ) { + return ( + + ); + } + + return ( + <> + + + + + + ); + }; + const labelId = `check-action-${project.id}`; const missingExecutionEnvironment = @@ -153,21 +216,7 @@ function ProjectListItem({ {project.scm_type === '' ? t`Manual` : toTitleCase(project.scm_type)} - - {project.scm_revision.substring(0, 7)} - {!project.scm_revision && ( - - )} - - + {renderRevision()} {['running', 'pending', 'waiting'].includes(job?.status) ? ( ', () => { }); test('should render proper alert modal on copy error', async () => { - ProjectsAPI.copy.mockRejectedValue(new Error()); + ProjectsAPI.copy.mockRejectedValue(new Error('This is an error')); const wrapper = mountWithContexts( @@ -285,7 +285,7 @@ describe('', () => { ); expect(wrapper.find('CopyButton').length).toBe(0); }); - test('should render disabled copy to clipboard button', () => { + test('should render proper revision text when project has not been synced', () => { const wrapper = mountWithContexts(
@@ -314,10 +314,82 @@ describe('', () => {
); - expect( - wrapper.find('span[aria-label="copy to clipboard disabled"]').text() - ).toBe('Sync for revision'); - expect(wrapper.find('ClipboardCopyButton').prop('isDisabled')).toBe(true); + expect(wrapper.find('ClipboardCopy').length).toBe(0); + expect(wrapper.find('td[data-label="Revision"]').text()).toBe( + 'Sync for revision' + ); + }); + test('should render the clipboard copy with the right text when scm revision available', () => { + const wrapper = mountWithContexts( + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: 'osofej904r09a9sf0udfsajogsdfbh4e23489adf', + summary_fields: { + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: true, + }, + }, + }} + /> + +
+ ); + expect(wrapper.find('ClipboardCopy').length).toBe(1); + expect(wrapper.find('ClipboardCopy').text()).toBe('osofej9'); + }); + test('should indicate that the revision needs to be refreshed when project sync is done', () => { + const wrapper = mountWithContexts( + + + {}} + project={{ + id: 1, + name: 'Project 1', + url: '/api/v2/projects/1', + type: 'project', + scm_type: 'git', + scm_revision: null, + summary_fields: { + current_job: { + id: 9001, + status: 'successful', + finished: '2021-06-01T18:43:53.332201Z', + }, + last_job: { + id: 9000, + status: 'successful', + }, + user_capabilities: { + edit: true, + }, + }, + }} + /> + +
+ ); + expect(wrapper.find('ClipboardCopy').length).toBe(0); + expect(wrapper.find('td[data-label="Revision"]').text()).toBe( + 'Refresh for revision' + ); + expect(wrapper.find('UndoIcon').length).toBe(1); }); test('should render expected details in expanded section', async () => { const wrapper = mountWithContexts( diff --git a/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js b/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js index de863a7ee7..9fcebf8f97 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js +++ b/awx/ui_next/src/screens/Project/ProjectList/useWsProjects.js @@ -33,6 +33,11 @@ export default function useWsProjects(initialProjects) { }, }, }; + + if (lastMessage.finished) { + updatedProject.scm_revision = null; + } + setProjects([ ...projects.slice(0, index), updatedProject, From 7467779ea9af9397c71c56b75bfc71518f923931 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 2 Jun 2021 09:32:59 -0400 Subject: [PATCH 2/6] Remove clipboard copy button component --- .../ClipboardCopyButton.jsx | 90 ------------------- .../ClipboardCopyButton.test.jsx | 61 ------------- .../components/ClipboardCopyButton/index.js | 1 - 3 files changed, 152 deletions(-) delete mode 100644 awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx delete mode 100644 awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx delete mode 100644 awx/ui_next/src/components/ClipboardCopyButton/index.js diff --git a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx deleted file mode 100644 index 2563ca5b15..0000000000 --- a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.jsx +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Button, Tooltip } from '@patternfly/react-core'; -import { CopyIcon } from '@patternfly/react-icons'; - -export const clipboardCopyFunc = (event, text) => { - const clipboard = event.currentTarget.parentElement; - const el = document.createElement('input'); - el.value = text; - clipboard.appendChild(el); - el.select(); - document.execCommand('copy'); - clipboard.removeChild(el); -}; - -class ClipboardCopyButton extends React.Component { - constructor(props) { - super(props); - - this.state = { - copied: false, - }; - - this.handleCopyClick = this.handleCopyClick.bind(this); - } - - handleCopyClick = event => { - const { stringToCopy, switchDelay } = this.props; - if (this.timer) { - window.clearTimeout(this.timer); - this.setState({ copied: false }); - } - clipboardCopyFunc(event, stringToCopy); - this.setState({ copied: true }, () => { - this.timer = window.setTimeout(() => { - this.setState({ copied: false }); - this.timer = null; - }, switchDelay); - }); - }; - - render() { - const { - copyTip, - entryDelay, - exitDelay, - copiedSuccessTip, - isDisabled, - ouiaId, - } = this.props; - const { copied } = this.state; - - return ( - - - - ); - } -} - -ClipboardCopyButton.propTypes = { - copyTip: PropTypes.string.isRequired, - entryDelay: PropTypes.number, - exitDelay: PropTypes.number, - copiedSuccessTip: PropTypes.string.isRequired, - stringToCopy: PropTypes.string.isRequired, - switchDelay: PropTypes.number, - isDisabled: PropTypes.bool.isRequired, -}; - -ClipboardCopyButton.defaultProps = { - entryDelay: 100, - exitDelay: 1600, - switchDelay: 2000, -}; - -export default ClipboardCopyButton; diff --git a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx b/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx deleted file mode 100644 index 5daaeb81e5..0000000000 --- a/awx/ui_next/src/components/ClipboardCopyButton/ClipboardCopyButton.test.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import React from 'react'; -import { mountWithContexts } from '../../../testUtils/enzymeHelpers'; -import ClipboardCopyButton from './ClipboardCopyButton'; - -describe('ClipboardCopyButton', () => { - beforeEach(() => { - document.execCommand = jest.fn(); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - test('renders the expected content', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper).toHaveLength(1); - }); - test('clicking button calls execCommand to copy to clipboard', async () => { - const mockDelay = 1; - const wrapper = mountWithContexts( - - ).find('ClipboardCopyButton'); - expect(wrapper.state('copied')).toBe(false); - wrapper.find('Button').simulate('click'); - expect(document.execCommand).toBeCalledWith('copy'); - expect(wrapper.state('copied')).toBe(true); - await new Promise(resolve => setTimeout(resolve, mockDelay)); - wrapper.update(); - expect(wrapper.state('copied')).toBe(false); - }); - test('should render disabled button', () => { - const wrapper = mountWithContexts( - - ); - expect(wrapper.find('Button').prop('isDisabled')).toBe(true); - }); -}); diff --git a/awx/ui_next/src/components/ClipboardCopyButton/index.js b/awx/ui_next/src/components/ClipboardCopyButton/index.js deleted file mode 100644 index 45adfe436e..0000000000 --- a/awx/ui_next/src/components/ClipboardCopyButton/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './ClipboardCopyButton'; From ddb6c775b16e47d7b05e62e5eb345b3f03423f29 Mon Sep 17 00:00:00 2001 From: mabashian Date: Wed, 2 Jun 2021 10:08:21 -0400 Subject: [PATCH 3/6] Adds dataCy to revision copy in project details --- awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index ba87a26b20..4200f1509f 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -161,6 +161,7 @@ function ProjectDetail({ project }) { value={ scm_revision ? ( Date: Wed, 2 Jun 2021 10:10:00 -0400 Subject: [PATCH 4/6] Fix data-cy --- awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx | 2 +- awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 4200f1509f..3548f14d80 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -161,7 +161,7 @@ function ProjectDetail({ project }) { value={ scm_revision ? ( Date: Mon, 7 Jun 2021 14:18:27 -0400 Subject: [PATCH 5/6] Updates onCopy functions to use navigator.clipboard instead of deprecated execCommand --- .../screens/Project/ProjectDetail/ProjectDetail.jsx | 12 +++--------- .../screens/Project/ProjectList/ProjectListItem.jsx | 12 +++--------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx index 3548f14d80..6e1bad5b24 100644 --- a/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx +++ b/awx/ui_next/src/screens/Project/ProjectDetail/ProjectDetail.jsx @@ -165,15 +165,9 @@ function ProjectDetail({ project }) { variant="inline-compact" clickTip={t`Successfully copied to clipboard!`} hoverTip={t`Copy full revision to clipboard.`} - onCopy={event => { - const clipboard = event.currentTarget.parentElement; - const el = document.createElement('textarea'); - el.value = scm_revision.toString(); - clipboard.appendChild(el); - el.select(); - document.execCommand('copy'); - clipboard.removeChild(el); - }} + onCopy={() => + navigator.clipboard.writeText(scm_revision.toString()) + } > {scm_revision.substring(0, 7)} diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx index 2ccaad5b94..894bb09947 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectListItem.jsx @@ -97,15 +97,9 @@ function ProjectListItem({ variant="inline-compact" clickTip={t`Successfully copied to clipboard!`} hoverTip={t`Copy full revision to clipboard.`} - onCopy={event => { - const clipboard = event.currentTarget.parentElement; - const el = document.createElement('textarea'); - el.value = project.scm_revision.toString(); - clipboard.appendChild(el); - el.select(); - document.execCommand('copy'); - clipboard.removeChild(el); - }} + onCopy={() => + navigator.clipboard.writeText(project.scm_revision.toString()) + } > {project.scm_revision.substring(0, 7)} From 4db6b8c1fe8d34a3bbe67fecb40f264b4f7ae418 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 7 Jun 2021 14:21:34 -0400 Subject: [PATCH 6/6] Rolls back aggressive find/replace on fetchProjects --- .../src/screens/Project/ProjectList/ProjectList.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index 6ea8bfca9e..eb6e799d40 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -60,7 +60,7 @@ function ProjectList() { }, error: contentError, isLoading, - request: fetchUpdatedProjects, + request: fetchProjects, setValue: setProjects, } = useRequest( useCallback(async () => { @@ -91,8 +91,8 @@ function ProjectList() { ); useEffect(() => { - fetchUpdatedProjects(); - }, [fetchUpdatedProjects]); + fetchProjects(); + }, [fetchProjects]); const projects = useWsProjects(results); @@ -117,7 +117,7 @@ function ProjectList() { { qsConfig: QS_CONFIG, allItemsSelected: isAllSelected, - fetchItems: fetchUpdatedProjects, + fetchItems: fetchProjects, } ); @@ -246,7 +246,7 @@ function ProjectList() { )} renderRow={(project, index) => (