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