Skip to content

Commit

Permalink
Fix connector form cancellation logic to ensure all fields are reset (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
edmundito authored Aug 3, 2022
1 parent 11448ff commit 392c4f6
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 45 deletions.
12 changes: 5 additions & 7 deletions airbyte-webapp/src/views/Connector/ServiceForm/FormRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,8 @@ const FormRoot: React.FC<FormRootProps> = ({
hasSuccess,
onStopTestingConnector,
}) => {
const { resetForm, dirty, isSubmitting, isValid } = useFormikContext<ServiceFormValues>();

const { resetUiFormProgress, isLoadingSchema, selectedService, isEditMode, formType } = useServiceForm();
const { dirty, isSubmitting, isValid } = useFormikContext<ServiceFormValues>();
const { resetServiceForm, isLoadingSchema, selectedService, isEditMode, formType } = useServiceForm();

return (
<FormContainer>
Expand All @@ -70,12 +69,11 @@ const FormRoot: React.FC<FormRootProps> = ({
isSubmitting={isSubmitting || isTestConnectionInProgress}
errorMessage={errorMessage}
formType={formType}
onRetest={onRetest}
onRetestClick={onRetest}
isValid={isValid}
dirty={dirty}
resetForm={() => {
resetForm();
resetUiFormProgress();
onCancelClick={() => {
resetServiceForm();
}}
successMessage={successMessage}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,11 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
[formType, props.onServiceSelect, props.availableServices, props.isEditMode, toggleOpenRequestModal]
);

const { uiWidgetsInfo, setUiWidgetsInfo } = useBuildUiWidgetsContext(formFields, initialValues, uiOverrides);
const { uiWidgetsInfo, setUiWidgetsInfo, resetUiWidgetsInfo } = useBuildUiWidgetsContext(
formFields,
initialValues,
uiOverrides
);

const validationSchema = useConstructValidationSchema(jsonSchema, uiWidgetsInfo);

Expand Down Expand Up @@ -240,6 +244,7 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
widgetsInfo={uiWidgetsInfo}
getValues={getValues}
setUiWidgetsInfo={setUiWidgetsInfo}
resetUiWidgetsInfo={resetUiWidgetsInfo}
formType={formType}
selectedConnector={selectedConnectorDefinitionSpecification}
availableServices={props.availableServices}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ interface IProps {
isSubmitting: boolean;
isValid: boolean;
dirty: boolean;
resetForm: () => void;
onRetest?: () => void;
onCancelClick: () => void;
onRetestClick?: () => void;
onCancelTesting?: () => void;
isTestConnectionInProgress?: boolean;
successMessage?: React.ReactNode;
Expand All @@ -38,9 +38,9 @@ const EditControls: React.FC<IProps> = ({
isTestConnectionInProgress,
isValid,
dirty,
resetForm,
onCancelClick,
formType,
onRetest,
onRetestClick,
successMessage,
errorMessage,
onCancelTesting,
Expand All @@ -51,7 +51,7 @@ const EditControls: React.FC<IProps> = ({
return <TestingConnectionSpinner isCancellable={isTestConnectionInProgress} onCancelTesting={onCancelTesting} />;
}

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

return (
<>
{showStatusMessage()}
{renderStatusMessage()}
<Controls>
<div>
<Button
Expand All @@ -73,13 +73,13 @@ const EditControls: React.FC<IProps> = ({
<FormattedMessage id="form.saveChangesAndTest" />
</Button>
<ButtonContainer>
<Button type="button" secondary disabled={isSubmitting || !isValid || !dirty} onClick={resetForm}>
<Button type="button" secondary disabled={isSubmitting || !dirty} onClick={onCancelClick}>
<FormattedMessage id="form.cancel" />
</Button>
</ButtonContainer>
</div>
{onRetest && (
<Button type="button" onClick={onRetest} disabled={!isValid}>
{onRetestClick && (
<Button type="button" onClick={onRetestClick} disabled={!isValid}>
<FormattedMessage id={`form.${formType}Retest`} />
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import { FeatureItem, useFeature } from "hooks/services/Feature";
import { ServiceFormValues } from "./types";
import { makeConnectionConfigurationPath, serverProvidedOauthPaths } from "./utils";

interface Context {
interface ServiceFormContext {
formType: "source" | "destination";
getValues: (values: ServiceFormValues) => ServiceFormValues;
widgetsInfo: WidgetConfigMap;
setUiWidgetsInfo: (path: string, value: Record<string, unknown>) => void;
unfinishedFlows: Record<string, { startValue: string; id: number | string }>;
addUnfinishedFlow: (key: string, info?: Record<string, unknown>) => void;
removeUnfinishedFlow: (key: string) => void;
resetUiFormProgress: () => void;
resetServiceForm: () => void;
selectedService?: ConnectorDefinition;
selectedConnector?: ConnectorDefinitionSpecification;
isLoadingSchema?: boolean;
Expand All @@ -25,37 +25,41 @@ interface Context {
authFieldsToHide: string[];
}

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

const useServiceForm = (): Context => {
const serviceFormHelpers = useContext(FormWidgetContext);
export const useServiceForm = (): ServiceFormContext => {
const serviceFormHelpers = useContext(serviceFormContext);
if (!serviceFormHelpers) {
throw new Error("useServiceForm should be used within ServiceFormContextProvider");
}
return serviceFormHelpers;
};

const ServiceFormContextProvider: React.FC<{
interface ServiceFormContextProviderProps {
widgetsInfo: WidgetConfigMap;
setUiWidgetsInfo: (path: string, value: Record<string, unknown>) => void;
resetUiWidgetsInfo: () => void;
formType: "source" | "destination";
isLoadingSchema?: boolean;
isEditMode?: boolean;
availableServices: ConnectorDefinition[];
getValues: (values: ServiceFormValues) => ServiceFormValues;
selectedConnector?: ConnectorDefinitionSpecification;
}> = ({
}

export const ServiceFormContextProvider: React.FC<ServiceFormContextProviderProps> = ({
availableServices,
children,
widgetsInfo,
setUiWidgetsInfo,
resetUiWidgetsInfo,
selectedConnector,
getValues,
formType,
isLoadingSchema,
isEditMode,
}) => {
const { values } = useFormikContext<ServiceFormValues>();
const { values, resetForm } = useFormikContext<ServiceFormValues>();
const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector);

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

const ctx = useMemo<Context>(() => {
const ctx = useMemo<ServiceFormContext>(() => {
const unfinishedFlows = widgetsInfo["_common.unfinishedFlows"] ?? {};
return {
widgetsInfo,
Expand All @@ -105,7 +109,10 @@ const ServiceFormContextProvider: React.FC<{
"_common.unfinishedFlows",
Object.fromEntries(Object.entries(unfinishedFlows).filter(([key]) => key !== path))
),
resetUiFormProgress: () => setUiWidgetsInfo("_common.unfinishedFlows", {}),
resetServiceForm: () => {
resetForm();
resetUiWidgetsInfo();
},
};
}, [
widgetsInfo,
Expand All @@ -118,9 +125,9 @@ const ServiceFormContextProvider: React.FC<{
formType,
isLoadingSchema,
isEditMode,
resetForm,
resetUiWidgetsInfo,
]);

return <FormWidgetContext.Provider value={ctx}>{children}</FormWidgetContext.Provider>;
return <serviceFormContext.Provider value={ctx}>{children}</serviceFormContext.Provider>;
};

export { useServiceForm, ServiceFormContextProvider };
35 changes: 20 additions & 15 deletions airbyte-webapp/src/views/Connector/ServiceForm/useBuildForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ function upgradeSchemaLegacyAuth(
);
}

function useBuildInitialSchema(
export function useBuildInitialSchema(
connectorSpecification?: ConnectorDefinitionSpecification
): JSONSchema7Definition | undefined {
const allowOAuthConnector = useFeature(FeatureItem.AllowOAuthConnector);
Expand All @@ -61,13 +61,12 @@ function useBuildInitialSchema(
}, [allowOAuthConnector, connectorSpecification]);
}

function useBuildForm(
jsonSchema: JSONSchema7,
initialValues?: Partial<ServiceFormValues>
): {
export interface BuildFormHook {
initialValues: ServiceFormValues;
formFields: FormBlock;
} {
}

export function useBuildForm(jsonSchema: JSONSchema7, initialValues?: Partial<ServiceFormValues>): BuildFormHook {
const startValues = useMemo<ServiceFormValues>(
() => ({
name: "",
Expand All @@ -86,14 +85,17 @@ function useBuildForm(
};
}

const useBuildUiWidgetsContext = (
interface BuildUiWidgetsContextHook {
uiWidgetsInfo: WidgetConfigMap;
setUiWidgetsInfo: (widgetId: string, updatedValues: WidgetConfig) => void;
resetUiWidgetsInfo: () => void;
}

export const useBuildUiWidgetsContext = (
formFields: FormBlock[] | FormBlock,
formValues: ServiceFormValues,
uiOverrides?: WidgetConfigMap
): {
uiWidgetsInfo: WidgetConfigMap;
setUiWidgetsInfo: (widgetId: string, updatedValues: WidgetConfig) => void;
} => {
): BuildUiWidgetsContextHook => {
const [overriddenWidgetState, setUiWidgetsInfo] = useState<WidgetConfigMap>(uiOverrides ?? {});

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

const resetUiWidgetsInfo = useCallback(() => {
setUiWidgetsInfo(uiOverrides ?? {});
}, [uiOverrides]);

return {
uiWidgetsInfo: mergedState,
setUiWidgetsInfo: setUiWidgetsInfoSubState,
resetUiWidgetsInfo,
};
};

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

const usePatchFormik = (): void => {
export const usePatchFormik = (): void => {
const { setFieldTouched, isSubmitting, isValidating, validationSchema, validateForm, errors } = useFormikContext();
// Formik doesn't validate values again, when validationSchema was changed on the fly.
useEffect(() => {
Expand All @@ -148,5 +155,3 @@ const usePatchFormik = (): void => {
}
}, [errors, isSubmitting, isValidating, setFieldTouched]);
};

export { useBuildForm, useBuildInitialSchema, useBuildUiWidgetsContext, useConstructValidationSchema, usePatchFormik };

0 comments on commit 392c4f6

Please sign in to comment.