Skip to content

Commit

Permalink
Fix confirmation modal appearing when adding a transformation while c…
Browse files Browse the repository at this point in the history
…reating or saving a connection (#13426)

* Update form tracker to track TransformationForm instead of parent form

* Update Setup connection icon to be disabled while creating or editing a transformation
* Add properties to track start/end editing in TransformationField
* Bubble up prop tracking to ConnectionForm
* Update CreateControls submit button to disable when form is not valid or a transformation is editing

* Update TransformationForm to clear form tracking when canceling edit.

* Disable save button while adding or editing a custom transformation in transformation page
* Update FormCard to be able to disable submit button and disable if form is not valid
* Update TransformationView to track if transformation is edited to enable and disable submit button
  • Loading branch information
edmundito authored Jun 10, 2022
1 parent a30018b commit ab4028d
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Field, FieldArray } from "formik";
import React, { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { useToggle } from "react-use";
import styled from "styled-components";

import { ContentCard, H4 } from "components";
Expand Down Expand Up @@ -55,6 +56,7 @@ const CustomTransformationsCard: React.FC<{
mode: ConnectionFormMode;
}> = ({ operations, onSubmit, mode }) => {
const defaultTransformation = useDefaultTransformation();
const [editingTransformation, toggleEditingTransformation] = useToggle(false);

const initialValues = useMemo(
() => ({
Expand All @@ -73,11 +75,18 @@ const CustomTransformationsCard: React.FC<{
enableReinitialize: true,
onSubmit,
}}
submitDisabled={editingTransformation}
mode={mode}
>
<FieldArray name="transformations">
{(formProps) => (
<TransformationField defaultTransformation={defaultTransformation} {...formProps} mode={mode} />
<TransformationField
defaultTransformation={defaultTransformation}
{...formProps}
mode={mode}
onStartEdit={toggleEditingTransformation}
onEndEdit={toggleEditingTransformation}
/>
)}
</FieldArray>
</FormCard>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Field, FieldProps, Form, Formik, FormikHelpers } from "formik";
import React, { useCallback, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useToggle } from "react-use";
import styled from "styled-components";

import { ControlLabels, DropDown, DropDownRow, H5, Input, Label } from "components";
Expand Down Expand Up @@ -129,6 +130,8 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({
const { clearFormChange } = useFormChangeTrackerService();
const formId = useUniqueFormId();
const [submitError, setSubmitError] = useState<Error | null>(null);
const [editingTransformation, toggleEditingTransformation] = useToggle(false);

const formatMessage = useIntl().formatMessage;

const isEditMode: boolean = mode !== "create";
Expand Down Expand Up @@ -346,12 +349,16 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({
)}
{mode === "create" && (
<>
<OperationsSection destDefinition={destDefinition} />
<OperationsSection
destDefinition={destDefinition}
onStartEditTransformation={toggleEditingTransformation}
onEndEditTransformation={toggleEditingTransformation}
/>
<EditLaterMessage message={<FormattedMessage id="form.dataSync.message" />} />
<CreateControls
additionBottomControls={additionBottomControls}
isSubmitting={isSubmitting}
isValid={isValid}
isValid={isValid && !editingTransformation}
errorMessage={
errorMessage || !isValid ? formatMessage({ id: "connectionForm.validation.error" }) : null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import styled from "styled-components";

import { Button, Spinner, StatusIcon } from "components";

interface IProps {
interface CreateControlsProps {
isSubmitting: boolean;
isValid: boolean;
errorMessage?: React.ReactNode;
Expand Down Expand Up @@ -59,7 +59,12 @@ const ErrorText = styled.div`
max-width: 400px;
`;

const CreateControls: React.FC<IProps> = ({ isSubmitting, errorMessage, additionBottomControls }) => {
const CreateControls: React.FC<CreateControlsProps> = ({
isSubmitting,
errorMessage,
additionBottomControls,
isValid,
}) => {
if (isSubmitting) {
return (
<LoadingContainer>
Expand All @@ -86,7 +91,7 @@ const CreateControls: React.FC<IProps> = ({ isSubmitting, errorMessage, addition
)}
<div>
{additionBottomControls || null}
<Button type="submit" disabled={isSubmitting}>
<Button type="submit" disabled={isSubmitting || !isValid}>
<FormattedMessage id="onboarding.setUpConnection" />
</Button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import styled from "styled-components";

import { Button, LoadingButton } from "components";

interface IProps {
interface EditControlProps {
isSubmitting: boolean;
dirty: boolean;
submitDisabled?: boolean;
resetForm: () => void;
successMessage?: React.ReactNode;
errorMessage?: React.ReactNode;
Expand Down Expand Up @@ -49,9 +50,10 @@ const Line = styled.div`
margin: 16px -27px 0 -24px;
`;

const EditControls: React.FC<IProps> = ({
const EditControls: React.FC<EditControlProps> = ({
isSubmitting,
dirty,
submitDisabled,
resetForm,
successMessage,
errorMessage,
Expand Down Expand Up @@ -79,18 +81,13 @@ const EditControls: React.FC<IProps> = ({
<Buttons>
<div>{showStatusMessage()}</div>
<div>
<Button
type="button"
secondary
disabled={(isSubmitting || !dirty) && (!editSchemeMode || isSubmitting)}
onClick={resetForm}
>
<Button type="button" secondary disabled={isSubmitting || (!dirty && !editSchemeMode)} onClick={resetForm}>
<FormattedMessage id="form.cancel" />
</Button>
<ControlButton
type="submit"
isLoading={isSubmitting}
disabled={(isSubmitting || !dirty) && (!editSchemeMode || isSubmitting)}
disabled={submitDisabled || isSubmitting || (!dirty && !editSchemeMode)}
>
{editSchemeMode ? (
<FormattedMessage id="connection.saveAndReset" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@ const SectionTitle = styled.div`
line-height: 17px;
`;

export const OperationsSection: React.FC<{
interface OperationsSectionProps {
destDefinition: DestinationDefinitionSpecificationRead;
}> = ({ destDefinition }) => {
onStartEditTransformation?: () => void;
onEndEditTransformation?: () => void;
}

export const OperationsSection: React.FC<OperationsSectionProps> = ({
destDefinition,
onStartEditTransformation,
onEndEditTransformation,
}) => {
const formatMessage = useIntl().formatMessage;
const { hasFeature } = useFeatureService();

Expand All @@ -42,7 +50,14 @@ export const OperationsSection: React.FC<{
{supportsNormalization && <Field name="normalization" component={NormalizationField} />}
{supportsTransformations && (
<FieldArray name="transformations">
{(formProps) => <TransformationField defaultTransformation={defaultTransformation} {...formProps} />}
{(formProps) => (
<TransformationField
defaultTransformation={defaultTransformation}
onStartEdit={onStartEditTransformation}
onEndEdit={onEndEditTransformation}
{...formProps}
/>
)}
</FieldArray>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,26 @@ import TransformationForm from "views/Connection/TransformationForm";

import { ConnectionFormMode } from "../ConnectionForm";

const TransformationField: React.FC<
ArrayHelpers & {
form: FormikProps<{ transformations: OperationRead[] }>;
defaultTransformation: OperationCreate;
mode?: ConnectionFormMode;
}
> = ({ remove, push, replace, form, defaultTransformation, mode }) => {
interface TransformationFieldProps extends ArrayHelpers {
form: FormikProps<{ transformations: OperationRead[] }>;
defaultTransformation: OperationCreate;
mode?: ConnectionFormMode;
onStartEdit?: () => void;
onEndEdit?: () => void;
}

const TransformationField: React.FC<TransformationFieldProps> = ({
remove,
push,
replace,
form,
defaultTransformation,
mode,
onStartEdit,
onEndEdit,
}) => {
const [editableItemIdx, setEditableItem] = useState<number | null>(null);

return (
<ArrayOfObjectsEditor
items={form.values.transformations}
Expand All @@ -27,20 +39,27 @@ const TransformationField: React.FC<
}
addButtonText={<FormattedMessage id="form.addTransformation" />}
onRemove={remove}
onStartEdit={(idx) => setEditableItem(idx)}
onStartEdit={(idx) => {
setEditableItem(idx);
onStartEdit?.();
}}
mode={mode}
>
{(editableItem) => (
<TransformationForm
transformation={editableItem ?? defaultTransformation}
isNewTransformation={!editableItem}
onCancel={() => setEditableItem(null)}
onCancel={() => {
setEditableItem(null);
onEndEdit?.();
}}
onDone={(transformation) => {
if (isDefined(editableItemIdx)) {
editableItemIdx >= form.values.transformations.length
? push(transformation)
: replace(editableItemIdx, transformation);
setEditableItem(null);
onEndEdit?.();
}
}}
/>
Expand Down
3 changes: 3 additions & 0 deletions airbyte-webapp/src/views/Connection/FormCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ interface FormCardProps<T> extends CollapsibleCardProps {
bottomSeparator?: boolean;
form: FormikConfig<T>;
mode?: ConnectionFormMode;
submitDisabled?: boolean;
}

export function FormCard<T>({
children,
form,
bottomSeparator = true,
mode,
submitDisabled,
...props
}: React.PropsWithChildren<FormCardProps<T>>) {
const { formatMessage } = useIntl();
Expand Down Expand Up @@ -54,6 +56,7 @@ export function FormCard<T>({
withLine={bottomSeparator}
isSubmitting={isSubmitting}
dirty={dirty}
submitDisabled={!isValid || submitDisabled}
resetForm={() => {
resetForm();
reset();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FormikErrors } from "formik/dist/types";

import { getIn, useFormik, useFormikContext } from "formik";
import { getIn, useFormik } from "formik";
import React from "react";
import { FormattedMessage, useIntl } from "react-intl";
import styled from "styled-components";
Expand All @@ -12,6 +12,7 @@ import { FormChangeTracker } from "components/FormChangeTracker";
import { OperationService } from "core/domain/connection";
import { OperationCreate, OperationRead } from "core/request/AirbyteClient";
import { useGetService } from "core/servicesProvider";
import { useFormChangeTrackerService, useUniqueFormId } from "hooks/services/FormChangeTracker";
import { equal } from "utils/objects";

const Content = styled.div`
Expand Down Expand Up @@ -87,20 +88,27 @@ const TransformationForm: React.FC<TransformationProps> = ({
}) => {
const formatMessage = useIntl().formatMessage;
const operationService = useGetService<OperationService>("OperationService");
const { clearFormChange } = useFormChangeTrackerService();
const formId = useUniqueFormId();

const formik = useFormik({
initialValues: transformation,
validationSchema: validationSchema,
onSubmit: async (values) => {
await operationService.check(values);
clearFormChange(formId);
onDone(values);
},
});
const { dirty } = useFormikContext();

const onFormCancel: React.MouseEventHandler<HTMLButtonElement> = () => {
clearFormChange(formId);
onCancel?.();
};

return (
<>
<FormChangeTracker changed={isNewTransformation || dirty} />
<FormChangeTracker changed={isNewTransformation || formik.dirty} formId={formId} />
<Content>
<Column>
<Label
Expand Down Expand Up @@ -154,7 +162,7 @@ const TransformationForm: React.FC<TransformationProps> = ({
</Column>
</Content>
<ButtonContainer>
<SmallButton onClick={onCancel} type="button" secondary>
<SmallButton onClick={onFormCancel} type="button" secondary>
<FormattedMessage id="form.cancel" />
</SmallButton>
<SmallButton
Expand Down

0 comments on commit ab4028d

Please sign in to comment.