Provisioning: Prevent sync if repository is not healthy (#110767)

* Provisioning: Prevent sync if repository is not healthy

* Update alert position

* Lint & i18n

* Improve check

* Update message

* Remove info and add cancel button

* Add spinner

* Fix test
This commit is contained in:
Alex Khomenko
2025-09-11 12:50:26 +03:00
committed by GitHub
parent cedfbc08e0
commit 995aea281d
4 changed files with 92 additions and 48 deletions
-5
View File
@@ -3275,11 +3275,6 @@
"count": 2
}
},
"public/app/features/provisioning/Wizard/SynchronizeStep.tsx": {
"react/no-unescaped-entities": {
"count": 1
}
},
"public/app/features/query/components/QueryEditorRow.tsx": {
"@grafana/no-aria-label-selectors": {
"count": 1
@@ -358,7 +358,13 @@ export function ProvisioningWizard({ type }: { type: RepoType }) {
<div className={styles.content}>
{activeStep === 'connection' && <ConnectStep />}
{activeStep === 'bootstrap' && <BootstrapStep settingsData={data} repoName={repoName} />}
{activeStep === 'synchronize' && <SynchronizeStep isLegacyStorage={isLegacyStorage} />}
{activeStep === 'synchronize' && (
<SynchronizeStep
isLegacyStorage={isLegacyStorage}
onCancel={handleRepositoryDeletion}
isCancelling={isCancelling}
/>
)}
{activeStep === 'finish' && <FinishStep />}
</div>
@@ -1,11 +1,13 @@
import { skipToken } from '@reduxjs/toolkit/query';
import { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { Trans, t } from '@grafana/i18n';
import { Button, Text, Stack, Alert, TextLink, Field, Checkbox } from '@grafana/ui';
import { Job } from 'app/api/clients/provisioning/v0alpha1';
import { Alert, Button, Checkbox, Field, Spinner, Stack, Text, TextLink } from '@grafana/ui';
import { Job, useGetRepositoryStatusQuery } from 'app/api/clients/provisioning/v0alpha1';
import { JobStatus } from '../Job/JobStatus';
import { ProvisioningAlert } from '../Shared/ProvisioningAlert';
import { useStepStatus } from './StepStatusContext';
import { useCreateSyncJob } from './hooks/useCreateSyncJob';
@@ -14,9 +16,11 @@ import { WizardFormData } from './types';
export interface SynchronizeStepProps {
isLegacyStorage?: boolean;
onCancel?: (repoName: string) => void;
isCancelling?: boolean;
}
export function SynchronizeStep({ isLegacyStorage }: SynchronizeStepProps) {
export function SynchronizeStep({ isLegacyStorage, onCancel, isCancelling }: SynchronizeStepProps) {
const { getValues, register, watch } = useFormContext<WizardFormData>();
const { setStepStatusInfo } = useStepStatus();
const [repoName = '', repoType] = watch(['repositoryName', 'repository.type']);
@@ -29,6 +33,14 @@ export function SynchronizeStep({ isLegacyStorage }: SynchronizeStepProps) {
setStepStatusInfo,
});
const [job, setJob] = useState<Job>();
const repositoryStatusQuery = useGetRepositoryStatusQuery(repoName ? { name: repoName } : skipToken);
const {
healthy: isRepositoryHealthy,
message: repositoryHealthMessages,
checked,
} = repositoryStatusQuery?.data?.status?.health || {};
const isButtonDisabled = checked !== undefined && isRepositoryHealthy === false;
const startSynchronization = async () => {
const [history] = getValues(['migrate.history']);
@@ -38,53 +50,69 @@ export function SynchronizeStep({ isLegacyStorage }: SynchronizeStepProps) {
}
};
if (repositoryStatusQuery.isFetching) {
return <Spinner />;
}
if (job) {
return <JobStatus watch={job} onStatusChange={setStepStatusInfo} jobType="sync" />;
}
return (
<Stack direction="column" gap={3} alignItems="flex-start">
<Stack direction="column" gap={3}>
<Text color="secondary">
<Trans i18nKey="provisioning.wizard.sync-description">
Sync resources with external storage. After this one-time step, all future updates will be automatically saved
to the repository and provisioned back into the instance.
</Trans>
</Text>
<Alert
title={t(
'provisioning.wizard.alert-title',
'Important: No data or configuration will be lost, but dashboards will be temporarily unavailable for a few minutes.'
)}
severity={'info'}
>
<ul style={{ marginLeft: '16px' }}>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-1">
Resources won't be able to be created, edited, or deleted during this process. In the last step, they will
disappear.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-2">
Once provisioning is complete, resources will reappear and be managed through external storage.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-3">
The duration of this process depends on the number of resources involved.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-4">
Enterprise instance administrators can display an announcement banner to users. See{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/administration/announcement-banner/">
this guide
</TextLink>{' '}
for step-by-step instructions.
</Trans>
</li>
</ul>
</Alert>
{repositoryHealthMessages && !isRepositoryHealthy && (
<ProvisioningAlert
error={{
title: t(
'provisioning.synchronize-step.repository-unhealthy',
'The repository cannot be synchronized. Cancel provisioning and try again once the issue has been resolved. See details below.'
),
message: repositoryHealthMessages,
}}
/>
)}
{isRepositoryHealthy && (
<Alert
title={t(
'provisioning.wizard.alert-title',
'Important: No data or configuration will be lost, but dashboards will be temporarily unavailable for a few minutes.'
)}
severity={'info'}
>
<ul style={{ marginLeft: '16px' }}>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-1">
Resources won&#39;t be able to be created, edited, or deleted during this process. In the last step,
they will disappear.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-2">
Once provisioning is complete, resources will reappear and be managed through external storage.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-3">
The duration of this process depends on the number of resources involved.
</Trans>
</li>
<li>
<Trans i18nKey="provisioning.wizard.alert-point-4">
Enterprise instance administrators can display an announcement banner to users. See{' '}
<TextLink external href="https://grafana.com/docs/grafana/latest/administration/announcement-banner/">
this guide
</TextLink>{' '}
for step-by-step instructions.
</Trans>
</li>
</ul>
</Alert>
)}
{supportsHistory && (
<>
<Text element="h3">
@@ -105,9 +133,21 @@ export function SynchronizeStep({ isLegacyStorage }: SynchronizeStepProps) {
</>
)}
<Button variant="primary" onClick={startSynchronization}>
<Trans i18nKey="provisioning.wizard.button-start">Begin synchronization</Trans>
</Button>
<Field noMargin>
{isRepositoryHealthy === false ? (
<Button variant="destructive" onClick={() => onCancel?.(repoName)} disabled={isCancelling}>
{isCancelling ? (
<Trans i18nKey="provisioning.wizard.button-cancelling">Cancelling...</Trans>
) : (
<Trans i18nKey="provisioning.wizard.button-cancel">Cancel</Trans>
)}
</Button>
) : (
<Button variant="primary" onClick={startSynchronization} disabled={isButtonDisabled}>
<Trans i18nKey="provisioning.wizard.button-start">Begin synchronization</Trans>
</Button>
)}
</Field>
</Stack>
);
}
+3
View File
@@ -11655,6 +11655,7 @@
"tooltip-unhealthy-repository": "Unable to pull an unhealthy repository"
},
"synchronize-step": {
"repository-unhealthy": "The repository cannot be synchronized. Cancel provisioning and try again once the issue has been resolved. See details below.",
"synchronization-description": "Include commits for each historical value",
"synchronization-options": "Synchronization options"
},
@@ -11678,6 +11679,8 @@
"alert-point-3": "The duration of this process depends on the number of resources involved.",
"alert-point-4": "Enterprise instance administrators can display an announcement banner to users. See <2>this guide</2> for step-by-step instructions.",
"alert-title": "Important: No data or configuration will be lost, but dashboards will be temporarily unavailable for a few minutes.",
"button-cancel": "Cancel",
"button-cancelling": "Cancelling...",
"button-next": "Finish",
"button-start": "Begin synchronization",
"discard-modal": {