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:
@@ -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':
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
Reference in New Issue
Block a user