From 179868dff2f2d9fffb611584affbe293d786eba0 Mon Sep 17 00:00:00 2001 From: Zita Nemeckova Date: Thu, 23 Feb 2023 13:49:40 +0100 Subject: [PATCH] Add possibility to select and delete HostMetrics --- awx/ui/src/screens/HostMetrics/HostMetrics.js | 120 +++++++--- .../HostMetrics/HostMetricsDeleteButton.js | 220 ++++++++++++++++++ .../HostMetrics/HostMetricsListItem.js | 7 +- 3 files changed, 312 insertions(+), 35 deletions(-) create mode 100644 awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js diff --git a/awx/ui/src/screens/HostMetrics/HostMetrics.js b/awx/ui/src/screens/HostMetrics/HostMetrics.js index 7133bf7a18..ce43a04000 100644 --- a/awx/ui/src/screens/HostMetrics/HostMetrics.js +++ b/awx/ui/src/screens/HostMetrics/HostMetrics.js @@ -5,13 +5,16 @@ import { HostMetricsAPI } from 'api'; import useRequest from 'hooks/useRequest'; import PaginatedTable, { HeaderRow, - HeaderCell + HeaderCell, } from 'components/PaginatedTable'; import DataListToolbar from 'components/DataListToolbar'; import { getQSConfig, parseQueryString } from 'util/qs'; import {Card, PageSection} from "@patternfly/react-core"; import { useLocation } from 'react-router-dom'; import HostMetricsListItem from "./HostMetricsListItem"; +import HostMetricsDeleteButton from "./HostMetricsDeleteButton"; +import useSelected from 'hooks/useSelected'; + function HostMetrics() { @@ -46,40 +49,91 @@ function HostMetrics() { readHostMetrics(); }, [readHostMetrics]); + const { selected, isAllSelected, handleSelect, selectAll, clearSelected } = + useSelected(results); + return( - <> + <> - - - ()} - qsConfig={QS_CONFIG} - toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]} - toolbarSearchableKeys={[]} - toolbarRelatedSearchableKeys={[]} - renderToolbar={(props) => - {t`Hostname`} - {t`First automated`} - {t`Last automated`} - {t`Automation`} - {t`Inventories`} - {t`Deleted`} - -} - /> - - - + streamType="none" + breadcrumbConfig={breadcrumbConfig} + /> + + + ( + row.hostname === item.hostname)} + onSelect={() => handleSelect(item)} + rowIndex={index} + /> + )} + qsConfig={QS_CONFIG} + toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]} + toolbarSearchableKeys={[]} + toolbarRelatedSearchableKeys={[]} + renderToolbar={(props) => + + Promise.all(selected.map((hostMetric) => + HostMetricsAPI.destroy(hostMetric.id))) + .then(() => { readHostMetrics(); clearSelected(); }) + } + itemsToDelete={selected} + pluralizedItemName={t`Host Metrics`} + />]} + />} + headerRow={ + + + {t`Hostname`} + + + {t`First automated`} + + + {t`Last automated`} + + + {t`Automation`} + + + {t`Inventories`} + + + {t`Deleted`} + + + } + /> + + + ); } diff --git a/awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js b/awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js new file mode 100644 index 0000000000..9519ca519e --- /dev/null +++ b/awx/ui/src/screens/HostMetrics/HostMetricsDeleteButton.js @@ -0,0 +1,220 @@ +import React, { useState } from 'react'; +import { + func, + node, + string, + arrayOf, + shape, +} from 'prop-types'; +import styled from 'styled-components'; +import { + Alert, + Badge, + Button, + Tooltip, +} from '@patternfly/react-core'; +import { t } from '@lingui/macro'; +import { getRelatedResourceDeleteCounts } from 'util/getRelatedResourceDeleteDetails'; +import AlertModal from '../../components/AlertModal'; + +import ErrorDetail from '../../components/ErrorDetail'; + +const WarningMessage = styled(Alert)` + margin-top: 10px; +`; + +const Label = styled.span` + && { + margin-right: 10px; + } +`; + +const ItemToDelete = shape({ + hostname: string.isRequired, +}); + +function HostMetricsDeleteButton({ + itemsToDelete, + pluralizedItemName, + errorMessage, + onDelete, + deleteDetailsRequests, + warningMessage, + deleteMessage, +}) { + const [isModalOpen, setIsModalOpen] = useState(false); + const [deleteDetails, setDeleteDetails] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const [deleteMessageError, setDeleteMessageError] = useState(); + const handleDelete = () => { + console.log("Delete"); + onDelete(); + toggleModal(); + }; + + const toggleModal = async (isOpen) => { + setIsLoading(true); + setDeleteDetails(null); + if ( + isOpen && + itemsToDelete.length === 1 && + deleteDetailsRequests?.length > 0 + ) { + const { results, error } = await getRelatedResourceDeleteCounts( + deleteDetailsRequests + ); + + if (error) { + setDeleteMessageError(error); + } else { + setDeleteDetails(results); + } + } + setIsLoading(false); + setIsModalOpen(isOpen); + }; + + const renderTooltip = () => { + if (itemsToDelete.length) { + return t`Soft delete`; + } else { + return t`Select a row to delete`; + } + }; + + const modalTitle = t`Soft delete ${pluralizedItemName}?`; + + const isDisabled = + itemsToDelete.length === 0; + + const buildDeleteWarning = () => { + const deleteMessages = []; + if (warningMessage) { + deleteMessages.push(warningMessage); + } + if (deleteMessage) { + if (itemsToDelete.length > 1 || deleteDetails) + { + deleteMessages.push(deleteMessage); + } else if (deleteDetails || itemsToDelete.length > 1) { + deleteMessages.push(deleteMessage); + } + } + return ( +
+ {deleteMessages.map((message) => ( +
+ {message} +
+ ))} + {deleteDetails && + Object.entries(deleteDetails).map(([key, value]) => ( +
+ + {value} +
+ ))} +
+ ); + }; + + if (deleteMessageError) { + return ( + { + toggleModal(false); + setDeleteMessageError(); + }} + > + + + ); + } + const shouldShowDeleteWarning = + warningMessage || + (itemsToDelete.length === 1 && deleteDetails) || + (itemsToDelete.length > 1 && deleteMessage); + + return ( + <> + +
+ +
+
+ {isModalOpen && ( + toggleModal(false)} + actions={[ + , + , + ]} + > +
{t`This action will soft delete the following:`}
+ {itemsToDelete.map((item) => ( + + {item.hostname} +
+
+ ))} + {shouldShowDeleteWarning && ( + + )} +
+ )} + + ); +} + +HostMetricsDeleteButton.propTypes = { + onDelete: func.isRequired, + itemsToDelete: arrayOf(ItemToDelete).isRequired, + pluralizedItemName: string, + warningMessage: node, +}; + +HostMetricsDeleteButton.defaultProps = { + pluralizedItemName: 'Items', + warningMessage: null, +}; + +export default HostMetricsDeleteButton; diff --git a/awx/ui/src/screens/HostMetrics/HostMetricsListItem.js b/awx/ui/src/screens/HostMetrics/HostMetricsListItem.js index fe9d06a25c..3fa4fb336e 100644 --- a/awx/ui/src/screens/HostMetrics/HostMetricsListItem.js +++ b/awx/ui/src/screens/HostMetrics/HostMetricsListItem.js @@ -4,12 +4,13 @@ import { Tr, Td } from '@patternfly/react-table'; import { formatDateString } from 'util/dates'; import { HostMetrics } from 'types'; import {t} from "@lingui/macro"; +import {bool, func} from "prop-types"; -function HostMetricsListItem({ item }) { +function HostMetricsListItem({ item, isSelected, onSelect, rowIndex }) { return ( - + {item.hostname} {formatDateString(item.first_automation)} {formatDateString(item.last_automation)} @@ -22,6 +23,8 @@ function HostMetricsListItem({ item }) { HostMetricsListItem.propTypes = { item: HostMetrics.isRequired, + isSelected: bool.isRequired, + onSelect: func.isRequired, }; export default HostMetricsListItem;