fleet/frontend/pages/ManageControlsPage/Scripts/cards/ScriptBatchProgress/ScriptBatchProgress.tsx

240 lines
6.7 KiB
TypeScript
Raw Normal View History

import React, { useCallback, useEffect, useRef, useState } from "react";
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
import { useQuery } from "react-query";
import { Tab, TabList, TabPanel, Tabs } from "react-tabs";
import PATHS from "router/paths";
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
import scriptsAPI, {
IScriptBatchSummaryV2,
IScriptBatchSummariesResponse,
} from "services/entities/scripts";
import { isValidScriptBatchStatus, ScriptBatchStatus } from "interfaces/script";
import { COLORS } from "styles/var/colors";
import Spinner from "components/Spinner";
import ProgressBar from "components/ProgressBar";
import SectionHeader from "components/SectionHeader";
import TabNav from "components/TabNav";
import TabText from "components/TabText";
import PaginatedList, { IPaginatedListHandle } from "components/PaginatedList";
import ListItem from "components/ListItem";
import Icon from "components/Icon/Icon";
import { IScriptsCommonProps } from "../../ScriptsNavItems";
import getWhen from "../../helpers";
const baseClass = "script-batch-progress";
const STATUS_BY_INDEX: ScriptBatchStatus[] = [
"started",
"scheduled",
"finished",
];
export const EMPTY_STATE_DETAILS: Record<ScriptBatchStatus, string> = {
started: "When a script is run on multiple hosts, progress will appear here.",
scheduled:
"When a script is scheduled to run in the future, it will appear here.",
finished:
"When a batch script is completed or canceled, historical results will appear here.",
};
const getEmptyState = (status: ScriptBatchStatus) => {
return (
<div className={`${baseClass}__empty`}>
<b>No batch scripts {status} for this team</b>
<p>{EMPTY_STATE_DETAILS[status]}</p>
</div>
);
};
export type IScriptBatchProgressProps = IScriptsCommonProps;
const ScriptBatchProgress = ({
location,
router,
teamId,
}: IScriptBatchProgressProps) => {
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
const [pageNumber, setPageNumber] = useState(0);
const paginatedListRef = useRef<IPaginatedListHandle<IScriptBatchSummaryV2>>(
null
);
const statusParam = location?.query.status;
const selectedStatus = statusParam as ScriptBatchStatus;
const DEFAULT_PAGE_SIZE = 10;
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
const queryKey = {
team_id: teamId,
status: selectedStatus,
page: pageNumber,
per_page: DEFAULT_PAGE_SIZE,
};
const { data, isFetching: updating } = useQuery<
IScriptBatchSummariesResponse,
Error,
IScriptBatchSummariesResponse
>([queryKey], () => scriptsAPI.getRunScriptBatchSummaries(queryKey), {
keepPreviousData: true,
});
const handleTabChange = useCallback(
(index: number) => {
const newStatus = STATUS_BY_INDEX[index];
const newParams = new URLSearchParams(location?.search);
newParams.set("status", newStatus);
const newQuery = newParams.toString();
router.push(
PATHS.CONTROLS_SCRIPTS_BATCH_PROGRESS.concat(
newQuery ? `?${newQuery}` : ""
)
);
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
setPageNumber(0);
},
[location?.search, router]
);
const onClickRow = (r: IScriptBatchSummaryV2) => {
// explicitly including the status param here avoids triggering the script details page's effect
// which would add it automatically, muddying browser history and preventing smooth forward/back navigation
router.push(
PATHS.CONTROLS_SCRIPTS_BATCH_DETAILS(r.batch_execution_id).concat(
"?status=ran"
)
);
return r;
};
const renderRow = (summary: IScriptBatchSummaryV2) => {
const {
script_name,
targeted_host_count,
ran_host_count,
errored_host_count,
} = summary;
const when = getWhen(summary);
return (
<>
<ListItem title={script_name} details={when} />
{summary.status !== "scheduled" && (
<div className={`${baseClass}__row-right`}>
<div className={`${baseClass}__status-count`}>
{ran_host_count + errored_host_count} / {targeted_host_count}{" "}
hosts
</div>
<ProgressBar
sections={[
{
// results
color: COLORS["status-success"],
portion: ran_host_count / targeted_host_count,
},
{
// errors
color: COLORS["status-error"],
portion: errored_host_count / targeted_host_count,
},
]}
/>
<div className={`${baseClass}__row-errors`}>
<Icon
name="error-outline"
color="ui-fleet-black-50"
size="small"
/>{" "}
<div>{errored_host_count}</div>
</div>
</div>
)}
</>
);
};
// Reset to first tab if status is invalid.
useEffect(() => {
if (!isValidScriptBatchStatus(statusParam)) {
handleTabChange(0);
}
}, [statusParam, handleTabChange]);
const renderTabContent = (status: ScriptBatchStatus) => {
// If we're switching to a new tab, show the loading spinner
// while we get the first page and # of results.
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
if (updating) {
return (
<div className={`${baseClass}__loading`}>
<Spinner />
</div>
);
}
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
const count = data?.count || 0;
const rows = data?.batch_executions || [];
if (count === 0) {
return getEmptyState(status);
}
return (
<div className={`${baseClass}__tab-content`}>
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
{!updating && count && (
<div className={`${baseClass}__status-count`}>
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
{count} batch script{count > 1 ? "s" : ""}
</div>
)}
<PaginatedList<IScriptBatchSummaryV2>
ref={paginatedListRef}
Refactor paginated list to use UseQuery (#33669) # Details As mentioned in a previous front-end sync, I realized after having to add a `reload()` method to the `PaginatedList` imperative handle that I had strayed too far from the path. The original concept for this component was for it to be fully self-contained, so the parent didn't have to concern itself with pagination at all other than being told what page to load. But the addition of an `onChangePage` property isn't a tragedy and is totally worth the reduction in code, consistency with use of `useQuery` elsewhere and React best practice of passing data from parent to child. This refactor still retains the use of the imperative handle for querying the "dirty state" of the list, so parents don't have to manage that state themselves. ## Testing - [X] Added/updated automated tests Updated PaginatedList tests as needed. Also confirmed that tests for the upstream components (PoliciesPaginatedList, Secrets, RunBatchScriptModal and ScriptBatchProgress) all passed without any modification. - [X] QA'd all new/changed functionality manually Added enough data to paginate each of the components that uses PaginatedList, confirmed pagination still works and dirty state functionality (in PoliciesPaginatedList) still works. Also confirmed add/delete functionality works as expected in Secrets. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Consistent, responsive pagination across Scripts, Secrets, Hosts, and Policies pages with clearer loading and empty states. - Bug Fixes - Delete Secret modal now reliably displays errors and refreshes the list after deletion. - Refactor - Unified data-driven pagination flow for improved performance and smoother navigation. - Simplified list interactions by removing manual reloads. - Tests - Updated tests to align with the new pagination behavior and data flow. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-10-28 16:27:05 +00:00
count={count}
data={rows}
pageSize={DEFAULT_PAGE_SIZE}
currentPage={pageNumber}
onChangePage={setPageNumber}
isLoading={updating}
onClickRow={onClickRow}
renderItemRow={renderRow}
useCheckBoxes={false}
/>
</div>
);
};
return (
<>
<div className={baseClass}>
<SectionHeader title="Batch progress" alignLeftHeaderVertically />
2025-09-29 17:10:41 +00:00
<TabNav secondary>
<Tabs
selectedIndex={STATUS_BY_INDEX.indexOf(selectedStatus)}
onSelect={handleTabChange}
>
<TabList>
<Tab>
<TabText>Started</TabText>
</Tab>
<Tab>
<TabText>Scheduled</TabText>
</Tab>
<Tab>
<TabText>Finished</TabText>
</Tab>
</TabList>
<TabPanel>{renderTabContent(STATUS_BY_INDEX[0])}</TabPanel>
<TabPanel>{renderTabContent(STATUS_BY_INDEX[1])}</TabPanel>
<TabPanel>{renderTabContent(STATUS_BY_INDEX[2])}</TabPanel>
</Tabs>
</TabNav>
</div>
</>
);
};
export default ScriptBatchProgress;