From 8d31d09d4af525635f5f63a907f3b3b5f8c47e0f Mon Sep 17 00:00:00 2001 From: Alex Corey Date: Wed, 22 Apr 2020 09:16:00 -0400 Subject: [PATCH] Adds copy button to JTList --- awx/ui_next/src/api/models/JobTemplates.js | 4 ++ .../Template/TemplateList/CopyButton.jsx | 54 ++++++++++++++++++ .../Template/TemplateList/TemplateList.jsx | 1 + .../TemplateList/TemplateListItem.jsx | 57 ++++++++++++++----- awx/ui_next/src/util/dates.jsx | 12 ++++ 5 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 awx/ui_next/src/screens/Template/TemplateList/CopyButton.jsx diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js index 0e2eba8079..03647de4b0 100644 --- a/awx/ui_next/src/api/models/JobTemplates.js +++ b/awx/ui_next/src/api/models/JobTemplates.js @@ -19,6 +19,10 @@ class JobTemplates extends SchedulesMixin( this.readWebhookKey = this.readWebhookKey.bind(this); } + copyTemplate(id, data) { + return this.http.post(`${this.baseUrl}${id}/copy/`, data); + } + launch(id, data) { return this.http.post(`${this.baseUrl}${id}/launch/`, data); } diff --git a/awx/ui_next/src/screens/Template/TemplateList/CopyButton.jsx b/awx/ui_next/src/screens/Template/TemplateList/CopyButton.jsx new file mode 100644 index 0000000000..50725ad232 --- /dev/null +++ b/awx/ui_next/src/screens/Template/TemplateList/CopyButton.jsx @@ -0,0 +1,54 @@ +import React, { useCallback, useEffect } from 'react'; +import { withI18n } from '@lingui/react'; +import { t } from '@lingui/macro'; + +import { Button, Tooltip } from '@patternfly/react-core'; +import { CopyIcon } from '@patternfly/react-icons'; +import useRequest, { useDismissableError } from '@util/useRequest'; +import AlertModal from '@components/AlertModal'; +import ErrorDetail from '@components/ErrorDetail'; + +function CopyButton({ i18n, itemName, copyItem, disableButtons }) { + const { isLoading, error, request: copyTemplateToAPI } = useRequest( + useCallback(async () => { + await copyItem(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []), + {} + ); + + useEffect(() => { + if (isLoading) { + return disableButtons(true); + } + return disableButtons(false); + }, [isLoading, disableButtons]); + + const { dismissError } = useDismissableError(error); + + return ( + <> + + + + dismissError} + > + {i18n._(t`Failed to copy ${itemName}.`)} + + + + ); +} +export default withI18n()(CopyButton); diff --git a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx index 1f7b5554a4..21d7dc0206 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateList.jsx @@ -227,6 +227,7 @@ function TemplateList({ i18n }) { detailUrl={`/templates/${template.type}/${template.id}`} onSelect={() => handleSelect(template)} isSelected={selected.some(row => row.id === template.id)} + fetchTemplates={fetchTemplates} /> )} 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 991f9b91c0..c82216421f 100644 --- a/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx +++ b/awx/ui_next/src/screens/Template/TemplateList/TemplateListItem.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import { Link } from 'react-router-dom'; import { Button, @@ -18,11 +18,14 @@ import { PencilAltIcon, RocketIcon, } from '@patternfly/react-icons'; +import { timeOfDay } from '@util/dates'; +import { JobTemplatesAPI } from '@api'; import LaunchButton from '@components/LaunchButton'; import Sparkline from '@components/Sparkline'; import { toTitleCase } from '@util/strings'; import styled from 'styled-components'; +import CopyButton from './CopyButton'; const DataListAction = styled(_DataListAction)` align-items: center; @@ -31,7 +34,15 @@ const DataListAction = styled(_DataListAction)` grid-template-columns: repeat(2, 40px); `; -function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) { +function TemplateListItem({ + i18n, + template, + isSelected, + onSelect, + detailUrl, + fetchTemplates, +}) { + const [disableButtons, setDisableButtons] = useState(false); const labelId = `check-action-${template.id}`; const canLaunch = template.summary_fields.user_capabilities.start; @@ -40,11 +51,11 @@ function TemplateListItem({ i18n, template, isSelected, onSelect, detailUrl }) { (!template.summary_fields.project || (!template.summary_fields.inventory && !template.ask_inventory_on_launch)); - return ( {({ handleLaunch }) => ( - + <> + + + + {template.summary_fields.user_capabilities.copy && ( + { + await JobTemplatesAPI.copyTemplate(template.id, { + name: `${template.name}@${timeOfDay()}`, + }); + await fetchTemplates(); + }} + /> + )} + ) : ( '' )} diff --git a/awx/ui_next/src/util/dates.jsx b/awx/ui_next/src/util/dates.jsx index 68d9461334..ba4df777bf 100644 --- a/awx/ui_next/src/util/dates.jsx +++ b/awx/ui_next/src/util/dates.jsx @@ -17,6 +17,18 @@ export function secondsToHHMMSS(seconds) { return new Date(seconds * 1000).toISOString().substr(11, 8); } +export function timeOfDay() { + const date = new Date(); + const hour = date.getHours(); + const minute = prependZeros(date.getMinutes()); + const second = prependZeros(date.getSeconds()); + const time = + hour > 12 + ? `${hour - 12}:${minute} :${second} PM` + : `${hour}:${minute}:${second}`; + return time; +} + export function dateToInputDateTime(dateObj) { // input type="date-time" expects values to be formatted // like: YYYY-MM-DDTHH-MM-SS