Provisioning: Update recent jobs (#113509)

* Provisioning: refactor recent jobs

* Re-render only on initial load

* i18n

* Refactor init loading

* Update spinner

* upd
This commit is contained in:
Alex Khomenko
2025-11-06 17:35:23 +02:00
committed by GitHub
parent acb0320796
commit 4430699f2d
3 changed files with 78 additions and 55 deletions
@@ -1,13 +1,14 @@
import { useMemo } from 'react';
import { useMemo, useRef } from 'react';
import { intervalToAbbreviatedDurationString, TraceKeyValuePair } from '@grafana/data';
import { t, Trans } from '@grafana/i18n';
import { Alert, Badge, Box, Card, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui';
import { Badge, Box, Card, InteractiveTable, Spinner, Stack, Text } from '@grafana/ui';
import { Job, Repository } from 'app/api/clients/provisioning/v0alpha1';
import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable';
import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs';
import { getErrorMessage } from '../utils/httpUtils';
import { getStatusColor } from '../utils/repositoryStatus';
import { formatTimestamp } from '../utils/time';
@@ -23,6 +24,21 @@ type JobCell = {
};
};
function formatJobDuration(job: Job): string | null {
const interval = {
start: job.status?.started ?? 0,
end: job.status?.finished ?? Date.now(),
};
if (!interval.start) {
return null;
}
const elapsed = interval.end - interval.start;
if (elapsed < 1000) {
return `${elapsed}ms`;
}
return intervalToAbbreviatedDurationString(interval, true);
}
const getJobColumns = () => [
{
id: 'jobId',
@@ -53,20 +69,7 @@ const getJobColumns = () => [
{
id: 'duration',
header: t('provisioning.recent-jobs.column-duration', 'Duration'),
cell: ({ row: { original: job } }: JobCell) => {
const interval = {
start: job.status?.started ?? 0,
end: job.status?.finished ?? Date.now(),
};
if (!interval.start) {
return null;
}
const elapsed = interval.end - interval.start;
if (elapsed < 1000) {
return `${elapsed}ms`;
}
return intervalToAbbreviatedDurationString(interval, true);
},
cell: ({ row: { original: job } }: JobCell) => formatJobDuration(job),
},
{
id: 'message',
@@ -105,6 +108,14 @@ function ExpandedRow({ row }: ExpandedRowProps) {
return null;
}
const state = row.status?.state;
const isValidState = state && ['success', 'warning', 'error'].includes(state);
const alertProps = isValidState
? {
[state]: { message: row.status?.errors },
}
: null;
return (
<Box padding={2}>
<Stack direction="column" gap={2}>
@@ -116,13 +127,13 @@ function ExpandedRow({ row }: ExpandedRowProps) {
<KeyValuesTable data={data} />
</Stack>
)}
{hasErrors && <ProvisioningAlert error={{ message: row.status?.errors }} />}
{hasSummary && (
{alertProps && <ProvisioningAlert {...alertProps} />}
{hasSummary && row.status?.summary && (
<Stack direction="column" gap={2}>
<Text variant="body" color="secondary">
<Trans i18nKey="provisioning.expanded-row.summary">Summary</Trans>
</Text>
<JobSummary summary={row.status!.summary!} />
<JobSummary summary={row.status.summary} />
</Stack>
)}
</Stack>
@@ -140,43 +151,55 @@ function EmptyState() {
);
}
function ErrorLoading(typ: string, error: string) {
return (
<Alert
title={t('provisioning.recent-jobs.error-loading', 'Error loading {{type}}', { type: typ })}
severity="error"
>
<pre>{JSON.stringify(error)}</pre>
</Alert>
);
}
function Loading() {
return (
<Stack direction={'column'} alignItems={'center'}>
<Spinner />
</Stack>
);
}
export function RecentJobs({ repo }: Props) {
// TODO: Decide on whether we want to wait on historic jobs to show the current ones.
// Gut feeling is that current jobs are far more important to show than historic ones.
const [jobs, activeQuery, historicQuery] = useRepositoryAllJobs({
repositoryName: repo.metadata?.name ?? 'x',
});
const jobColumns = useMemo(() => getJobColumns(), []);
const hasLoadedDataRef = useRef(false);
let description: JSX.Element;
if (activeQuery.isLoading || historicQuery.isLoading) {
description = Loading();
} else if (activeQuery.isError) {
description = ErrorLoading(t('provisioning.recent-jobs.active-jobs', 'active jobs'), activeQuery.error);
// TODO: Figure out what to do if historic fails. Maybe a separate card?
} else if (!jobs?.length) {
description = <EmptyState />;
} else {
description = (
if (activeQuery.data || historicQuery.data) {
hasLoadedDataRef.current = true;
}
const renderContent = () => {
const isInitialLoading = !hasLoadedDataRef.current && (activeQuery.isLoading || historicQuery.isLoading);
if (isInitialLoading) {
return (
<Stack direction="column" alignItems="center">
<Spinner />
</Stack>
);
}
if (activeQuery.isError) {
return (
<ProvisioningAlert
error={{
title: t('provisioning.recent-jobs.error-loading-active-jobs', 'Error loading active jobs'),
message: getErrorMessage(activeQuery.error),
}}
/>
);
}
if (historicQuery.isError) {
return (
<ProvisioningAlert
error={{
title: t('provisioning.recent-jobs.error-loading-historic-jobs', 'Error loading historic jobs'),
message: getErrorMessage(historicQuery.error),
}}
/>
);
}
if (!jobs?.length) {
return <EmptyState />;
}
return (
<InteractiveTable
data={jobs}
columns={jobColumns}
@@ -185,14 +208,14 @@ export function RecentJobs({ repo }: Props) {
pageSize={10}
/>
);
}
};
return (
<Card noMargin>
<Card.Heading>
<Trans i18nKey="provisioning.recent-jobs.jobs">Jobs</Trans>
</Card.Heading>
<Card.Description>{description}</Card.Description>
<Card.Description>{renderContent()}</Card.Description>
</Card>
);
}
@@ -29,9 +29,9 @@ export const getStatusIcon = (state?: SyncStatus['state']): IconName => {
switch (state) {
case 'success':
return 'check';
case 'working':
case 'warning':
return 'exclamation-triangle';
case 'working':
case 'pending':
return 'spinner';
case 'error':
+2 -2
View File
@@ -11729,14 +11729,14 @@
"read-only-local-tooltip": "This resource is read-only and provisioned through file provisioning. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.",
"read-only-remote-tooltip": "This resource is read-only and provisioned through Git. To make any changes, update the connected repository. To modify the settings go to Administration > Provisioning > Repositories.",
"recent-jobs": {
"active-jobs": "active jobs",
"column-action": "Action",
"column-duration": "Duration",
"column-job-id": "Job ID",
"column-message": "Message",
"column-started": "Started",
"column-status": "Status",
"error-loading": "Error loading {{type}}",
"error-loading-active-jobs": "Error loading active jobs",
"error-loading-historic-jobs": "Error loading historic jobs",
"jobs": "Jobs"
},
"repository-actions": {