port SteppedFlow polish and BigQuery JSON-on-submit onto wizard tip

This commit is contained in:
Danny White committed 2026-08-27 13:28:14 +10:00
1 parent 7fb29e8192
commit 7fa8093ee5
3 files changed
+106 -13

No files matched your search

@@ -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
}
@@ -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(
<SteppedFlow steps={steps} currentStep="destination" onStepChange={onStepChange}>
Step body
</SteppedFlow>
)
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(
<SteppedFlow
steps={steps}
currentStep="destination"
onStepChange={vi.fn()}
navigationDisabled
>
Step body
</SteppedFlow>
)
expect(screen.getByRole('button', { name: 'Next' })).toBeDisabled()
})
test('disables the final action while navigation is locked', () => {
customRender(
<SteppedFlow
steps={steps}
currentStep="review"
onStepChange={vi.fn()}
navigationDisabled
finalAction={{ label: 'Create and start pipeline' }}
>
Step body
</SteppedFlow>
)
expect(screen.getByRole('button', { name: 'Create and start pipeline' })).toBeDisabled()
})
})
@@ -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 (
<div className="flex min-h-full flex-col">
<div className="mx-auto w-full max-w-[760px] flex-1 px-6 pt-8 pb-10">
<p className="mb-4 text-xs text-foreground-lighter">
Step {currentIndex + 1} of {steps.length}
<p className="mb-4 text-xs text-foreground-lighter" role="status">
Step {currentIndex + 1} of {stepCount}
{currentStepLabel ? ` · ${currentStepLabel}` : ''}
</p>
<Card key={currentStep} className="animate-in fade-in-0 duration-200">
<Card
key={currentStep}
className="animate-in fade-in-0 duration-200 motion-reduce:animate-none"
>
{children}
<CardFooter
className={cn(currentIndex > 0 || showCancel ? 'justify-between' : 'justify-end')}
@@ -101,7 +121,8 @@ export const SteppedFlow = ({
>
Back
</Button>
) : showCancel ? (
) : null}
{currentIndex === 0 && showCancel ? (
<Button
type="button"
variant="default"
@@ -118,7 +139,7 @@ export const SteppedFlow = ({
form={finalAction.form}
variant="primary"
loading={finalAction.loading}
disabled={finalAction.disabled}
disabled={navigationDisabled || finalAction.disabled}
onClick={finalAction.onClick}
>
{finalAction.label}
@@ -127,9 +148,9 @@ export const SteppedFlow = ({
<Button
type="button"
variant="primary"
disabled={nextDisabled}
disabled={navigationDisabled || nextDisabled || !nextStepId}
loading={nextLoading}
onClick={onNext}
onClick={handleNext}
>
{nextLabel}
</Button>