Use hook for step status
This commit is contained in:
@@ -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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user