Skip to content

Commit ab4028d

Browse files
authored
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
1 parent a30018b commit ab4028d

File tree

8 files changed

+94
-31
lines changed

8 files changed

+94
-31
lines changed

airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/components/TransformationView.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Field, FieldArray } from "formik";
22
import React, { useMemo } from "react";
33
import { FormattedMessage } from "react-intl";
4+
import { useToggle } from "react-use";
45
import styled from "styled-components";
56

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

5961
const initialValues = useMemo(
6062
() => ({
@@ -73,11 +75,18 @@ const CustomTransformationsCard: React.FC<{
7375
enableReinitialize: true,
7476
onSubmit,
7577
}}
78+
submitDisabled={editingTransformation}
7679
mode={mode}
7780
>
7881
<FieldArray name="transformations">
7982
{(formProps) => (
80-
<TransformationField defaultTransformation={defaultTransformation} {...formProps} mode={mode} />
83+
<TransformationField
84+
defaultTransformation={defaultTransformation}
85+
{...formProps}
86+
mode={mode}
87+
onStartEdit={toggleEditingTransformation}
88+
onEndEdit={toggleEditingTransformation}
89+
/>
8190
)}
8291
</FieldArray>
8392
</FormCard>

airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionForm.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Field, FieldProps, Form, Formik, FormikHelpers } from "formik";
22
import React, { useCallback, useState } from "react";
33
import { FormattedMessage, useIntl } from "react-intl";
4+
import { useToggle } from "react-use";
45
import styled from "styled-components";
56

67
import { ControlLabels, DropDown, DropDownRow, H5, Input, Label } from "components";
@@ -129,6 +130,8 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({
129130
const { clearFormChange } = useFormChangeTrackerService();
130131
const formId = useUniqueFormId();
131132
const [submitError, setSubmitError] = useState<Error | null>(null);
133+
const [editingTransformation, toggleEditingTransformation] = useToggle(false);
134+
132135
const formatMessage = useIntl().formatMessage;
133136

134137
const isEditMode: boolean = mode !== "create";
@@ -346,12 +349,16 @@ const ConnectionForm: React.FC<ConnectionFormProps> = ({
346349
)}
347350
{mode === "create" && (
348351
<>
349-
<OperationsSection destDefinition={destDefinition} />
352+
<OperationsSection
353+
destDefinition={destDefinition}
354+
onStartEditTransformation={toggleEditingTransformation}
355+
onEndEditTransformation={toggleEditingTransformation}
356+
/>
350357
<EditLaterMessage message={<FormattedMessage id="form.dataSync.message" />} />
351358
<CreateControls
352359
additionBottomControls={additionBottomControls}
353360
isSubmitting={isSubmitting}
354-
isValid={isValid}
361+
isValid={isValid && !editingTransformation}
355362
errorMessage={
356363
errorMessage || !isValid ? formatMessage({ id: "connectionForm.validation.error" }) : null
357364
}

airbyte-webapp/src/views/Connection/ConnectionForm/components/CreateControls.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import styled from "styled-components";
44

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

7-
interface IProps {
7+
interface CreateControlsProps {
88
isSubmitting: boolean;
99
isValid: boolean;
1010
errorMessage?: React.ReactNode;
@@ -59,7 +59,12 @@ const ErrorText = styled.div`
5959
max-width: 400px;
6060
`;
6161

62-
const CreateControls: React.FC<IProps> = ({ isSubmitting, errorMessage, additionBottomControls }) => {
62+
const CreateControls: React.FC<CreateControlsProps> = ({
63+
isSubmitting,
64+
errorMessage,
65+
additionBottomControls,
66+
isValid,
67+
}) => {
6368
if (isSubmitting) {
6469
return (
6570
<LoadingContainer>
@@ -86,7 +91,7 @@ const CreateControls: React.FC<IProps> = ({ isSubmitting, errorMessage, addition
8691
)}
8792
<div>
8893
{additionBottomControls || null}
89-
<Button type="submit" disabled={isSubmitting}>
94+
<Button type="submit" disabled={isSubmitting || !isValid}>
9095
<FormattedMessage id="onboarding.setUpConnection" />
9196
</Button>
9297
</div>

airbyte-webapp/src/views/Connection/ConnectionForm/components/EditControls.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import styled from "styled-components";
44

55
import { Button, LoadingButton } from "components";
66

7-
interface IProps {
7+
interface EditControlProps {
88
isSubmitting: boolean;
99
dirty: boolean;
10+
submitDisabled?: boolean;
1011
resetForm: () => void;
1112
successMessage?: React.ReactNode;
1213
errorMessage?: React.ReactNode;
@@ -49,9 +50,10 @@ const Line = styled.div`
4950
margin: 16px -27px 0 -24px;
5051
`;
5152

52-
const EditControls: React.FC<IProps> = ({
53+
const EditControls: React.FC<EditControlProps> = ({
5354
isSubmitting,
5455
dirty,
56+
submitDisabled,
5557
resetForm,
5658
successMessage,
5759
errorMessage,
@@ -79,18 +81,13 @@ const EditControls: React.FC<IProps> = ({
7981
<Buttons>
8082
<div>{showStatusMessage()}</div>
8183
<div>
82-
<Button
83-
type="button"
84-
secondary
85-
disabled={(isSubmitting || !dirty) && (!editSchemeMode || isSubmitting)}
86-
onClick={resetForm}
87-
>
84+
<Button type="button" secondary disabled={isSubmitting || (!dirty && !editSchemeMode)} onClick={resetForm}>
8885
<FormattedMessage id="form.cancel" />
8986
</Button>
9087
<ControlButton
9188
type="submit"
9289
isLoading={isSubmitting}
93-
disabled={(isSubmitting || !dirty) && (!editSchemeMode || isSubmitting)}
90+
disabled={submitDisabled || isSubmitting || (!dirty && !editSchemeMode)}
9491
>
9592
{editSchemeMode ? (
9693
<FormattedMessage id="connection.saveAndReset" />

airbyte-webapp/src/views/Connection/ConnectionForm/components/OperationsSection.tsx

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,17 @@ const SectionTitle = styled.div`
1616
line-height: 17px;
1717
`;
1818

19-
export const OperationsSection: React.FC<{
19+
interface OperationsSectionProps {
2020
destDefinition: DestinationDefinitionSpecificationRead;
21-
}> = ({ destDefinition }) => {
21+
onStartEditTransformation?: () => void;
22+
onEndEditTransformation?: () => void;
23+
}
24+
25+
export const OperationsSection: React.FC<OperationsSectionProps> = ({
26+
destDefinition,
27+
onStartEditTransformation,
28+
onEndEditTransformation,
29+
}) => {
2230
const formatMessage = useIntl().formatMessage;
2331
const { hasFeature } = useFeatureService();
2432

@@ -42,7 +50,14 @@ export const OperationsSection: React.FC<{
4250
{supportsNormalization && <Field name="normalization" component={NormalizationField} />}
4351
{supportsTransformations && (
4452
<FieldArray name="transformations">
45-
{(formProps) => <TransformationField defaultTransformation={defaultTransformation} {...formProps} />}
53+
{(formProps) => (
54+
<TransformationField
55+
defaultTransformation={defaultTransformation}
56+
onStartEdit={onStartEditTransformation}
57+
onEndEdit={onEndEditTransformation}
58+
{...formProps}
59+
/>
60+
)}
4661
</FieldArray>
4762
)}
4863
</>

airbyte-webapp/src/views/Connection/ConnectionForm/components/TransformationField.tsx

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,26 @@ import TransformationForm from "views/Connection/TransformationForm";
1010

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

13-
const TransformationField: React.FC<
14-
ArrayHelpers & {
15-
form: FormikProps<{ transformations: OperationRead[] }>;
16-
defaultTransformation: OperationCreate;
17-
mode?: ConnectionFormMode;
18-
}
19-
> = ({ remove, push, replace, form, defaultTransformation, mode }) => {
13+
interface TransformationFieldProps extends ArrayHelpers {
14+
form: FormikProps<{ transformations: OperationRead[] }>;
15+
defaultTransformation: OperationCreate;
16+
mode?: ConnectionFormMode;
17+
onStartEdit?: () => void;
18+
onEndEdit?: () => void;
19+
}
20+
21+
const TransformationField: React.FC<TransformationFieldProps> = ({
22+
remove,
23+
push,
24+
replace,
25+
form,
26+
defaultTransformation,
27+
mode,
28+
onStartEdit,
29+
onEndEdit,
30+
}) => {
2031
const [editableItemIdx, setEditableItem] = useState<number | null>(null);
32+
2133
return (
2234
<ArrayOfObjectsEditor
2335
items={form.values.transformations}
@@ -27,20 +39,27 @@ const TransformationField: React.FC<
2739
}
2840
addButtonText={<FormattedMessage id="form.addTransformation" />}
2941
onRemove={remove}
30-
onStartEdit={(idx) => setEditableItem(idx)}
42+
onStartEdit={(idx) => {
43+
setEditableItem(idx);
44+
onStartEdit?.();
45+
}}
3146
mode={mode}
3247
>
3348
{(editableItem) => (
3449
<TransformationForm
3550
transformation={editableItem ?? defaultTransformation}
3651
isNewTransformation={!editableItem}
37-
onCancel={() => setEditableItem(null)}
52+
onCancel={() => {
53+
setEditableItem(null);
54+
onEndEdit?.();
55+
}}
3856
onDone={(transformation) => {
3957
if (isDefined(editableItemIdx)) {
4058
editableItemIdx >= form.values.transformations.length
4159
? push(transformation)
4260
: replace(editableItemIdx, transformation);
4361
setEditableItem(null);
62+
onEndEdit?.();
4463
}
4564
}}
4665
/>

airbyte-webapp/src/views/Connection/FormCard.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ interface FormCardProps<T> extends CollapsibleCardProps {
2020
bottomSeparator?: boolean;
2121
form: FormikConfig<T>;
2222
mode?: ConnectionFormMode;
23+
submitDisabled?: boolean;
2324
}
2425

2526
export function FormCard<T>({
2627
children,
2728
form,
2829
bottomSeparator = true,
2930
mode,
31+
submitDisabled,
3032
...props
3133
}: React.PropsWithChildren<FormCardProps<T>>) {
3234
const { formatMessage } = useIntl();
@@ -54,6 +56,7 @@ export function FormCard<T>({
5456
withLine={bottomSeparator}
5557
isSubmitting={isSubmitting}
5658
dirty={dirty}
59+
submitDisabled={!isValid || submitDisabled}
5760
resetForm={() => {
5861
resetForm();
5962
reset();

airbyte-webapp/src/views/Connection/TransformationForm/TransformationForm.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { FormikErrors } from "formik/dist/types";
22

3-
import { getIn, useFormik, useFormikContext } from "formik";
3+
import { getIn, useFormik } from "formik";
44
import React from "react";
55
import { FormattedMessage, useIntl } from "react-intl";
66
import styled from "styled-components";
@@ -12,6 +12,7 @@ import { FormChangeTracker } from "components/FormChangeTracker";
1212
import { OperationService } from "core/domain/connection";
1313
import { OperationCreate, OperationRead } from "core/request/AirbyteClient";
1414
import { useGetService } from "core/servicesProvider";
15+
import { useFormChangeTrackerService, useUniqueFormId } from "hooks/services/FormChangeTracker";
1516
import { equal } from "utils/objects";
1617

1718
const Content = styled.div`
@@ -87,20 +88,27 @@ const TransformationForm: React.FC<TransformationProps> = ({
8788
}) => {
8889
const formatMessage = useIntl().formatMessage;
8990
const operationService = useGetService<OperationService>("OperationService");
91+
const { clearFormChange } = useFormChangeTrackerService();
92+
const formId = useUniqueFormId();
9093

9194
const formik = useFormik({
9295
initialValues: transformation,
9396
validationSchema: validationSchema,
9497
onSubmit: async (values) => {
9598
await operationService.check(values);
99+
clearFormChange(formId);
96100
onDone(values);
97101
},
98102
});
99-
const { dirty } = useFormikContext();
103+
104+
const onFormCancel: React.MouseEventHandler<HTMLButtonElement> = () => {
105+
clearFormChange(formId);
106+
onCancel?.();
107+
};
100108

101109
return (
102110
<>
103-
<FormChangeTracker changed={isNewTransformation || dirty} />
111+
<FormChangeTracker changed={isNewTransformation || formik.dirty} formId={formId} />
104112
<Content>
105113
<Column>
106114
<Label
@@ -154,7 +162,7 @@ const TransformationForm: React.FC<TransformationProps> = ({
154162
</Column>
155163
</Content>
156164
<ButtonContainer>
157-
<SmallButton onClick={onCancel} type="button" secondary>
165+
<SmallButton onClick={onFormCancel} type="button" secondary>
158166
<FormattedMessage id="form.cancel" />
159167
</SmallButton>
160168
<SmallButton

0 commit comments

Comments
 (0)