Add possibility to select and delete HostMetrics

This commit is contained in:
Zita Nemeckova 2023-02-23 13:49:40 +01:00 committed by John Westcott IV
parent 9f3c4f6240
commit 179868dff2
3 changed files with 312 additions and 35 deletions

View File

@ -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(
<>
<>
<ScreenHeader
streamType="none"
breadcrumbConfig={breadcrumbConfig}
/>
<PageSection>
<Card>
<PaginatedTable
contentError={error}
hasContentLoading={isLoading}
items={results}
itemCount={count}
pluralizedItemName={t`Host Metrics`}
renderRow={(item)=> (<HostMetricsListItem item={item} />)}
qsConfig={QS_CONFIG}
toolbarSearchColumns={[{name: t`Hostname`, key: 'hostname__icontains', isDefault: true}]}
toolbarSearchableKeys={[]}
toolbarRelatedSearchableKeys={[]}
renderToolbar={(props) => <DataListToolbar {...props} advancedSearchDisabled={true} fillWidth}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="hostname">{t`Hostname`}</HeaderCell>
<HeaderCell sortKey="first_automation" tooltip={t`When was the host first automated`}>{t`First automated`}</HeaderCell>
<HeaderCell sortKey="last_automation" tooltip={t`When was the host last automated`}>{t`Last automated`}</HeaderCell>
<HeaderCell sortKey="automated_counter" tooltip={t`How many times was the host automated`}>{t`Automation`}</HeaderCell>
<HeaderCell sortKey="used_in_inventories" tooltip={t`How many inventories is the host in, recomputed on a weekly schedule`}>{t`Inventories`}</HeaderCell>
<HeaderCell sortKey="deleted_counter" tooltip={t`How many times was the host deleted`}>{t`Deleted`}</HeaderCell>
</HeaderRow>
}
/>
</Card>
</PageSection>
</>
streamType="none"
breadcrumbConfig={breadcrumbConfig}
/>
<PageSection>
<Card>
<PaginatedTable
contentError={error}
hasContentLoading={isLoading}
items={results}
itemCount={count}
pluralizedItemName={t`Host Metrics`}
renderRow={(item, index)=> (
<HostMetricsListItem
item={item}
isSelected={selected.some((row) => 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) =>
<DataListToolbar
{...props}
advancedSearchDisabled={true}
fillWidth
isAllSelected={isAllSelected}
onSelectAll={selectAll}
additionalControls={[
<HostMetricsDeleteButton
key="delete"
onDelete={() =>
Promise.all(selected.map((hostMetric) =>
HostMetricsAPI.destroy(hostMetric.id)))
.then(() => { readHostMetrics(); clearSelected(); })
}
itemsToDelete={selected}
pluralizedItemName={t`Host Metrics`}
/>]}
/>}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell
sortKey="hostname">
{t`Hostname`}
</HeaderCell>
<HeaderCell
sortKey="first_automation"
tooltip={t`When was the host first automated`}>
{t`First automated`}
</HeaderCell>
<HeaderCell
sortKey="last_automation"
tooltip={t`When was the host last automated`}>
{t`Last automated`}
</HeaderCell>
<HeaderCell
sortKey="automated_counter"
tooltip={t`How many times was the host automated`}>
{t`Automation`}
</HeaderCell>
<HeaderCell
sortKey="used_in_inventories"
tooltip={t`How many inventories is the host in, recomputed on a weekly schedule`}>
{t`Inventories`}
</HeaderCell>
<HeaderCell
sortKey="deleted_counter"
tooltip={t`How many times was the host deleted`}>
{t`Deleted`}
</HeaderCell>
</HeaderRow>
}
/>
</Card>
</PageSection>
</>
);
}

View File

@ -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 (
<div>
{deleteMessages.map((message) => (
<div aria-label={message} key={message}>
{message}
</div>
))}
{deleteDetails &&
Object.entries(deleteDetails).map(([key, value]) => (
<div key={key} aria-label={`${key}: ${value}`}>
<Label>{key}</Label>
<Badge>{value}</Badge>
</div>
))}
</div>
);
};
if (deleteMessageError) {
return (
<AlertModal
isOpen={deleteMessageError}
title={t`Error!`}
onClose={() => {
toggleModal(false);
setDeleteMessageError();
}}
>
<ErrorDetail error={deleteMessageError} />
</AlertModal>
);
}
const shouldShowDeleteWarning =
warningMessage ||
(itemsToDelete.length === 1 && deleteDetails) ||
(itemsToDelete.length > 1 && deleteMessage);
return (
<>
<Tooltip content={renderTooltip()} position="top">
<div>
<Button
variant="secondary"
isLoading={isLoading}
ouiaId="delete-button"
spinnerAriaValueText={isLoading ? 'Loading' : undefined}
aria-label={t`Delete`}
onClick={() => toggleModal(true)}
isDisabled={isDisabled}
>
{t`Delete`}
</Button>
</div>
</Tooltip>
{isModalOpen && (
<AlertModal
variant="danger"
title={modalTitle}
isOpen={isModalOpen}
onClose={() => toggleModal(false)}
actions={[
<Button
ouiaId="delete-modal-confirm"
key="delete"
variant="danger"
aria-label={t`confirm delete`}
isDisabled={Boolean(
deleteDetails && itemsToDelete[0]?.type === 'credential_type'
)}
onClick={handleDelete}
>
{t`Delete`}
</Button>,
<Button
ouiaId="delete-cancel"
key="cancel"
variant="link"
aria-label={t`cancel delete`}
onClick={() => toggleModal(false)}
>
{t`Cancel`}
</Button>,
]}
>
<div>{t`This action will soft delete the following:`}</div>
{itemsToDelete.map((item) => (
<span key={item.hostname} id={`item-to-be-deleted-${item.hostname}`}>
<strong>{item.hostname}</strong>
<br />
</span>
))}
{shouldShowDeleteWarning && (
<WarningMessage
variant="warning"
isInline
title={buildDeleteWarning()}
/>
)}
</AlertModal>
)}
</>
);
}
HostMetricsDeleteButton.propTypes = {
onDelete: func.isRequired,
itemsToDelete: arrayOf(ItemToDelete).isRequired,
pluralizedItemName: string,
warningMessage: node,
};
HostMetricsDeleteButton.defaultProps = {
pluralizedItemName: 'Items',
warningMessage: null,
};
export default HostMetricsDeleteButton;

View File

@ -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 (
<Tr id={`host_metrics-row-${item.hostname}`} ouiaId={`host-metrics-row-${item.hostname}`}>
<Td />
<Td select={{ rowIndex, isSelected, onSelect}} dataLabel={t`Selected`} />
<Td dataLabel={t`Hostname`}>{item.hostname}</Td>
<Td dataLabel={t`First automation`}>{formatDateString(item.first_automation)}</Td>
<Td dataLabel={t`Last automation`}>{formatDateString(item.last_automation)}</Td>
@ -22,6 +23,8 @@ function HostMetricsListItem({ item }) {
HostMetricsListItem.propTypes = {
item: HostMetrics.isRequired,
isSelected: bool.isRequired,
onSelect: func.isRequired,
};
export default HostMetricsListItem;