Provisioning: Fix error loop in synchronise step (#115570)

* Refactor requiresMigration

* Remove InlineSecureValueWarning

* Prevent error loop

* Fix error loop

* Cleanup

* i18n
This commit is contained in:
Alex Khomenko
2025-12-19 17:57:45 +02:00
committed by GitHub
parent aa69d97f1e
commit 0e4b1c7b1e
10 changed files with 37 additions and 60 deletions
@@ -58,6 +58,7 @@ export function createOnCacheEntryAdded<Spec, Status>(resourceName: string) {
});
} catch (error) {
console.error('Error in onCacheEntryAdded:', error);
return;
}
await cacheEntryRemoved;
@@ -28,7 +28,6 @@ import { FormPrompt } from 'app/core/components/FormPrompt/FormPrompt';
import { DeleteRepositoryButton } from '../Repository/DeleteRepositoryButton';
import { TokenPermissionsInfo } from '../Shared/TokenPermissionsInfo';
import { getGitProviderFields, getLocalProviderFields } from '../Wizard/fields';
import { InlineSecureValueWarning } from '../components/InlineSecureValueWarning';
import { PROVISIONING_URL } from '../constants';
import { useCreateOrUpdateRepository } from '../hooks/useCreateOrUpdateRepository';
import { RepositoryFormData } from '../types';
@@ -178,7 +177,6 @@ export function ConfigForm({ data }: ConfigFormProps) {
</Field>
{gitFields && (
<>
<InlineSecureValueWarning repo={data} />
<Field
noMargin
label={gitFields.tokenConfig.label}
@@ -9,7 +9,6 @@ import GettingStarted from './GettingStarted/GettingStarted';
import GettingStartedPage from './GettingStarted/GettingStartedPage';
import { ConnectRepositoryButton } from './Shared/ConnectRepositoryButton';
import { RepositoryList } from './Shared/RepositoryList';
import { InlineSecureValueWarning } from './components/InlineSecureValueWarning';
import { useRepositoryList } from './hooks/useRepositoryList';
enum TabSelection {
@@ -67,7 +66,6 @@ export default function HomePage() {
actions={activeTab === TabSelection.Repositories && <ConnectRepositoryButton items={items} />}
>
<Page.Contents isLoading={isLoading}>
<InlineSecureValueWarning items={items} />
<ConfirmModal
isOpen={showDeleteModal}
title={t(
@@ -86,6 +86,11 @@ export function FinishedJobStatus({ jobUid, repositoryName, jobType, onStatusCha
};
}, [finishedQuery, job, onStatusChange, retryFailed]);
// If retry failed, return null - parent handles the error via onStatusChange
if (retryFailed) {
return null;
}
if (!job || finishedQuery.isLoading || finishedQuery.isFetching) {
return (
<Stack direction="row" alignItems="center" justifyContent="center" gap={2}>
@@ -1,8 +1,11 @@
import { useEffect } from 'react';
import { Trans, t } from '@grafana/i18n';
import { Spinner, Stack, Text } from '@grafana/ui';
import { Job, useListJobQuery } from 'app/api/clients/provisioning/v0alpha1';
import { StepStatusInfo } from '../Wizard/types';
import { getErrorMessage } from '../utils/httpUtils';
import { FinishedJobStatus } from './FinishedJobStatus';
import { JobContent } from './JobContent';
@@ -25,6 +28,18 @@ export function JobStatus({ jobType, watch, onStatusChange }: JobStatusProps) {
const activeQueryCompleted = !activeQuery.isUninitialized && !activeQuery.isLoading;
const shouldCheckFinishedJobs = activeQueryCompleted && !activeJob && !!repoLabel;
useEffect(() => {
if (activeQuery.isError) {
onStatusChange?.({
status: 'error',
error: {
title: t('provisioning.job-status.title.error-fetching-active-job', 'Error fetching active job'),
message: getErrorMessage(activeQuery.error),
},
});
}
}, [activeQuery.isError, activeQuery.error, onStatusChange]);
if (activeQuery.isLoading) {
return (
<Stack direction="row" alignItems="center" justifyContent="center" gap={2}>
@@ -37,12 +52,6 @@ export function JobStatus({ jobType, watch, onStatusChange }: JobStatusProps) {
}
if (activeQuery.isError) {
onStatusChange?.({
status: 'error',
error: {
title: t('provisioning.job-status.title.error-fetching-active-job', 'Error fetching active job'),
},
});
return null;
}
@@ -10,8 +10,8 @@ import { Page } from 'app/core/components/Page/Page';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { isNotFoundError } from 'app/features/alerting/unified/api/util';
import { InlineSecureValueWarning } from '../components/InlineSecureValueWarning';
import { PROVISIONING_URL } from '../constants';
import { getErrorMessage } from '../utils/httpUtils';
import { RepositoryActions } from './RepositoryActions';
import { RepositoryOverview } from './RepositoryOverview';
@@ -36,6 +36,7 @@ export default function RepositoryStatusPage() {
const tab = queryParams['tab'] ?? TabSelection.Overview;
const notFound = query.isError && isNotFoundError(query.error);
const hasError = query.isError && !notFound;
const tabInfo = useMemo<SelectableValue<TabSelection>>(
() => [
@@ -63,7 +64,11 @@ export default function RepositoryStatusPage() {
actions={data && <RepositoryActions repository={data} />}
>
<Page.Contents isLoading={query.isLoading}>
<InlineSecureValueWarning repo={data} />
{hasError && (
<Alert severity="error" title={t('provisioning.repository-status.error', 'Failed to load repository')}>
{getErrorMessage(query.error)}
</Alert>
)}
{notFound ? (
<EmptyState
message={t('provisioning.repository-status-page.not-found-message', 'Repository not found')}
@@ -121,22 +121,10 @@ export function useResourceStats(repoName?: string, syncTarget?: RepositoryView[
};
}, [resourceStatsQuery.data]);
// Calculate base requiresMigration: true if there are resources to migrate
const baseRequiresMigration = resourceCount > 0;
// Calculate final requiresMigration based on sync target and user selection
// For instance sync: always use baseRequiresMigration (checkbox is disabled and always true)
// Calculate requiresMigration based on sync target and user selection
// For instance sync: migrate if there are resources (checkbox is disabled and always true)
// For folder sync: only migrate if user explicitly opts in via checkbox
const requiresMigration = useMemo(() => {
if (syncTarget === 'instance') {
return baseRequiresMigration;
}
if (syncTarget === 'folder') {
return migrateResources ?? false;
}
return baseRequiresMigration;
}, [syncTarget, baseRequiresMigration, migrateResources]);
const requiresMigration = syncTarget === 'instance' ? resourceCount > 0 : (migrateResources ?? false);
const shouldSkipSync = (resourceCount === 0 || syncTarget === 'folder') && fileCount === 0;
// Format display strings
@@ -1,32 +0,0 @@
import { t } from '@grafana/i18n';
import { Alert } from '@grafana/ui';
import { Repository } from 'app/api/clients/provisioning/v0alpha1';
interface Props {
repo?: Repository;
items?: Repository[];
}
// TODO: remove this after 12.2
export function InlineSecureValueWarning({ repo, items }: Props) {
const isRepoValid = (r?: Repository) => r?.spec?.type === 'local' || !!r?.secure?.token?.name;
if (isRepoValid(repo)) {
return null;
}
// When a list is passed in, show an error if anything is missing
if (items?.every(isRepoValid)) {
return null;
}
return (
<Alert
title={t(
'provisioning.inline-secure-values-warning',
'You need to save your access tokens again due to a system update'
)}
severity="error"
/>
);
}
@@ -1,4 +1,5 @@
import { t } from '@grafana/i18n';
import { isFetchError } from '@grafana/runtime';
import { HttpError, isHttpError } from '../guards';
@@ -187,6 +188,8 @@ export function getErrorMessage(err: unknown) {
} else if (err.message) {
errorMessage = err.message;
}
} else if (isFetchError(err)) {
errorMessage = err.data.message;
}
return errorMessage;
+3 -1
View File
@@ -11985,7 +11985,6 @@
"resource-not-found": "Resource not found. Please check the URL or repository.",
"unsupported-repository-type": "Unsupported repository type: {{repositoryType}}"
},
"inline-secure-values-warning": "You need to save your access tokens again due to a system update",
"instance-sync-deprecation": {
"message": "Instance sync is currently not fully supported and breaks library panels and alerts. To use library panels and alerts, disconnect your repository and reconnect it using folder sync instead.",
"title": "Instance sync is not fully supported"
@@ -12095,6 +12094,9 @@
"webhook-last-event": "Last Event:",
"webhook-url": "View Webhook"
},
"repository-status": {
"error": "Failed to load repository"
},
"repository-status-page": {
"back-to-repositories": "Back to repositories",
"cleaning-up-resources": "Cleaning up repository resources",