fleet/frontend/pages/ManageControlsPage/Scripts/ScriptBatchDetailsPage/components/ScriptBatchHostsTable/ScriptBatchHostsTable.tsx
jacobshandling 166e5ed663
UI: Batch script run detail page (#32333)
## For #31226 

New features:
- Dynamic header for each possible state of a batch script run: Started,
Scheduled, and Finished (corresponds to tabs at
`/controls/scripts/progress`
- Unique tabs for each possible status of hosts targeted by a batch
script run: Ran, Errored, Pending, Incompatible, Canceled.
- Within each tab, sortable, paginated host results with output preview
and execution time.
- View script/run details, cancel a batch, view manage hosts page
filtered for the script batch run and a status.
- Global script batch runs activities and and Scripts progress rows now
navigate to this details page.

Cleanups and improvements:
- Expand tab count badge options using “alert”/“pending” variants across
hosts, policies, and query results.
- Misc cleanups and improvements


![ezgif-1438d4041f694f](https://github.com/user-attachments/assets/2d93127b-dea4-4ca6-abcc-7c888b2e0b93)


- [x] Changes file added for user-visible changes in `changes/`,


- [x] Updated automated tests - new tests tracked for follow-up work
- [x] QA'd all new/changed functionality manually

---------

Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
2025-08-29 09:37:05 -06:00

146 lines
4.3 KiB
TypeScript

import React, { useCallback } from "react";
import { useQuery } from "react-query";
import { InjectedRouter } from "react-router";
import { AxiosError } from "axios";
import PATHS from "router/paths";
import scriptsAPI, {
IScriptBatchHostResultsResponse,
IScriptBatchHostResultsQueryKey,
ScriptBatchHostsOrderKey,
} from "services/entities/scripts";
import { OrderDirection } from "services/entities/common";
import {
SCRIPT_BATCH_HOST_EXECUTED_STATUSES,
ScriptBatchHostStatus,
} from "interfaces/script";
import { DEFAULT_USE_QUERY_OPTIONS } from "utilities/constants";
import { getNextLocationPath } from "utilities/helpers";
import TableContainer from "components/TableContainer";
import DataError from "components/DataError";
import { ITableQueryData } from "components/TableContainer/TableContainer";
import generateColumnConfigs from "./ScriptBatchHostsTableConfig";
export const DEFAULT_SORT_DIRECTION = "asc";
export const DEFAULT_PAGE_SIZE = 20;
export const DEFAULT_SORT_COLUMN = "display_name";
const baseClass = "script-batch-hosts-table";
interface IScriptBatchHostsTableProps {
batchExecutionId: string;
selectedHostStatus: ScriptBatchHostStatus;
page: number;
orderDirection: OrderDirection;
orderKey: ScriptBatchHostsOrderKey;
setHostScriptExecutionIdForModal: (id: string) => void;
router: InjectedRouter;
}
const ScriptBatchHostsTable = ({
batchExecutionId,
selectedHostStatus,
page,
orderDirection,
orderKey,
setHostScriptExecutionIdForModal,
router,
}: IScriptBatchHostsTableProps) => {
const perPage = DEFAULT_PAGE_SIZE;
const { data: hostResults, isLoading, error } = useQuery<
IScriptBatchHostResultsResponse,
AxiosError,
IScriptBatchHostResultsResponse,
IScriptBatchHostResultsQueryKey[]
>(
[
{
scope: "script_batch_host_results",
batch_execution_id: batchExecutionId,
status: selectedHostStatus,
page,
per_page: perPage,
order_direction: orderDirection,
order_key: orderKey,
},
],
({ queryKey }) => scriptsAPI.getScriptBatchHostResults(queryKey[0]),
{
...DEFAULT_USE_QUERY_OPTIONS,
}
);
const handleRowClick = useCallback(
(row: any) => {
if (SCRIPT_BATCH_HOST_EXECUTED_STATUSES.includes(selectedHostStatus)) {
setHostScriptExecutionIdForModal(row.original.script_execution_id);
} else {
router.push(PATHS.HOST_DETAILS(row.original.id));
}
},
[router, selectedHostStatus, setHostScriptExecutionIdForModal]
);
const handleQueryChange = useCallback(
(newTableQuery: ITableQueryData) => {
const {
pageIndex: newPageIndex,
sortDirection: newOrderDirection,
sortHeader: newOrderKey,
} = newTableQuery;
const newQueryParams: { [key: string]: string | number | undefined } = {};
newQueryParams.status = selectedHostStatus;
newQueryParams.order_key = newOrderKey;
newQueryParams.order_direction = newOrderDirection;
newQueryParams.page = newPageIndex.toString();
if (newOrderKey !== orderKey || newOrderDirection !== orderDirection) {
newQueryParams.page = "0";
}
const path = getNextLocationPath({
pathPrefix: PATHS.CONTROLS_SCRIPTS_BATCH_DETAILS(batchExecutionId),
queryParams: newQueryParams,
});
router.push(path);
},
[selectedHostStatus, orderKey, orderDirection, batchExecutionId, router]
);
if (error) {
return <DataError description="Could not load host results." />;
}
const columnConfigs = generateColumnConfigs(selectedHostStatus);
return (
<div className={baseClass}>
<TableContainer
columnConfigs={columnConfigs}
data={hostResults?.hosts ?? []}
isLoading={isLoading}
defaultSortHeader={orderKey || DEFAULT_SORT_COLUMN}
defaultSortDirection={orderDirection || DEFAULT_SORT_DIRECTION}
pageIndex={page}
pageSize={perPage}
showMarkAllPages={false}
isAllPagesSelected={false}
manualSortBy
disableTableHeader
emptyComponent={() => <></>} // empty state handled by parent
disableMultiRowSelect
searchable={false}
onClickRow={handleRowClick}
onQueryChange={handleQueryChange}
/>
</div>
);
};
export default ScriptBatchHostsTable;