Skip to content

Commit 392c4f6

Browse files
authored
Fix connector form cancellation logic to ensure all fields are reset (#14857)
* Add resetUiWidgetsInfo function to buildUiWidgetsContext to reset the uiWidgetsInfo to its original state * Add resetServiceForm function to service form context that both resets service form values and formik values * Update Cancel button in EditControls to avoid disabling it when form values invalid * Cleanup typing
1 parent 11448ff commit 392c4f6

File tree

5 files changed

+60
-45
lines changed

5 files changed

+60
-45
lines changed

airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,8 @@ const FormRoot: React.FC<FormRootProps> = ({
4747
hasSuccess,
4848
onStopTestingConnector,
4949
}) => {
50-
const { resetForm, dirty, isSubmitting, isValid } = useFormikContext<ServiceFormValues>();
51-
52-
const { resetUiFormProgress, isLoadingSchema, selectedService, isEditMode, formType } = useServiceForm();
50+
const { dirty, isSubmitting, isValid } = useFormikContext<ServiceFormValues>();
51+
const { resetServiceForm, isLoadingSchema, selectedService, isEditMode, formType } = useServiceForm();
5352

5453
return (
5554
<FormContainer>
@@ -70,12 +69,11 @@ const FormRoot: React.FC<FormRootProps> = ({
7069
isSubmitting={isSubmitting || isTestConnectionInProgress}
7170
errorMessage={errorMessage}
7271
formType={formType}
73-
onRetest={onRetest}
72+
onRetestClick={onRetest}
7473
isValid={isValid}
7574
dirty={dirty}
76-
resetForm={() => {
77-
resetForm();
78-
resetUiFormProgress();
75+
onCancelClick={() => {
76+
resetServiceForm();
7977
}}
8078
successMessage={successMessage}
8179
/>

airbyte-webapp/src/views/Connector/ServiceForm/ServiceForm.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,11 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
204204
[formType, props.onServiceSelect, props.availableServices, props.isEditMode, toggleOpenRequestModal]
205205
);
206206

207-
const { uiWidgetsInfo, setUiWidgetsInfo } = useBuildUiWidgetsContext(formFields, initialValues, uiOverrides);
207+
const { uiWidgetsInfo, setUiWidgetsInfo, resetUiWidgetsInfo } = useBuildUiWidgetsContext(
208+
formFields,
209+
initialValues,
210+
uiOverrides
211+
);
208212

209213
const validationSchema = useConstructValidationSchema(jsonSchema, uiWidgetsInfo);
210214

@@ -240,6 +244,7 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
240244
widgetsInfo={uiWidgetsInfo}
241245
getValues={getValues}
242246
setUiWidgetsInfo={setUiWidgetsInfo}
247+
resetUiWidgetsInfo={resetUiWidgetsInfo}
243248
formType={formType}
244249
selectedConnector={selectedConnectorDefinitionSpecification}
245250
availableServices={props.availableServices}

airbyte-webapp/src/views/Connector/ServiceForm/components/EditControls.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ interface IProps {
2525
isSubmitting: boolean;
2626
isValid: boolean;
2727
dirty: boolean;
28-
resetForm: () => void;
29-
onRetest?: () => void;
28+
onCancelClick: () => void;
29+
onRetestClick?: () => void;
3030
onCancelTesting?: () => void;
3131
isTestConnectionInProgress?: boolean;
3232
successMessage?: React.ReactNode;
@@ -38,9 +38,9 @@ const EditControls: React.FC<IProps> = ({
3838
isTestConnectionInProgress,
3939
isValid,
4040
dirty,
41-
resetForm,
41+
onCancelClick,
4242
formType,
43-
onRetest,
43+
onRetestClick,
4444
successMessage,
4545
errorMessage,
4646
onCancelTesting,
@@ -51,7 +51,7 @@ const EditControls: React.FC<IProps> = ({
5151
return <TestingConnectionSpinner isCancellable={isTestConnectionInProgress} onCancelTesting={onCancelTesting} />;
5252
}
5353

54-
const showStatusMessage = () => {
54+
const renderStatusMessage = () => {
5555
if (errorMessage) {
5656
return <TestingConnectionError errorMessage={errorMessage} />;
5757
}
@@ -63,7 +63,7 @@ const EditControls: React.FC<IProps> = ({
6363

6464
return (
6565
<>
66-
{showStatusMessage()}
66+
{renderStatusMessage()}
6767
<Controls>
6868
<div>
6969
<Button
@@ -73,13 +73,13 @@ const EditControls: React.FC<IProps> = ({
7373
<FormattedMessage id="form.saveChangesAndTest" />
7474
</Button>
7575
<ButtonContainer>
76-
<Button type="button" secondary disabled={isSubmitting || !isValid || !dirty} onClick={resetForm}>
76+
<Button type="button" secondary disabled={isSubmitting || !dirty} onClick={onCancelClick}>
7777
<FormattedMessage id="form.cancel" />
7878
</Button>
7979
</ButtonContainer>
8080
</div>
81-
{onRetest && (
82-
<Button type="button" onClick={onRetest} disabled={!isValid}>
81+
{onRetestClick && (
82+
<Button type="button" onClick={onRetestClick} disabled={!isValid}>
8383
<FormattedMessage id={`form.${formType}Retest`} />
8484
</Button>
8585
)}

airbyte-webapp/src/views/Connector/ServiceForm/serviceFormContext.tsx

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import { FeatureItem, useFeature } from "hooks/services/Feature";
88
import { ServiceFormValues } from "./types";
99
import { makeConnectionConfigurationPath, serverProvidedOauthPaths } from "./utils";
1010

11-
interface Context {
11+
interface ServiceFormContext {
1212
formType: "source" | "destination";
1313
getValues: (values: ServiceFormValues) => ServiceFormValues;
1414
widgetsInfo: WidgetConfigMap;
1515
setUiWidgetsInfo: (path: string, value: Record<string, unknown>) => void;
1616
unfinishedFlows: Record<string, { startValue: string; id: number | string }>;
1717
addUnfinishedFlow: (key: string, info?: Record<string, unknown>) => void;
1818
removeUnfinishedFlow: (key: string) => void;
19-
resetUiFormProgress: () => void;
19+
resetServiceForm: () => void;
2020
selectedService?: ConnectorDefinition;
2121
selectedConnector?: ConnectorDefinitionSpecification;
2222
isLoadingSchema?: boolean;
@@ -25,37 +25,41 @@ interface Context {
2525
authFieldsToHide: string[];
2626
}
2727

28-
const FormWidgetContext = React.createContext<Context | null>(null);
28+
const serviceFormContext = React.createContext<ServiceFormContext | null>(null);
2929

30-
const useServiceForm = (): Context => {
31-
const serviceFormHelpers = useContext(FormWidgetContext);
30+
export const useServiceForm = (): ServiceFormContext => {
31+
const serviceFormHelpers = useContext(serviceFormContext);
3232
if (!serviceFormHelpers) {
3333
throw new Error("useServiceForm should be used within ServiceFormContextProvider");
3434
}
3535
return serviceFormHelpers;
3636
};
3737

38-
const ServiceFormContextProvider: React.FC<{
38+
interface ServiceFormContextProviderProps {
3939
widgetsInfo: WidgetConfigMap;
4040
setUiWidgetsInfo: (path: string, value: Record<string, unknown>) => void;
41+
resetUiWidgetsInfo: () => void;
4142
formType: "source" | "destination";
4243
isLoadingSchema?: boolean;
4344
isEditMode?: boolean;
4445
availableServices: ConnectorDefinition[];
4546
getValues: (values: ServiceFormValues) => ServiceFormValues;
4647
selectedConnector?: ConnectorDefinitionSpecification;
47-
}> = ({
48+
}
49+
50+
export const ServiceFormContextProvider: React.FC<ServiceFormContextProviderProps> = ({
4851
availableServices,
4952
children,
5053
widgetsInfo,
5154
setUiWidgetsInfo,
55+
resetUiWidgetsInfo,
5256
selectedConnector,
5357
getValues,
5458
formType,
5559
isLoadingSchema,
5660
isEditMode,
5761
}) => {
58-
const { values } = useFormikContext<ServiceFormValues>();
62+
const { values, resetForm } = useFormikContext<ServiceFormValues>();
5963
const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector);
6064

6165
const { serviceType } = values;
@@ -81,7 +85,7 @@ const ServiceFormContextProvider: React.FC<{
8185
[selectedConnector]
8286
);
8387

84-
const ctx = useMemo<Context>(() => {
88+
const ctx = useMemo<ServiceFormContext>(() => {
8589
const unfinishedFlows = widgetsInfo["_common.unfinishedFlows"] ?? {};
8690
return {
8791
widgetsInfo,
@@ -105,7 +109,10 @@ const ServiceFormContextProvider: React.FC<{
105109
"_common.unfinishedFlows",
106110
Object.fromEntries(Object.entries(unfinishedFlows).filter(([key]) => key !== path))
107111
),
108-
resetUiFormProgress: () => setUiWidgetsInfo("_common.unfinishedFlows", {}),
112+
resetServiceForm: () => {
113+
resetForm();
114+
resetUiWidgetsInfo();
115+
},
109116
};
110117
}, [
111118
widgetsInfo,
@@ -118,9 +125,9 @@ const ServiceFormContextProvider: React.FC<{
118125
formType,
119126
isLoadingSchema,
120127
isEditMode,
128+
resetForm,
129+
resetUiWidgetsInfo,
121130
]);
122131

123-
return <FormWidgetContext.Provider value={ctx}>{children}</FormWidgetContext.Provider>;
132+
return <serviceFormContext.Provider value={ctx}>{children}</serviceFormContext.Provider>;
124133
};
125-
126-
export { useServiceForm, ServiceFormContextProvider };

airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ function upgradeSchemaLegacyAuth(
4242
);
4343
}
4444

45-
function useBuildInitialSchema(
45+
export function useBuildInitialSchema(
4646
connectorSpecification?: ConnectorDefinitionSpecification
4747
): JSONSchema7Definition | undefined {
4848
const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector);
@@ -61,13 +61,12 @@ function useBuildInitialSchema(
6161
}, [allowOAuthConnector, connectorSpecification]);
6262
}
6363

64-
function useBuildForm(
65-
jsonSchema: JSONSchema7,
66-
initialValues?: Partial<ServiceFormValues>
67-
): {
64+
export interface BuildFormHook {
6865
initialValues: ServiceFormValues;
6966
formFields: FormBlock;
70-
} {
67+
}
68+
69+
export function useBuildForm(jsonSchema: JSONSchema7, initialValues?: Partial<ServiceFormValues>): BuildFormHook {
7170
const startValues = useMemo<ServiceFormValues>(
7271
() => ({
7372
name: "",
@@ -86,14 +85,17 @@ function useBuildForm(
8685
};
8786
}
8887

89-
const useBuildUiWidgetsContext = (
88+
interface BuildUiWidgetsContextHook {
89+
uiWidgetsInfo: WidgetConfigMap;
90+
setUiWidgetsInfo: (widgetId: string, updatedValues: WidgetConfig) => void;
91+
resetUiWidgetsInfo: () => void;
92+
}
93+
94+
export const useBuildUiWidgetsContext = (
9095
formFields: FormBlock[] | FormBlock,
9196
formValues: ServiceFormValues,
9297
uiOverrides?: WidgetConfigMap
93-
): {
94-
uiWidgetsInfo: WidgetConfigMap;
95-
setUiWidgetsInfo: (widgetId: string, updatedValues: WidgetConfig) => void;
96-
} => {
98+
): BuildUiWidgetsContextHook => {
9799
const [overriddenWidgetState, setUiWidgetsInfo] = useState<WidgetConfigMap>(uiOverrides ?? {});
98100

99101
// As schema is dynamic, it is possible, that new updated values, will differ from one stored.
@@ -111,17 +113,22 @@ const useBuildUiWidgetsContext = (
111113
[mergedState, setUiWidgetsInfo]
112114
);
113115

116+
const resetUiWidgetsInfo = useCallback(() => {
117+
setUiWidgetsInfo(uiOverrides ?? {});
118+
}, [uiOverrides]);
119+
114120
return {
115121
uiWidgetsInfo: mergedState,
116122
setUiWidgetsInfo: setUiWidgetsInfoSubState,
123+
resetUiWidgetsInfo,
117124
};
118125
};
119126

120127
// As validation schema depends on what path of oneOf is currently selected in jsonschema
121-
const useConstructValidationSchema = (jsonSchema: JSONSchema7, uiWidgetsInfo: WidgetConfigMap): AnySchema =>
128+
export const useConstructValidationSchema = (jsonSchema: JSONSchema7, uiWidgetsInfo: WidgetConfigMap): AnySchema =>
122129
useMemo(() => buildYupFormForJsonSchema(jsonSchema, uiWidgetsInfo), [uiWidgetsInfo, jsonSchema]);
123130

124-
const usePatchFormik = (): void => {
131+
export const usePatchFormik = (): void => {
125132
const { setFieldTouched, isSubmitting, isValidating, validationSchema, validateForm, errors } = useFormikContext();
126133
// Formik doesn't validate values again, when validationSchema was changed on the fly.
127134
useEffect(() => {
@@ -148,5 +155,3 @@ const usePatchFormik = (): void => {
148155
}
149156
}, [errors, isSubmitting, isValidating, setFieldTouched]);
150157
};
151-
152-
export { useBuildForm, useBuildInitialSchema, useBuildUiWidgetsContext, useConstructValidationSchema, usePatchFormik };

0 commit comments

Comments
 (0)