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 { intervalToAbbreviatedDurationString, TraceKeyValuePair } from '@grafana/data';
import { t, Trans } from '@grafana/i18n'; 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 { Job, Repository } from 'app/api/clients/provisioning/v0alpha1';
import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable'; import KeyValuesTable from 'app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/KeyValuesTable';
import { ProvisioningAlert } from '../Shared/ProvisioningAlert'; import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs'; import { useRepositoryAllJobs } from '../hooks/useRepositoryAllJobs';
import { getErrorMessage } from '../utils/httpUtils';
import { getStatusColor } from '../utils/repositoryStatus'; import { getStatusColor } from '../utils/repositoryStatus';
import { formatTimestamp } from '../utils/time'; 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 = () => [ const getJobColumns = () => [
{ {
id: 'jobId', id: 'jobId',
@@ -53,20 +69,7 @@ const getJobColumns = () => [
{ {
id: 'duration', id: 'duration',
header: t('provisioning.recent-jobs.column-duration', 'Duration'), header: t('provisioning.recent-jobs.column-duration', 'Duration'),
cell: ({ row: { original: job } }: JobCell) => { cell: ({ row: { original: job } }: JobCell) => formatJobDuration(job),
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);
},
}, },
{ {
id: 'message', id: 'message',
@@ -105,6 +108,14 @@ function ExpandedRow({ row }: ExpandedRowProps) {
return null; 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 ( return (
<Box padding={2}> <Box padding={2}>
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
@@ -116,13 +127,13 @@ function ExpandedRow({ row }: ExpandedRowProps) {
<KeyValuesTable data={data} /> <KeyValuesTable data={data} />
</Stack> </Stack>
)} )}
{hasErrors && <ProvisioningAlert error={{ message: row.status?.errors }} />} {alertProps && <ProvisioningAlert {...alertProps} />}
{hasSummary && ( {hasSummary && row.status?.summary && (
<Stack direction="column" gap={2}> <Stack direction="column" gap={2}>
<Text variant="body" color="secondary"> <Text variant="body" color="secondary">
<Trans i18nKey="provisioning.expanded-row.summary">Summary</Trans> <Trans i18nKey="provisioning.expanded-row.summary">Summary</Trans>
</Text> </Text>
<JobSummary summary={row.status!.summary!} /> <JobSummary summary={row.status.summary} />
</Stack> </Stack>
)} )}
</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) { 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({ const [jobs, activeQuery, historicQuery] = useRepositoryAllJobs({
repositoryName: repo.metadata?.name ?? 'x', repositoryName: repo.metadata?.name ?? 'x',
}); });
const jobColumns = useMemo(() => getJobColumns(), []); const jobColumns = useMemo(() => getJobColumns(), []);
const hasLoadedDataRef = useRef(false);
let description: JSX.Element; if (activeQuery.data || historicQuery.data) {
if (activeQuery.isLoading || historicQuery.isLoading) { hasLoadedDataRef.current = true;
description = Loading(); }
} else if (activeQuery.isError) {
description = ErrorLoading(t('provisioning.recent-jobs.active-jobs', 'active jobs'), activeQuery.error); const renderContent = () => {
// TODO: Figure out what to do if historic fails. Maybe a separate card? const isInitialLoading = !hasLoadedDataRef.current && (activeQuery.isLoading || historicQuery.isLoading);
} else if (!jobs?.length) {
description = <EmptyState />; if (isInitialLoading) {
} else { return (
description = ( <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 <InteractiveTable
data={jobs} data={jobs}
columns={jobColumns} columns={jobColumns}
@@ -185,14 +208,14 @@ export function RecentJobs({ repo }: Props) {
pageSize={10} pageSize={10}
/> />
); );
} };
return ( return (
<Card noMargin> <Card noMargin>
<Card.Heading> <Card.Heading>
<Trans i18nKey="provisioning.recent-jobs.jobs">Jobs</Trans> <Trans i18nKey="provisioning.recent-jobs.jobs">Jobs</Trans>
</Card.Heading> </Card.Heading>
<Card.Description>{description}</Card.Description> <Card.Description>{renderContent()}</Card.Description>
</Card> </Card>
); );
} }
@@ -29,9 +29,9 @@ export const getStatusIcon = (state?: SyncStatus['state']): IconName => {
switch (state) { switch (state) {
case 'success': case 'success':
return 'check'; return 'check';
case 'working':
case 'warning': case 'warning':
return 'exclamation-triangle'; return 'exclamation-triangle';
case 'working':
case 'pending': case 'pending':
return 'spinner'; return 'spinner';
case 'error': 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-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.", "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": { "recent-jobs": {
"active-jobs": "active jobs",
"column-action": "Action", "column-action": "Action",
"column-duration": "Duration", "column-duration": "Duration",
"column-job-id": "Job ID", "column-job-id": "Job ID",
"column-message": "Message", "column-message": "Message",
"column-started": "Started", "column-started": "Started",
"column-status": "Status", "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" "jobs": "Jobs"
}, },
"repository-actions": { "repository-actions": {