1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-06 14:25:31 +02:00

fix(app/edge-jobs): edge job results page crash at scale (#954)

This commit is contained in:
LP B 2025-08-04 17:10:46 +02:00 committed by GitHub
parent d306d7a983
commit a472de1919
27 changed files with 2595 additions and 107 deletions

View file

@ -1,18 +1,16 @@
import { List } from 'lucide-react';
import { useMemo } from 'react';
import { Environment } from '@/react/portainer/environments/types';
import { useEnvironmentList } from '@/react/portainer/environments/queries';
import { queryOptionsFromTableState } from '@/react/common/api/listQueryParams';
import { Datatable } from '@@/datatables';
import { useTableState } from '@@/datatables/useTableState';
import { withMeta } from '@@/datatables/extend-options/withMeta';
import { mergeOptions } from '@@/datatables/extend-options/mergeOptions';
import { EdgeJob, JobResult, LogsStatus } from '../../types';
import { EdgeJob, LogsStatus } from '../../types';
import { useJobResults } from '../../queries/jobResults/useJobResults';
import { columns } from './columns';
import { columns, sortOptions } from './columns';
import { createStore } from './datatable-store';
const tableKey = 'edge-job-results';
@ -22,8 +20,9 @@ export function ResultsDatatable({ jobId }: { jobId: EdgeJob['Id'] }) {
const tableState = useTableState(store, tableKey);
const jobResultsQuery = useJobResults(jobId, {
...queryOptionsFromTableState({ ...tableState }, sortOptions),
refetchInterval(dataset) {
const anyCollecting = dataset?.some(
const anyCollecting = dataset?.data.some(
(r) => r.LogsStatus === LogsStatus.Pending
);
@ -35,40 +34,17 @@ export function ResultsDatatable({ jobId }: { jobId: EdgeJob['Id'] }) {
},
});
const environmentIds = jobResultsQuery.data?.map(
(result) => result.EndpointId
);
const environmentsQuery = useEnvironmentList(
{ endpointIds: environmentIds },
{ enabled: !!environmentIds && !jobResultsQuery.isLoading }
);
const dataset = useMemo(
() =>
jobResultsQuery.isLoading || environmentsQuery.isLoading
? []
: associateEndpointsToResults(
jobResultsQuery.data || [],
environmentsQuery.environments
),
[
environmentsQuery.environments,
environmentsQuery.isLoading,
jobResultsQuery.data,
jobResultsQuery.isLoading,
]
);
const dataset = jobResultsQuery.data?.data || [];
return (
<Datatable
disableSelect
columns={columns}
dataset={dataset}
isLoading={jobResultsQuery.isLoading || environmentsQuery.isLoading}
title="Results"
titleIcon={List}
columns={columns}
disableSelect
dataset={dataset}
settingsManager={tableState}
isLoading={jobResultsQuery.isLoading}
extendTableOptions={mergeOptions(
withMeta({
table: 'edge-job-results',
@ -76,21 +52,11 @@ export function ResultsDatatable({ jobId }: { jobId: EdgeJob['Id'] }) {
})
)}
data-cy="edge-job-results-datatable"
isServerSidePagination
page={tableState.page}
onPageChange={tableState.setPage}
onSearchChange={() => tableState.setPage(0)}
totalCount={jobResultsQuery.data?.totalCount || 0}
/>
);
}
function associateEndpointsToResults(
results: Array<JobResult>,
environments: Array<Environment>
) {
return results.map((result) => {
const environment = environments.find(
(environment) => environment.Id === result.EndpointId
);
return {
...result,
Endpoint: environment,
};
});
}

View file

@ -1,18 +1,20 @@
import { CellContext, createColumnHelper } from '@tanstack/react-table';
import { sortOptionsFromColumns } from '@/react/common/api/sort.types';
import { Button } from '@@/buttons';
import { LogsStatus } from '../../types';
import { JobResult, LogsStatus } from '../../types';
import { useDownloadLogsMutation } from '../../queries/jobResults/useDownloadLogsMutation';
import { useClearLogsMutation } from '../../queries/jobResults/useClearLogsMutation';
import { useCollectLogsMutation } from '../../queries/jobResults/useCollectLogsMutation';
import { DecoratedJobResult, getTableMeta } from './types';
import { getTableMeta } from './types';
const columnHelper = createColumnHelper<DecoratedJobResult>();
const columnHelper = createColumnHelper<JobResult>();
export const columns = [
columnHelper.accessor('Endpoint.Name', {
columnHelper.accessor('EndpointName', {
header: 'Environment',
meta: {
className: 'w-1/2',
@ -30,7 +32,7 @@ export const columns = [
function ActionsCell({
row: { original: item },
table,
}: CellContext<DecoratedJobResult, unknown>) {
}: CellContext<JobResult, unknown>) {
const tableMeta = getTableMeta(table.options.meta);
const id = tableMeta.jobId;
@ -51,13 +53,13 @@ function ActionsCell({
<>
<Button
onClick={() => downloadLogsMutation.mutate(item.EndpointId)}
data-cy={`edge-job-download-logs-${item.Endpoint?.Name}`}
data-cy={`edge-job-download-logs-${item.EndpointName}`}
>
Download logs
</Button>
<Button
onClick={() => clearLogsMutations.mutate(item.EndpointId)}
data-cy={`edge-job-clear-logs-${item.Endpoint?.Name}`}
data-cy={`edge-job-clear-logs-${item.EndpointName}`}
>
Clear logs
</Button>
@ -68,10 +70,12 @@ function ActionsCell({
return (
<Button
onClick={() => collectLogsMutation.mutate(item.EndpointId)}
data-cy={`edge-job-retrieve-logs-${item.Endpoint?.Name}`}
data-cy={`edge-job-retrieve-logs-${item.EndpointName}`}
>
Retrieve logs
</Button>
);
}
}
export const sortOptions = sortOptionsFromColumns(columns);

View file

@ -3,12 +3,18 @@ import {
createPersistedStore,
BasicTableSettings,
RefreshableTableSettings,
BackendPaginationTableSettings,
backendPaginationSettings,
} from '@@/datatables/types';
interface TableSettings extends BasicTableSettings, RefreshableTableSettings {}
interface TableSettings
extends BasicTableSettings,
RefreshableTableSettings,
BackendPaginationTableSettings {}
export function createStore(storageKey: string) {
return createPersistedStore<TableSettings>(storageKey, undefined, (set) => ({
...refreshableSettings(set),
...backendPaginationSettings(set),
}));
}

View file

@ -1,10 +1,4 @@
import { Environment } from '@/react/portainer/environments/types';
import { EdgeJob, JobResult } from '../../types';
export interface DecoratedJobResult extends JobResult {
Endpoint?: Environment;
}
import { EdgeJob } from '../../types';
interface TableMeta {
table: 'edge-job-results';