Use hook for step status

This commit is contained in:
Roberto Jimenez Sanchez
2025-03-11 16:54:48 +01:00
parent 360ecaa93d
commit 11962ccb07
4 changed files with 72 additions and 64 deletions
@@ -5,16 +5,15 @@ import { Alert, Stack, Text } from '@grafana/ui';
import { JobStatus } from '../JobStatus';
import { useCreateRepositoryMigrateMutation } from '../api';
import { StepStatus, useStepStatus } from '../hooks/useStepStatus';
import { WizardFormData } from './types';
export interface MigrateStepProps {
onStatusChange: (success: boolean) => void;
onRunningChange: (isRunning: boolean) => void;
onErrorChange: (error: string | null) => void;
onStepUpdate: (status: StepStatus, error?: string) => void;
}
export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }: MigrateStepProps) {
export function MigrateStep({ onStepUpdate }: MigrateStepProps) {
const [migrateRepo, migrateQuery] = useCreateRepositoryMigrateMutation();
const hasInitialized = useRef(false);
const { watch } = useFormContext<WizardFormData>();
@@ -23,8 +22,9 @@ export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }:
const history = watch('migrate.history');
const migrateName = migrateQuery.data?.metadata?.name;
const stepStatus = useStepStatus({ onStepUpdate });
useEffect(() => {
// Early return conditions
if (!repositoryName || hasInitialized.current) {
return;
}
@@ -32,19 +32,9 @@ export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }:
hasInitialized.current = true;
let isMounted = true;
const handleError = (error: unknown) => {
if (!isMounted) {
return;
}
const errorMessage = error instanceof Error ? error.message : 'Failed to start migration operation';
onErrorChange(errorMessage);
onStatusChange(false);
onRunningChange(false);
};
const startMigrate = async () => {
try {
onRunningChange(true);
stepStatus.setRunning();
const response = await migrateRepo({
name: repositoryName,
body: { identifier, history },
@@ -55,10 +45,13 @@ export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }:
}
if (!response?.metadata?.name) {
handleError(new Error('Invalid response from migration operation'));
stepStatus.setError('Invalid response from migration operation');
}
} catch (error) {
handleError(error);
if (!isMounted) {
return;
}
stepStatus.setError(error instanceof Error ? error.message : 'Failed to start migration operation');
}
};
@@ -66,7 +59,7 @@ export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }:
return () => {
isMounted = false;
};
}, [repositoryName, migrateRepo, onStatusChange, onErrorChange, onRunningChange, identifier, history]);
}, [repositoryName, migrateRepo, stepStatus, identifier, history]);
return (
<Stack direction="column" gap={2}>
@@ -82,9 +75,9 @@ export function MigrateStep({ onStatusChange, onRunningChange, onErrorChange }:
{migrateName && (
<JobStatus
name={migrateName}
onStatusChange={onStatusChange}
onRunningChange={onRunningChange}
onErrorChange={onErrorChange}
onStatusChange={(success) => (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
onErrorChange={(error) => error && stepStatus.setError(error)}
/>
)}
</Stack>
@@ -1,16 +1,17 @@
import { css } from '@emotion/css';
import { useEffect, useState, useCallback, useMemo } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm, useFormContext } from 'react-hook-form';
import { useNavigate } from 'react-router-dom-v5-compat';
import { AppEvents, GrafanaTheme2 } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Button, Stack, useStyles2, Text, Box } from '@grafana/ui';
import { Alert, Box, Button, Stack, Text, useStyles2 } from '@grafana/ui';
import { getDefaultValues } from '../ConfigForm';
import { useDeleteRepositoryMutation, useGetFrontendSettingsQuery } from '../api';
import { PROVISIONING_URL } from '../constants';
import { useCreateOrUpdateRepository } from '../hooks';
import { StepStatus } from '../hooks/useStepStatus';
import { dataToSpec } from '../utils/data';
import { BootstrapStep } from './BootstrapStep';
@@ -19,7 +20,7 @@ import { FinishStep } from './FinishStep';
import { MigrateStep } from './MigrateStep';
import { PullStep } from './PullStep';
import { RequestErrorAlert } from './RequestErrorAlert';
import { Stepper, Step } from './Stepper';
import { Step, Stepper } from './Stepper';
import { WizardFormData, WizardStep } from './types';
const steps: Array<Step<WizardStep>> = [
@@ -166,6 +167,14 @@ function WizardContent({
const [isJobRunning, setIsJobRunning] = useState(false);
const [hasError, setHasError] = useState(false);
const [stepStatus, setStepStatus] = useState<StepStatus>('idle');
const [stepError, setStepError] = useState<string | undefined>();
const handleStepUpdate = useCallback((status: StepStatus, error?: string) => {
setStepStatus(status);
setStepError(error);
}, []);
const handleJobRunningChange = (isRunning: boolean) => {
setIsJobRunning(isRunning);
};
@@ -278,12 +287,10 @@ function WizardContent({
return true;
}
// For job steps, wait for job completion and success
if (isJobStep(activeStep)) {
return isJobRunning || hasError;
return stepStatus === 'running' || stepStatus === 'error';
}
// For form steps, only disable during submission
return false;
};
@@ -318,23 +325,13 @@ function WizardContent({
onErrorChange={handleJobErrorChange}
/>
)}
{activeStep === 'migrate' && requiresMigration && (
<MigrateStep
onStatusChange={handleJobStatusChange}
onRunningChange={handleJobRunningChange}
onErrorChange={handleJobErrorChange}
/>
)}
{activeStep === 'pull' && !requiresMigration && (
<PullStep
onStatusChange={handleJobStatusChange}
onRunningChange={handleJobRunningChange}
onErrorChange={handleJobErrorChange}
/>
)}
{activeStep === 'migrate' && requiresMigration && <MigrateStep onStepUpdate={handleStepUpdate} />}
{activeStep === 'pull' && !requiresMigration && <PullStep onStepUpdate={handleStepUpdate} />}
{activeStep === 'finish' && <FinishStep />}
</div>
{stepError && <Alert severity="error" title={stepError} />}
<Stack gap={2} justifyContent="flex-end">
<Button
variant={hasError ? 'primary' : 'secondary'}
@@ -5,24 +5,24 @@ import { Stack, Text } from '@grafana/ui';
import { JobStatus } from '../JobStatus';
import { useCreateRepositorySyncMutation } from '../api';
import { StepStatus, useStepStatus } from '../hooks/useStepStatus';
import { WizardFormData } from './types';
export interface PullStepProps {
onStatusChange: (success: boolean) => void;
onRunningChange: (isRunning: boolean) => void;
onErrorChange: (error: string | null) => void;
onStepUpdate: (status: StepStatus, error?: string) => void;
}
export function PullStep({ onStatusChange, onRunningChange, onErrorChange }: PullStepProps) {
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;
const stepStatus = useStepStatus({ onStepUpdate });
useEffect(() => {
// Early return conditions
if (!repositoryName || hasInitialized.current) {
return;
}
@@ -30,19 +30,9 @@ export function PullStep({ onStatusChange, onRunningChange, onErrorChange }: Pul
hasInitialized.current = true;
let isMounted = true;
const handleError = (error: unknown) => {
if (!isMounted) {
return;
}
const errorMessage = error instanceof Error ? error.message : 'Failed to start sync operation';
onErrorChange(errorMessage);
onStatusChange(false);
onRunningChange(false);
};
const startSync = async () => {
try {
onRunningChange(true);
stepStatus.setRunning();
const response = await syncRepo({
name: repositoryName,
body: { incremental: false },
@@ -53,10 +43,13 @@ export function PullStep({ onStatusChange, onRunningChange, onErrorChange }: Pul
}
if (!response?.metadata?.name) {
handleError(new Error('Invalid response from sync operation'));
stepStatus.setError('Invalid response from sync operation');
}
} catch (error) {
handleError(error);
if (!isMounted) {
return;
}
stepStatus.setError(error instanceof Error ? error.message : 'Failed to start sync operation');
}
};
@@ -64,7 +57,7 @@ export function PullStep({ onStatusChange, onRunningChange, onErrorChange }: Pul
return () => {
isMounted = false;
};
}, [repositoryName, syncRepo, onStatusChange, onErrorChange, onRunningChange]);
}, [repositoryName, syncRepo, stepStatus]);
return (
<Stack direction="column" gap={2}>
@@ -75,9 +68,9 @@ export function PullStep({ onStatusChange, onRunningChange, onErrorChange }: Pul
{syncName && (
<JobStatus
name={syncName}
onStatusChange={onStatusChange}
onRunningChange={onRunningChange}
onErrorChange={onErrorChange}
onStatusChange={(success) => (success ? stepStatus.setSuccess() : stepStatus.setError('Job failed'))}
onRunningChange={(isRunning) => isRunning && stepStatus.setRunning()}
onErrorChange={(error) => error && stepStatus.setError(error)}
/>
)}
</Stack>
@@ -0,0 +1,25 @@
import { useCallback } from 'react';
export type StepStatus = 'idle' | 'running' | 'error' | 'success';
export interface StepStatusProps {
onStepUpdate: (status: StepStatus, error?: string) => void;
}
export interface StepStatusActions {
setRunning: () => void;
setError: (error: string) => void;
setSuccess: () => void;
}
export function useStepStatus({ onStepUpdate }: StepStatusProps): StepStatusActions {
const setRunning = useCallback(() => onStepUpdate('running'), [onStepUpdate]);
const setError = useCallback((error: string) => onStepUpdate('error', error), [onStepUpdate]);
const setSuccess = useCallback(() => onStepUpdate('success'), [onStepUpdate]);
return {
setRunning,
setError,
setSuccess,
};
}