diff --git a/.betterer.results b/.betterer.results
index 0f785f57c76..e67e21cfcb1 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -5899,9 +5899,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "21"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "22"]
],
+ "public/app/features/provisioning/Wizard/JobStep.tsx:5381": [
+ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
+ ],
"public/app/features/provisioning/Wizard/MigrateStep.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "0"],
- [0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
+ [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with ", "2"]
],
"public/app/features/provisioning/Wizard/ProvisioningWizard.tsx:5381": [
@@ -5909,9 +5912,6 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with ", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
- "public/app/features/provisioning/Wizard/PullStep.tsx:5381": [
- [0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
- ],
"public/app/features/provisioning/Wizard/Stepper.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with ", "0"]
],
diff --git a/public/app/features/provisioning/Wizard/JobStep.tsx b/public/app/features/provisioning/Wizard/JobStep.tsx
new file mode 100644
index 00000000000..73c60755e55
--- /dev/null
+++ b/public/app/features/provisioning/Wizard/JobStep.tsx
@@ -0,0 +1,111 @@
+import { skipToken } from '@reduxjs/toolkit/query';
+import { ReactNode, useEffect, useRef, useState } from 'react';
+import { useFormContext } from 'react-hook-form';
+
+import { Box, Spinner, Stack, Text } from '@grafana/ui';
+
+import { JobStatus } from '../JobStatus';
+import { useListJobQuery } from '../api';
+import { StepStatus, useStepStatus } from '../hooks/useStepStatus';
+
+import { WizardFormData } from './types';
+
+interface JobStepProps {
+ onStepUpdate: (status: StepStatus, error?: string) => void;
+ description: ReactNode;
+ startJob: (repositoryName: string) => Promise<{ metadata?: { name?: string } }>;
+ children?: ReactNode;
+}
+
+export type { JobStepProps };
+
+export function JobStep({ onStepUpdate, description, startJob, children }: JobStepProps) {
+ const hasInitialized = useRef(false);
+ const { watch } = useFormContext();
+ const repositoryName = watch('repositoryName');
+ const stepStatus = useStepStatus({ onStepUpdate });
+ const [jobName, setJobName] = useState();
+
+ // Query the job status if we have a job name
+ const jobQuery = useListJobQuery(jobName ? { watch: true, fieldSelector: `metadata.name=${jobName}` } : skipToken);
+
+ useEffect(() => {
+ if (!repositoryName || hasInitialized.current) {
+ return;
+ }
+
+ hasInitialized.current = true;
+ let isMounted = true;
+
+ const executeJob = async () => {
+ try {
+ if (!isMounted) {
+ return;
+ }
+
+ stepStatus.setRunning();
+ const response = await startJob(repositoryName);
+
+ if (!response?.metadata?.name) {
+ stepStatus.setError('Invalid response from operation');
+ return;
+ }
+ setJobName(response.metadata.name);
+ } catch (error) {
+ if (!isMounted) {
+ return;
+ }
+ stepStatus.setError(error instanceof Error ? error.message : 'Failed to start operation');
+ }
+ };
+
+ executeJob();
+ return () => {
+ isMounted = false;
+ };
+ }, [repositoryName, startJob, stepStatus]);
+
+ const job = jobQuery.data?.items?.[0];
+ const showSpinner = !job;
+
+ return (
+
+ {description && {description}}
+ {children}
+
+
+ {showSpinner && (
+
+
+
+ Starting...
+
+
+ )}
+
+ {job && (
+ {
+ if (success) {
+ stepStatus.setSuccess();
+ } else {
+ stepStatus.setError('Job failed');
+ }
+ }}
+ onRunningChange={(isRunning) => {
+ if (isRunning) {
+ stepStatus.setRunning();
+ }
+ }}
+ onErrorChange={(error) => {
+ if (error) {
+ stepStatus.setError(error);
+ }
+ }}
+ />
+ )}
+
+
+ );
+}
diff --git a/public/app/features/provisioning/Wizard/MigrateStep.tsx b/public/app/features/provisioning/Wizard/MigrateStep.tsx
index b67394af3e1..bb9839f3649 100644
--- a/public/app/features/provisioning/Wizard/MigrateStep.tsx
+++ b/public/app/features/provisioning/Wizard/MigrateStep.tsx
@@ -1,12 +1,11 @@
-import { useEffect, useRef } from 'react';
import { useFormContext } from 'react-hook-form';
-import { Alert, Stack, Text } from '@grafana/ui';
+import { Alert } from '@grafana/ui';
-import { JobStatus } from '../JobStatus';
import { useCreateRepositoryMigrateMutation } from '../api';
-import { StepStatus, useStepStatus } from '../hooks/useStepStatus';
+import { StepStatus } from '../hooks/useStepStatus';
+import { JobStep } from './JobStep';
import { WizardFormData } from './types';
export interface MigrateStepProps {
@@ -14,72 +13,30 @@ export interface MigrateStepProps {
}
export function MigrateStep({ onStepUpdate }: MigrateStepProps) {
- const [migrateRepo, migrateQuery] = useCreateRepositoryMigrateMutation();
- const hasInitialized = useRef(false);
+ const [migrateRepo] = useCreateRepositoryMigrateMutation();
const { watch } = useFormContext();
- const repositoryName = watch('repositoryName');
const identifier = watch('migrate.identifier');
const history = watch('migrate.history');
- const migrateName = migrateQuery.data?.metadata?.name;
- const stepStatus = useStepStatus({ onStepUpdate });
+ const startMigration = async (repositoryName: string) => {
+ const response = await migrateRepo({
+ name: repositoryName,
+ body: { identifier, history },
+ }).unwrap();
- useEffect(() => {
- if (!repositoryName || hasInitialized.current) {
- return;
- }
-
- hasInitialized.current = true;
- let isMounted = true;
-
- const startMigrate = async () => {
- try {
- stepStatus.setRunning();
- const response = await migrateRepo({
- name: repositoryName,
- body: { identifier, history },
- }).unwrap();
-
- if (!isMounted) {
- return;
- }
-
- if (!response?.metadata?.name) {
- stepStatus.setError('Invalid response from migration operation');
- }
- } catch (error) {
- if (!isMounted) {
- return;
- }
- stepStatus.setError(error instanceof Error ? error.message : 'Failed to start migration operation');
- }
- };
-
- startMigrate();
- return () => {
- isMounted = false;
- };
- }, [repositoryName, migrateRepo, stepStatus, identifier, history]);
+ return response;
+ };
return (
-
-
- Migrating all dashboards from this instance to your repository, including their identifiers and complete
- history. After this one-time migration, all future updates will be automatically saved to the repository.
-
-
+
Dashboards will be unavailable while running this process.
-
- {migrateName && (
- (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
- onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
- onErrorChange={(error) => error && stepStatus.setError(error)}
- />
- )}
-
+
);
}
diff --git a/public/app/features/provisioning/Wizard/PullStep.tsx b/public/app/features/provisioning/Wizard/PullStep.tsx
index f85e47dc89a..3d7f1d1791a 100644
--- a/public/app/features/provisioning/Wizard/PullStep.tsx
+++ b/public/app/features/provisioning/Wizard/PullStep.tsx
@@ -1,78 +1,31 @@
-import { useEffect, useRef } from 'react';
-import { useFormContext } from 'react-hook-form';
+import { ReactNode } from 'react';
-import { Stack, Text } from '@grafana/ui';
-
-import { JobStatus } from '../JobStatus';
import { useCreateRepositorySyncMutation } from '../api';
-import { StepStatus, useStepStatus } from '../hooks/useStepStatus';
+import { StepStatus } from '../hooks/useStepStatus';
-import { WizardFormData } from './types';
+import { JobStep } from './JobStep';
-export interface PullStepProps {
+interface PullStepProps {
onStepUpdate: (status: StepStatus, error?: string) => void;
+ description?: ReactNode;
}
-export function PullStep({ onStepUpdate }: PullStepProps) {
- const [syncRepo, syncQuery] = useCreateRepositorySyncMutation();
- const hasInitialized = useRef(false);
- const { watch } = useFormContext();
- const repositoryName = watch('repositoryName');
- const syncName = syncQuery.data?.metadata?.name;
+export function PullStep({ onStepUpdate, description }: PullStepProps) {
+ const [syncRepo] = useCreateRepositorySyncMutation();
- const stepStatus = useStepStatus({ onStepUpdate });
-
- useEffect(() => {
- if (!repositoryName || hasInitialized.current) {
- return;
- }
-
- hasInitialized.current = true;
- let isMounted = true;
-
- const startSync = async () => {
- try {
- stepStatus.setRunning();
- const response = await syncRepo({
- name: repositoryName,
- body: { incremental: false },
- }).unwrap();
-
- if (!isMounted) {
- return;
- }
-
- if (!response?.metadata?.name) {
- stepStatus.setError('Invalid response from sync operation');
- }
- } catch (error) {
- if (!isMounted) {
- return;
- }
- stepStatus.setError(error instanceof Error ? error.message : 'Failed to start sync operation');
- }
- };
-
- startSync();
- return () => {
- isMounted = false;
- };
- }, [repositoryName, syncRepo, stepStatus]);
+ const startSync = async (repositoryName: string) => {
+ const response = await syncRepo({
+ name: repositoryName,
+ body: { incremental: false },
+ }).unwrap();
+ return response;
+ };
return (
-
-
- Pulling all resources from your repository to this Grafana instance. After this initial pull, all future updates
- from the repository will be automatically synchronized.
-
- {syncName && (
- (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
- onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
- onErrorChange={(error) => error && stepStatus.setError(error)}
- />
- )}
-
+
);
}