Skip to content

Commit de2035e

Browse files
authored
Disable form inputs while testing and saving source and destination (#13132)
* Disable form inputs while testing and saving source and destination * Replace delete button in EditorRow with real button * Fix input disabled state so it doesn't hover on disable * Disable TagInput hover state when disabled
1 parent f694b09 commit de2035e

File tree

17 files changed

+207
-90
lines changed

17 files changed

+207
-90
lines changed

airbyte-webapp/src/components/ArrayOfObjectsEditor/ArrayOfObjectsEditor.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export interface ArrayOfObjectsEditorProps<T extends { name: string }> {
3939
onDone?: () => void;
4040
onRemove: (index: number) => void;
4141
mode?: ConnectionFormMode;
42+
disabled?: boolean;
4243
}
4344

4445
export function ArrayOfObjectsEditor<T extends { name: string } = { name: string }>({
@@ -52,10 +53,12 @@ export function ArrayOfObjectsEditor<T extends { name: string } = { name: string
5253
mainTitle,
5354
addButtonText,
5455
mode,
56+
disabled,
5557
}: ArrayOfObjectsEditorProps<T>): JSX.Element {
5658
const onAddItem = React.useCallback(() => onStartEdit(items.length), [onStartEdit, items]);
5759

5860
const isEditable = editableItemIndex !== null && editableItemIndex !== undefined;
61+
5962
if (mode !== "readonly" && isEditable) {
6063
const item = typeof editableItemIndex === "number" ? items[editableItemIndex] : undefined;
6164
return (
@@ -64,12 +67,12 @@ export function ArrayOfObjectsEditor<T extends { name: string } = { name: string
6467
{onCancelEdit || onDone ? (
6568
<ButtonContainer>
6669
{onCancelEdit && (
67-
<SmallButton onClick={onCancelEdit} type="button" secondary>
70+
<SmallButton onClick={onCancelEdit} type="button" secondary disabled={disabled}>
6871
<FormattedMessage id="form.cancel" />
6972
</SmallButton>
7073
)}
7174
{onDone && (
72-
<SmallButton onClick={onDone} type="button" data-testid="done-button">
75+
<SmallButton onClick={onDone} type="button" data-testid="done-button" disabled={disabled}>
7376
<FormattedMessage id="form.done" />
7477
</SmallButton>
7578
)}
@@ -87,11 +90,19 @@ export function ArrayOfObjectsEditor<T extends { name: string } = { name: string
8790
mainTitle={mainTitle}
8891
addButtonText={addButtonText}
8992
mode={mode}
93+
disabled={disabled}
9094
/>
9195
{items.length ? (
9296
<ItemsList>
9397
{items.map((item, key) => (
94-
<EditorRow key={`form-item-${key}`} name={item.name} id={key} onEdit={onStartEdit} onRemove={onRemove} />
98+
<EditorRow
99+
key={`form-item-${key}`}
100+
name={item.name}
101+
id={key}
102+
onEdit={onStartEdit}
103+
onRemove={onRemove}
104+
disabled={disabled}
105+
/>
95106
))}
96107
</ItemsList>
97108
) : null}

airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorHeader.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,28 @@ const Content = styled.div`
1818
margin: 5px 0;
1919
`;
2020

21-
type EditorHeaderProps = {
21+
interface EditorHeaderProps {
2222
mainTitle?: React.ReactNode;
2323
addButtonText?: React.ReactNode;
2424
itemsCount: number;
2525
onAddItem: () => void;
2626
mode?: ConnectionFormMode;
27-
};
27+
disabled?: boolean;
28+
}
2829

29-
const EditorHeader: React.FC<EditorHeaderProps> = ({ itemsCount, onAddItem, mainTitle, addButtonText, mode }) => {
30+
const EditorHeader: React.FC<EditorHeaderProps> = ({
31+
itemsCount,
32+
onAddItem,
33+
mainTitle,
34+
addButtonText,
35+
mode,
36+
disabled,
37+
}) => {
3038
return (
3139
<Content>
3240
{mainTitle || <FormattedMessage id="form.items" values={{ count: itemsCount }} />}
3341
{mode !== "readonly" && (
34-
<Button secondary type="button" onClick={onAddItem} data-testid="addItemButton">
42+
<Button secondary type="button" onClick={onAddItem} data-testid="addItemButton" disabled={disabled}>
3543
{addButtonText || <FormattedMessage id="form.addItems" />}
3644
</Button>
3745
)}

airbyte-webapp/src/components/ArrayOfObjectsEditor/components/EditorRow.tsx

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { faTimes } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import React from "react";
4-
import { FormattedMessage } from "react-intl";
4+
import { FormattedMessage, useIntl } from "react-intl";
55
import styled from "styled-components";
66

77
import { Button } from "components";
@@ -23,31 +23,32 @@ const Content = styled.div`
2323
}
2424
`;
2525

26-
const Delete = styled(FontAwesomeIcon)`
27-
color: ${({ theme }) => theme.greyColor55};
28-
font-weight: 300;
29-
font-size: 14px;
30-
line-height: 24px;
26+
const DeleteButton = styled(Button)`
3127
margin-left: 7px;
32-
cursor: pointer;
3328
`;
3429

35-
type EditorRowProps = {
30+
interface EditorRowProps {
3631
name: string;
3732
id: number;
3833
onEdit: (id: number) => void;
3934
onRemove: (id: number) => void;
40-
};
35+
disabled?: boolean;
36+
}
37+
38+
const EditorRow: React.FC<EditorRowProps> = ({ name, id, onEdit, onRemove, disabled }) => {
39+
const { formatMessage } = useIntl();
40+
const buttonLabel = formatMessage({ id: "form.delete" });
4141

42-
const EditorRow: React.FC<EditorRowProps> = ({ name, id, onEdit, onRemove }) => {
4342
return (
4443
<Content>
4544
<div>{name || id}</div>
4645
<div>
47-
<Button secondary onClick={() => onEdit(id)} type="button">
46+
<Button secondary onClick={() => onEdit(id)} type="button" disabled={disabled}>
4847
<FormattedMessage id="form.edit" />
4948
</Button>
50-
<Delete icon={faTimes} onClick={() => onRemove(id)} />
49+
<DeleteButton iconOnly onClick={() => onRemove(id)} disabled={disabled} aria-label={buttonLabel}>
50+
<FontAwesomeIcon icon={faTimes} />
51+
</DeleteButton>
5152
</div>
5253
</Content>
5354
);

airbyte-webapp/src/components/base/DropDown/DropDown.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import { SelectContainer } from "./SelectContainer";
1414

1515
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1616
export type OptionType = any;
17-
type DropdownProps = Props<OptionType> & {
17+
18+
export interface DropdownProps extends Props<OptionType> {
1819
withBorder?: boolean;
1920
fullText?: boolean;
2021
error?: boolean;
21-
};
22+
}
2223

23-
const DropDown: React.FC<DropdownProps> = React.forwardRef((props, ref) => {
24+
export const DropDown: React.FC<DropdownProps> = React.forwardRef((props, ref) => {
2425
const propsComponents = props.components;
2526

2627
const components = React.useMemo<SelectComponentsConfig<OptionType, boolean>>(
@@ -77,8 +78,6 @@ const DropDown: React.FC<DropdownProps> = React.forwardRef((props, ref) => {
7778
);
7879
});
7980

80-
const defaultDataItemSort = naturalComparatorBy<IDataItem>((dataItem) => dataItem.label || "");
81+
export const defaultDataItemSort = naturalComparatorBy<IDataItem>((dataItem) => dataItem.label || "");
8182

8283
export default DropDown;
83-
export { DropDown, defaultDataItemSort };
84-
export type { DropdownProps };

airbyte-webapp/src/components/base/Input/Input.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,14 @@ const InputContainer = styled.div<InputProps>`
3333
border: 1px solid ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor0)};
3434
border-radius: 4px;
3535
36-
&:hover {
37-
background: ${({ theme, light }) => (light ? theme.whiteColor : theme.greyColor20)};
38-
border-color: ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor20)};
39-
}
36+
${({ disabled, theme, light, error }) =>
37+
!disabled &&
38+
`
39+
&:hover {
40+
background: ${light ? theme.whiteColor : theme.greyColor20};
41+
border-color: ${error ? theme.dangerColor : theme.greyColor20};
42+
}
43+
`}
4044
4145
&.input-container--focused {
4246
background: ${({ theme, light }) => (light ? theme.whiteColor : theme.primaryColor12)};
@@ -46,7 +50,7 @@ const InputContainer = styled.div<InputProps>`
4650

4751
const InputComponent = styled.input<InputProps & { isPassword?: boolean }>`
4852
outline: none;
49-
width: ${({ isPassword }) => (isPassword ? "calc(100% - 22px)" : "100%")};
53+
width: ${({ isPassword, disabled }) => (isPassword && !disabled ? "calc(100% - 22px)" : "100%")};
5054
padding: 7px 8px 7px 8px;
5155
font-size: 14px;
5256
line-height: 20px;

airbyte-webapp/src/components/base/TagInput/TagInput.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled from "styled-components";
33

44
import TagItem, { IItemProps } from "./TagItem";
55

6-
const MainContainer = styled.div<{ error?: boolean }>`
6+
const MainContainer = styled.div<{ error?: boolean; disabled?: boolean }>`
77
width: 100%;
88
min-height: 36px;
99
border-radius: 4px;
@@ -19,10 +19,14 @@ const MainContainer = styled.div<{ error?: boolean }>`
1919
background: ${(props) => (props.error ? props.theme.greyColor10 : props.theme.greyColor0)};
2020
caret-color: ${({ theme }) => theme.primaryColor};
2121
22-
&:hover {
23-
background: ${({ theme }) => theme.greyColor20};
24-
border-color: ${(props) => (props.error ? props.theme.dangerColor : props.theme.greyColor20)};
25-
}
22+
${({ disabled, theme, error }) =>
23+
!disabled &&
24+
`
25+
&:hover {
26+
background: ${theme.greyColor20};
27+
border-color: ${error ? theme.dangerColor : theme.greyColor20};
28+
}
29+
`}
2630
2731
&:focus,
2832
&:focus-within {
@@ -47,7 +51,7 @@ const InputElement = styled.input`
4751
}
4852
`;
4953

50-
type TagInputProps = {
54+
export interface TagInputProps {
5155
inputProps?: React.InputHTMLAttributes<HTMLInputElement>;
5256
value: IItemProps[];
5357
className?: string;
@@ -60,9 +64,9 @@ type TagInputProps = {
6064
onEnter: (value?: string | number | readonly string[]) => void;
6165
onDelete: (value: string) => void;
6266
onError?: () => void;
63-
};
67+
}
6468

65-
const TagInput: React.FC<TagInputProps> = ({
69+
export const TagInput: React.FC<TagInputProps> = ({
6670
inputProps,
6771
onEnter,
6872
value,
@@ -131,7 +135,13 @@ const TagInput: React.FC<TagInputProps> = ({
131135
const inputPlaceholder = !value.length && inputProps?.placeholder ? inputProps.placeholder : "";
132136

133137
return (
134-
<MainContainer onBlur={handleContainerBlur} onClick={handleContainerClick} className={className} error={error}>
138+
<MainContainer
139+
onBlur={handleContainerBlur}
140+
onClick={handleContainerClick}
141+
className={className}
142+
error={error}
143+
disabled={disabled}
144+
>
135145
{value.map((item, key) => (
136146
<TagItem
137147
disabled={disabled}
@@ -159,6 +169,3 @@ const TagInput: React.FC<TagInputProps> = ({
159169
</MainContainer>
160170
);
161171
};
162-
163-
export { TagInput };
164-
export type { TagInputProps };

airbyte-webapp/src/core/form/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,6 @@ export type { FormBlock, FormConditionItem, FormGroupItem, FormObjectArrayItem }
4848
// eslint-disable-next-line @typescript-eslint/no-explicit-any
4949
export type WidgetConfig = { [key: string]: any };
5050
export type WidgetConfigMap = { [key: string]: WidgetConfig };
51+
52+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
53+
export type FormComponentOverrideProps = Record<string, any>;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const FormRoot: React.FC<FormRootProps> = ({
5353

5454
return (
5555
<FormContainer>
56-
<FormSection blocks={formFields} />
56+
<FormSection blocks={formFields} disabled={isSubmitting || isTestConnectionInProgress} />
5757
{isLoadingSchema && (
5858
<LoaderContainer>
5959
<Spinner />

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { FormChangeTracker } from "components/FormChangeTracker";
88
import { ConnectorDefinition, ConnectorDefinitionSpecification } from "core/domain/connector";
99
import { isDestinationDefinitionSpecification } from "core/domain/connector/destination";
1010
import { isSourceDefinition, isSourceDefinitionSpecification } from "core/domain/connector/source";
11-
import { FormBaseItem } from "core/form/types";
11+
import { FormBaseItem, FormComponentOverrideProps } from "core/form/types";
1212
import { useFormChangeTrackerService, useUniqueFormId } from "hooks/services/FormChangeTracker";
1313
import { isDefined } from "utils/common";
1414
import RequestConnectorModal from "views/Connector/RequestConnectorModal";
@@ -184,10 +184,12 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
184184
const uiOverrides = useMemo(
185185
() => ({
186186
name: {
187-
component: (property: FormBaseItem) => <ConnectorNameControl property={property} formType={formType} />,
187+
component: (property: FormBaseItem, componentProps: FormComponentOverrideProps) => (
188+
<ConnectorNameControl property={property} formType={formType} {...componentProps} />
189+
),
188190
},
189191
serviceType: {
190-
component: (property: FormBaseItem) => (
192+
component: (property: FormBaseItem, componentProps: FormComponentOverrideProps) => (
191193
<ConnectorServiceTypeControl
192194
property={property}
193195
formType={formType}
@@ -198,6 +200,7 @@ const ServiceForm: React.FC<ServiceFormProps> = (props) => {
198200
setInitialRequestName(name);
199201
toggleOpenRequestModal();
200202
}}
203+
{...componentProps}
201204
/>
202205
),
203206
},

airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorNameControl.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import { Input, ControlLabels } from "components";
66

77
import { FormBaseItem } from "core/form/types";
88

9-
const ConnectorNameControl: React.FC<{
9+
interface ConnnectorNameControlProps {
1010
property: FormBaseItem;
1111
formType: "source" | "destination";
12-
}> = ({ property, formType }) => {
13-
const formatMessage = useIntl().formatMessage;
12+
disabled?: boolean;
13+
}
14+
15+
export const ConnectorNameControl: React.FC<ConnnectorNameControlProps> = ({ property, formType, disabled }) => {
16+
const { formatMessage } = useIntl();
1417
const [field, fieldMeta] = useField(property.path);
1518

1619
return (
@@ -28,9 +31,8 @@ const ConnectorNameControl: React.FC<{
2831
placeholder={formatMessage({
2932
id: `form.${formType}Name.placeholder`,
3033
})}
34+
disabled={disabled}
3135
/>
3236
</ControlLabels>
3337
);
3438
};
35-
36-
export { ConnectorNameControl };

airbyte-webapp/src/views/Connector/ServiceForm/components/Controls/ConnectorServiceTypeControl.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,22 +152,26 @@ const SingleValue: React.FC<SingleValueProps> = (props) => {
152152
);
153153
};
154154

155-
const ConnectorServiceTypeControl: React.FC<{
155+
interface ConnectorServiceTypeControlProps {
156156
property: FormBaseItem;
157157
formType: "source" | "destination";
158158
availableServices: ConnectorDefinition[];
159159
isEditMode?: boolean;
160160
documentationUrl?: string;
161161
onChangeServiceType?: (id: string) => void;
162162
onOpenRequestConnectorModal: (initialName: string) => void;
163-
}> = ({
163+
disabled?: boolean;
164+
}
165+
166+
const ConnectorServiceTypeControl: React.FC<ConnectorServiceTypeControlProps> = ({
164167
property,
165168
formType,
166169
isEditMode,
167170
onChangeServiceType,
168171
availableServices,
169172
documentationUrl,
170173
onOpenRequestConnectorModal,
174+
disabled,
171175
}) => {
172176
const { formatMessage } = useIntl();
173177
const orderOverwrite = useExperiment("connector.orderOverwrite", {});
@@ -258,7 +262,7 @@ const ConnectorServiceTypeControl: React.FC<{
258262
}}
259263
selectProps={{ onOpenRequestConnectorModal }}
260264
error={!!fieldMeta.error && fieldMeta.touched}
261-
isDisabled={isEditMode}
265+
isDisabled={isEditMode || disabled}
262266
isSearchable
263267
placeholder={formatMessage({
264268
id: "form.selectConnector",

0 commit comments

Comments
 (0)