JobStep component

This commit is contained in:
Roberto Jimenez Sanchez
2025-03-11 17:22:08 +01:00
parent 11962ccb07
commit b204a85683
4 changed files with 152 additions and 131 deletions
+4 -4
View File
@@ -5899,9 +5899,12 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "21"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "22"]
],
"public/app/features/provisioning/Wizard/JobStep.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
"public/app/features/provisioning/Wizard/MigrateStep.tsx:5381": [
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "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 <Trans />", "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 <Trans />", "0"]
],
"public/app/features/provisioning/Wizard/Stepper.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
],
@@ -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<WizardFormData>();
const repositoryName = watch('repositoryName');
const stepStatus = useStepStatus({ onStepUpdate });
const [jobName, setJobName] = useState<string>();
// 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 (
<Stack direction="column" gap={2}>
{description && <Text color="secondary">{description}</Text>}
{children}
<Box>
{showSpinner && (
<Stack direction="row" alignItems="center" gap={2}>
<Spinner size={24} />
<Text element="h4" weight="bold">
Starting...
</Text>
</Stack>
)}
{job && (
<JobStatus
name={jobName!}
onStatusChange={(success) => {
if (success) {
stepStatus.setSuccess();
} else {
stepStatus.setError('Job failed');
}
}}
onRunningChange={(isRunning) => {
if (isRunning) {
stepStatus.setRunning();
}
}}
onErrorChange={(error) => {
if (error) {
stepStatus.setError(error);
}
}}
/>
)}
</Box>
</Stack>
);
}
@@ -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<WizardFormData>();
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 (
<Stack direction="column" gap={2}>
<Text color="secondary">
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.
</Text>
<JobStep
onStepUpdate={onStepUpdate}
description="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."
startJob={startMigration}
>
<Alert severity="info" title="Note">
Dashboards will be unavailable while running this process.
</Alert>
{migrateName && (
<JobStatus
name={migrateName}
onStatusChange={(success) => (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
onErrorChange={(error) => error && stepStatus.setError(error)}
/>
)}
</Stack>
</JobStep>
);
}
@@ -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<WizardFormData>();
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 (
<Stack direction="column" gap={2}>
<Text color="secondary">
Pulling all resources from your repository to this Grafana instance. After this initial pull, all future updates
from the repository will be automatically synchronized.
</Text>
{syncName && (
<JobStatus
name={syncName}
onStatusChange={(success) => (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
onErrorChange={(error) => error && stepStatus.setError(error)}
/>
)}
</Stack>
<JobStep
onStepUpdate={onStepUpdate}
description={description || 'Pulling repository content...'}
startJob={startSync}
/>
);
}