From ab4028db4b50f9c31075b5ab139f594da112675d Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Fri, 10 Jun 2022 10:32:58 -0400 Subject: [PATCH] Fix confirmation modal appearing when adding a transformation while creating 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 --- .../components/TransformationView.tsx | 11 +++++- .../ConnectionForm/ConnectionForm.tsx | 11 +++++- .../components/CreateControls.tsx | 11 ++++-- .../components/EditControls.tsx | 15 +++----- .../components/OperationsSection.tsx | 21 +++++++++-- .../components/TransformationField.tsx | 37 ++++++++++++++----- .../src/views/Connection/FormCard.tsx | 3 ++ .../TransformationForm/TransformationForm.tsx | 16 ++++++-- 8 files changed, 94 insertions(+), 31 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/TransformationView.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/TransformationView.tsx index 597e1fa18a4afc..480e42b2d74023 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/TransformationView.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/TransformationView.tsx @@ -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"; @@ -55,6 +56,7 @@ const CustomTransformationsCard: React.FC<{ mode: ConnectionFormMode; }> = ({ operations, onSubmit, mode }) => { const defaultTransformation = useDefaultTransformation(); + const [editingTransformation, toggleEditingTransformation] = useToggle(false); const initialValues = useMemo( () => ({ @@ -73,11 +75,18 @@ const CustomTransformationsCard: React.FC<{ enableReinitialize: true, onSubmit, }} + submitDisabled={editingTransformation} mode={mode} > {(formProps) => ( - + )} diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx index 767d07444c2493..61f0669cebad60 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx @@ -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"; @@ -129,6 +130,8 @@ const ConnectionForm: React.FC = ({ const { clearFormChange } = useFormChangeTrackerService(); const formId = useUniqueFormId(); const [submitError, setSubmitError] = useState(null); + const [editingTransformation, toggleEditingTransformation] = useToggle(false); + const formatMessage = useIntl().formatMessage; const isEditMode: boolean = mode !== "create"; @@ -346,12 +349,16 @@ const ConnectionForm: React.FC = ({ )} {mode === "create" && ( <> - + } /> = ({ isSubmitting, errorMessage, additionBottomControls }) => { +const CreateControls: React.FC = ({ + isSubmitting, + errorMessage, + additionBottomControls, + isValid, +}) => { if (isSubmitting) { return ( @@ -86,7 +91,7 @@ const CreateControls: React.FC = ({ isSubmitting, errorMessage, addition )}
{additionBottomControls || null} -
diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/EditControls.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/EditControls.tsx index a7501270832771..c8366c3d3dc9f5 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/EditControls.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/EditControls.tsx @@ -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; @@ -49,9 +50,10 @@ const Line = styled.div` margin: 16px -27px 0 -24px; `; -const EditControls: React.FC = ({ +const EditControls: React.FC = ({ isSubmitting, dirty, + submitDisabled, resetForm, successMessage, errorMessage, @@ -79,18 +81,13 @@ const EditControls: React.FC = ({
{showStatusMessage()}
- {editSchemeMode ? ( diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/OperationsSection.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/OperationsSection.tsx index 39ffd363e36930..769b35f16aaca9 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/OperationsSection.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/OperationsSection.tsx @@ -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 = ({ + destDefinition, + onStartEditTransformation, + onEndEditTransformation, +}) => { const formatMessage = useIntl().formatMessage; const { hasFeature } = useFeatureService(); @@ -42,7 +50,14 @@ export const OperationsSection: React.FC<{ {supportsNormalization && } {supportsTransformations && ( - {(formProps) => } + {(formProps) => ( + + )} )} diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/components/TransformationField.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/components/TransformationField.tsx index 076ee9db1e0bab..1567f2781b2d74 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/components/TransformationField.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/components/TransformationField.tsx @@ -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 = ({ + remove, + push, + replace, + form, + defaultTransformation, + mode, + onStartEdit, + onEndEdit, +}) => { const [editableItemIdx, setEditableItem] = useState(null); + return ( } onRemove={remove} - onStartEdit={(idx) => setEditableItem(idx)} + onStartEdit={(idx) => { + setEditableItem(idx); + onStartEdit?.(); + }} mode={mode} > {(editableItem) => ( setEditableItem(null)} + onCancel={() => { + setEditableItem(null); + onEndEdit?.(); + }} onDone={(transformation) => { if (isDefined(editableItemIdx)) { editableItemIdx >= form.values.transformations.length ? push(transformation) : replace(editableItemIdx, transformation); setEditableItem(null); + onEndEdit?.(); } }} /> diff --git a/airbyte-webapp/src/views/Connection/FormCard.tsx b/airbyte-webapp/src/views/Connection/FormCard.tsx index 2cab6d8f45d674..ec18e057c23fb1 100644 --- a/airbyte-webapp/src/views/Connection/FormCard.tsx +++ b/airbyte-webapp/src/views/Connection/FormCard.tsx @@ -20,6 +20,7 @@ interface FormCardProps extends CollapsibleCardProps { bottomSeparator?: boolean; form: FormikConfig; mode?: ConnectionFormMode; + submitDisabled?: boolean; } export function FormCard({ @@ -27,6 +28,7 @@ export function FormCard({ form, bottomSeparator = true, mode, + submitDisabled, ...props }: React.PropsWithChildren>) { const { formatMessage } = useIntl(); @@ -54,6 +56,7 @@ export function FormCard({ withLine={bottomSeparator} isSubmitting={isSubmitting} dirty={dirty} + submitDisabled={!isValid || submitDisabled} resetForm={() => { resetForm(); reset(); diff --git a/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx b/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx index a97c060d4f3e91..a6856085f051d9 100644 --- a/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx +++ b/airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx @@ -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"; @@ -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` @@ -87,20 +88,27 @@ const TransformationForm: React.FC = ({ }) => { const formatMessage = useIntl().formatMessage; const operationService = useGetService("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 = () => { + clearFormChange(formId); + onCancel?.(); + }; return ( <> - + - +