From 7fa8093ee5800d11de7e2dcbcc58276753cc2ac8 Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Thu, 27 Aug 2026 13:28:14 +1000 Subject: [PATCH] port SteppedFlow polish and BigQuery JSON-on-submit onto wizard tip --- .../CreatePipeline/CreatePipelineWizard.tsx | 38 +++++++++++++--- .../ui/SteppedFlow/SteppedFlow.test.tsx | 44 +++++++++++++++++++ .../components/ui/SteppedFlow/SteppedFlow.tsx | 37 ++++++++++++---- 3 files changed, 106 insertions(+), 13 deletions(-) diff --git a/apps/studio/components/interfaces/Database/Replication/CreatePipeline/CreatePipelineWizard.tsx b/apps/studio/components/interfaces/Database/Replication/CreatePipeline/CreatePipelineWizard.tsx index 4d0aba4d2b4..e4e13ed34f2 100644 --- a/apps/studio/components/interfaces/Database/Replication/CreatePipeline/CreatePipelineWizard.tsx +++ b/apps/studio/components/interfaces/Database/Replication/CreatePipeline/CreatePipelineWizard.tsx @@ -28,7 +28,10 @@ import * as z from 'zod' import { AdvancedSettings } from '../DestinationPanel/DestinationForm/AdvancedSettings' import { getAnalyticsBucketValidationIssues } from '../DestinationPanel/DestinationForm/AnalyticsBucket/AnalyticsBucket.utils' import { AnalyticsBucketFields } from '../DestinationPanel/DestinationForm/AnalyticsBucket/Fields' -import { getBigQueryValidationIssues } from '../DestinationPanel/DestinationForm/BigQuery/BigQuery.utils' +import { + BIGQUERY_SERVICE_ACCOUNT_JSON_MESSAGE, + getBigQueryValidationIssues, +} from '../DestinationPanel/DestinationForm/BigQuery/BigQuery.utils' import { BigQueryFields } from '../DestinationPanel/DestinationForm/BigQuery/Fields' import { getClickHouseValidationIssues } from '../DestinationPanel/DestinationForm/ClickHouse/ClickHouse.utils' import { ClickHouseFields } from '../DestinationPanel/DestinationForm/ClickHouse/Fields' @@ -250,9 +253,11 @@ export const CreatePipelineWizard = () => { } if (selectedType === 'BigQuery') { - getBigQueryValidationIssues(data).forEach(({ path, message }) => { - addRequiredFieldError(path, message) - }) + getBigQueryValidationIssues(data, { validateJson: false }).forEach( + ({ path, message }) => { + addRequiredFieldError(path, message) + } + ) } else if (selectedType === 'Analytics Bucket') { getAnalyticsBucketValidationIssues(data).forEach(({ path, message }) => { addRequiredFieldError(path, message) @@ -384,6 +389,17 @@ export const CreatePipelineWizard = () => { }), } + if (selectedType === 'BigQuery') { + const jsonIssue = getBigQueryValidationIssues(data).find( + (issue) => issue.message === BIGQUERY_SERVICE_ACCOUNT_JSON_MESSAGE + ) + if (jsonIssue) { + form.setError(jsonIssue.path, { message: jsonIssue.message }) + setStep('connection') + return + } + } + const previousValidationFailures = allValidationFailures const previousWarnings = previousValidationFailures.filter((f) => f.failure_type === 'warning') const previousFailuresAreOnlyWarnings = @@ -444,7 +460,19 @@ export const CreatePipelineWizard = () => { if (step === 'connection' && selectedType) { const valid = await form.trigger(getPipelineCreateConnectionStepFieldNames(selectedType)) - if (valid) setStep('data') + if (!valid) return + + if (selectedType === 'BigQuery') { + const jsonIssue = getBigQueryValidationIssues(form.getValues()).find( + (issue) => issue.message === BIGQUERY_SERVICE_ACCOUNT_JSON_MESSAGE + ) + if (jsonIssue) { + form.setError(jsonIssue.path, { message: jsonIssue.message }) + return + } + } + + setStep('data') return } diff --git a/apps/studio/components/ui/SteppedFlow/SteppedFlow.test.tsx b/apps/studio/components/ui/SteppedFlow/SteppedFlow.test.tsx index c8704e32325..c0c65150a3d 100644 --- a/apps/studio/components/ui/SteppedFlow/SteppedFlow.test.tsx +++ b/apps/studio/components/ui/SteppedFlow/SteppedFlow.test.tsx @@ -62,6 +62,19 @@ describe('SteppedFlow', () => { expect(onStepChange).toHaveBeenCalledWith('destination') }) + test('advances to the next step when onNext is omitted', () => { + const onStepChange = vi.fn() + + customRender( + + Step body + + ) + + fireEvent.click(screen.getByRole('button', { name: 'Next' })) + expect(onStepChange).toHaveBeenCalledWith('connection') + }) + test('shows the final action on the last step instead of Next', () => { const onFinal = vi.fn() @@ -126,4 +139,35 @@ describe('SteppedFlow', () => { expect(screen.getByRole('button', { name: 'Back' })).toBeDisabled() }) + + test('disables Next while navigation is locked', () => { + customRender( + + Step body + + ) + + expect(screen.getByRole('button', { name: 'Next' })).toBeDisabled() + }) + + test('disables the final action while navigation is locked', () => { + customRender( + + Step body + + ) + + expect(screen.getByRole('button', { name: 'Create and start pipeline' })).toBeDisabled() + }) }) diff --git a/apps/studio/components/ui/SteppedFlow/SteppedFlow.tsx b/apps/studio/components/ui/SteppedFlow/SteppedFlow.tsx index c70c3f4127a..0d149df260c 100644 --- a/apps/studio/components/ui/SteppedFlow/SteppedFlow.tsx +++ b/apps/studio/components/ui/SteppedFlow/SteppedFlow.tsx @@ -75,19 +75,39 @@ export const SteppedFlow = ({ 0, steps.findIndex((step) => step.id === currentStep) ) - const isLastStep = currentIndex === steps.length - 1 + const stepCount = steps.length + const isLastStep = stepCount > 0 && currentIndex === stepCount - 1 const isFirstStep = currentIndex === 0 const currentStepLabel = steps[currentIndex]?.label const showCancel = isFirstStep && !!onCancel + const nextStepId = steps[currentIndex + 1]?.id + + const handleNext = () => { + if (onNext) { + onNext() + return + } + + if (nextStepId) { + onStepChange(nextStepId) + } + } + + if (stepCount === 0) { + return null + } return (
-

- Step {currentIndex + 1} of {steps.length} +

+ Step {currentIndex + 1} of {stepCount} {currentStepLabel ? ` ยท ${currentStepLabel}` : ''}

- + {children} 0 || showCancel ? 'justify-between' : 'justify-end')} @@ -101,7 +121,8 @@ export const SteppedFlow = ({ > Back - ) : showCancel ? ( + ) : null} + {currentIndex === 0 && showCancel ? (