Skip to content

Commit 09a39e8

Browse files
shubhamraj-gitshubham-pyc
authored andcommitted
Add Edit connection button on list connection page (apache#48102)
* Edit connection form * Reviews * Fix extra fields
1 parent 8362a1c commit 09a39e8

File tree

9 files changed

+384
-127
lines changed

9 files changed

+384
-127
lines changed

airflow-core/src/airflow/ui/src/pages/Connections/AddConnectionButton.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,39 +16,38 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import { Box, Heading, Spinner, VStack } from "@chakra-ui/react";
19+
import { Box, Heading, VStack } from "@chakra-ui/react";
2020
import { useDisclosure } from "@chakra-ui/react";
2121
import { FiPlusCircle } from "react-icons/fi";
2222

2323
import { Dialog } from "src/components/ui";
2424
import ActionButton from "src/components/ui/ActionButton";
25-
import { useConnectionTypeMeta } from "src/queries/useConnectionTypeMeta";
25+
import { useAddConnection } from "src/queries/useAddConnection";
2626

2727
import ConnectionForm from "./ConnectionForm";
28-
29-
export type AddConnectionParams = {
30-
conf: string;
31-
conn_type: string;
32-
connection_id: string;
33-
description: string;
34-
host: string;
35-
login: string;
36-
password: string;
37-
port: string;
38-
schema: string;
39-
};
28+
import type { ConnectionBody } from "./Connections";
4029

4130
const AddConnectionButton = () => {
4231
const { onClose, onOpen, open } = useDisclosure();
43-
const { formattedData: connectionTypeMeta, isPending, keysList: connectionTypes } = useConnectionTypeMeta();
32+
const { addConnection, error, isPending } = useAddConnection({ onSuccessConfirm: onClose });
33+
const initialConnection: ConnectionBody = {
34+
conn_type: "",
35+
connection_id: "",
36+
description: "",
37+
extra: "{}",
38+
host: "",
39+
login: "",
40+
password: "",
41+
port: "",
42+
schema: "",
43+
};
4444

4545
return (
4646
<Box>
4747
<ActionButton
4848
actionName="Add Connection"
4949
colorPalette="blue"
50-
disabled={isPending}
51-
icon={isPending ? <Spinner size="sm" /> : <FiPlusCircle />}
50+
icon={<FiPlusCircle />}
5251
onClick={onOpen}
5352
text="Add Connection"
5453
variant="solid"
@@ -66,9 +65,10 @@ const AddConnectionButton = () => {
6665

6766
<Dialog.Body>
6867
<ConnectionForm
69-
connectionTypeMeta={connectionTypeMeta}
70-
connectionTypes={connectionTypes}
71-
onClose={onClose}
68+
error={error}
69+
initialConnection={initialConnection}
70+
isPending={isPending}
71+
mutateConnection={addConnection}
7272
/>
7373
</Dialog.Body>
7474
</Dialog.Content>

airflow-core/src/airflow/ui/src/pages/Connections/ConnectionForm.tsx

Lines changed: 49 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,51 @@
1616
* specific language governing permissions and limitations
1717
* under the License.
1818
*/
19-
import { Input, Button, Box, Spacer, HStack, Field, Stack, VStack, Textarea } from "@chakra-ui/react";
19+
import { Input, Button, Box, Spacer, HStack, Field, Stack, VStack, Spinner } from "@chakra-ui/react";
2020
import { Select } from "chakra-react-select";
2121
import { useEffect, useState } from "react";
2222
import { useForm, Controller } from "react-hook-form";
23-
import { FiEye, FiEyeOff, FiSave } from "react-icons/fi";
23+
import { FiSave } from "react-icons/fi";
2424

2525
import { ErrorAlert } from "src/components/ErrorAlert";
2626
import { FlexibleForm, flexibleFormExtraFieldSection } from "src/components/FlexibleForm";
2727
import { JsonEditor } from "src/components/JsonEditor";
2828
import { Accordion } from "src/components/ui";
29-
import { useAddConnection } from "src/queries/useAddConnection";
30-
import type { ConnectionMetaEntry } from "src/queries/useConnectionTypeMeta";
29+
import { useConnectionTypeMeta } from "src/queries/useConnectionTypeMeta";
3130
import type { ParamsSpec } from "src/queries/useDagParams";
3231
import { useParamStore } from "src/queries/useParamStore";
3332

34-
import type { AddConnectionParams } from "./AddConnectionButton";
33+
import StandardFields from "./ConnectionStandardFields";
34+
import type { ConnectionBody } from "./Connections";
3535

3636
type AddConnectionFormProps = {
37-
readonly connectionTypeMeta: Record<string, ConnectionMetaEntry>;
38-
readonly connectionTypes: Array<string>;
39-
readonly onClose: () => void;
37+
readonly error: unknown;
38+
readonly initialConnection: ConnectionBody;
39+
readonly isPending: boolean;
40+
readonly mutateConnection: (requestBody: ConnectionBody) => void;
4041
};
4142

42-
const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddConnectionFormProps) => {
43+
const ConnectionForm = ({
44+
error,
45+
initialConnection,
46+
isPending,
47+
mutateConnection,
48+
}: AddConnectionFormProps) => {
4349
const [errors, setErrors] = useState<{ conf?: string }>({});
44-
const { addConnection, error, isPending } = useAddConnection({ onSuccessConfirm: onClose });
45-
const { conf, setConf } = useParamStore();
46-
const [showPassword, setShowPassword] = useState(false);
50+
const {
51+
formattedData: connectionTypeMeta,
52+
isPending: isMetaPending,
53+
keysList: connectionTypes,
54+
} = useConnectionTypeMeta();
55+
const { conf: extra, setConf } = useParamStore();
4756
const {
4857
control,
4958
formState: { isValid },
5059
handleSubmit,
5160
reset,
5261
watch,
53-
} = useForm<AddConnectionParams>({
54-
defaultValues: {
55-
conf,
56-
conn_type: "",
57-
connection_id: "",
58-
description: "",
59-
host: "",
60-
login: "",
61-
password: "",
62-
port: "",
63-
schema: "",
64-
},
62+
} = useForm<ConnectionBody>({
63+
defaultValues: initialConnection,
6564
mode: "onBlur",
6665
});
6766

@@ -71,27 +70,23 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
7170

7271
useEffect(() => {
7372
reset((prevValues) => ({
74-
...prevValues,
73+
...initialConnection,
7574
conn_type: selectedConnType,
76-
description: "",
77-
host: "",
78-
login: "",
79-
password: "",
80-
port: "",
81-
schema: "",
75+
connection_id: prevValues.connection_id,
8276
}));
83-
}, [selectedConnType, reset]);
77+
setConf(JSON.stringify(JSON.parse(initialConnection.extra), undefined, 2));
78+
}, [selectedConnType, reset, initialConnection, setConf]);
8479

8580
// Automatically reset form when conf is fetched
8681
useEffect(() => {
8782
reset((prevValues) => ({
8883
...prevValues, // Retain existing form values
89-
conf,
84+
extra,
9085
}));
91-
}, [conf, reset, setConf]);
86+
}, [extra, reset, setConf]);
9287

93-
const onSubmit = (data: AddConnectionParams) => {
94-
addConnection(data);
88+
const onSubmit = (data: ConnectionBody) => {
89+
mutateConnection(data);
9590
};
9691

9792
const validateAndPrettifyJson = (value: string) => {
@@ -101,7 +96,7 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
10196
setErrors((prev) => ({ ...prev, conf: undefined }));
10297
const formattedJson = JSON.stringify(parsedJson, undefined, 2);
10398

104-
if (formattedJson !== conf) {
99+
if (formattedJson !== extra) {
105100
setConf(formattedJson); // Update only if the value is different
106101
}
107102

@@ -137,7 +132,7 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
137132
</Field.Label>
138133
</Stack>
139134
<Stack css={{ flexBasis: "70%" }}>
140-
<Input {...field} required size="sm" />
135+
<Input {...field} disabled={Boolean(initialConnection.connection_id)} required size="sm" />
141136
{fieldState.error ? <Field.ErrorText>{fieldState.error.message}</Field.ErrorText> : undefined}
142137
</Stack>
143138
</Field.Root>
@@ -159,13 +154,19 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
159154
</Field.Label>
160155
</Stack>
161156
<Stack css={{ flexBasis: "70%" }}>
162-
<Select
163-
{...Field}
164-
onChange={(val) => onChange(val?.value)}
165-
options={connTypesOptions}
166-
placeholder="Select Connection Type"
167-
value={connTypesOptions.find((type) => type.value === value)}
168-
/>
157+
<Stack>
158+
{isMetaPending ? (
159+
<Spinner size="sm" style={{ left: "60%", position: "absolute", top: "20%" }} />
160+
) : undefined}
161+
<Select
162+
{...Field}
163+
isDisabled={isMetaPending}
164+
onChange={(val) => onChange(val?.value)}
165+
options={connTypesOptions}
166+
placeholder="Select Connection Type"
167+
value={connTypesOptions.find((type) => type.value === value)}
168+
/>
169+
</Stack>
169170
<Field.HelperText>
170171
Connection type missing? Make sure you have installed the corresponding Airflow Providers
171172
Package.
@@ -188,60 +189,9 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
188189
variant="enclosed"
189190
>
190191
<Accordion.Item key="standardFields" value="standardFields">
191-
<Accordion.ItemTrigger cursor="button">Standard Fields</Accordion.ItemTrigger>
192+
<Accordion.ItemTrigger>Standard Fields</Accordion.ItemTrigger>
192193
<Accordion.ItemContent>
193-
<Stack pb={3} pl={3} pr={3}>
194-
{Object.entries(standardFields).map(([key, fields]) => {
195-
if (Boolean(fields.hidden)) {
196-
return undefined;
197-
} // Skip hidden fields
198-
199-
return (
200-
<Controller
201-
control={control}
202-
key={key}
203-
name={key as keyof AddConnectionParams}
204-
render={({ field }) => (
205-
<Field.Root mt={3} orientation="horizontal">
206-
<Stack>
207-
<Field.Label fontSize="md" style={{ flexBasis: "30%" }}>
208-
{fields.title ?? key}
209-
</Field.Label>
210-
</Stack>
211-
<Stack css={{ flexBasis: "70%", position: "relative" }}>
212-
{key === "description" ? (
213-
<Textarea {...field} placeholder={fields.placeholder ?? ""} />
214-
) : (
215-
<div style={{ position: "relative", width: "100%" }}>
216-
<Input
217-
{...field}
218-
placeholder={fields.placeholder ?? ""}
219-
type={key === "password" && !showPassword ? "password" : "text"}
220-
/>
221-
{key === "password" && (
222-
<button
223-
onClick={() => setShowPassword(!showPassword)}
224-
style={{
225-
cursor: "pointer",
226-
position: "absolute",
227-
right: "10px",
228-
top: "50%",
229-
transform: "translateY(-50%)",
230-
}}
231-
type="button"
232-
>
233-
{showPassword ? <FiEye size={15} /> : <FiEyeOff size={15} />}
234-
</button>
235-
)}
236-
</div>
237-
)}
238-
</Stack>
239-
</Field.Root>
240-
)}
241-
/>
242-
);
243-
})}
244-
</Stack>
194+
<StandardFields control={control} standardFields={standardFields} />
245195
</Accordion.ItemContent>
246196
</Accordion.Item>
247197
<FlexibleForm
@@ -254,7 +204,7 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
254204
<Accordion.ItemContent>
255205
<Controller
256206
control={control}
257-
name="conf"
207+
name="extra"
258208
render={({ field }) => (
259209
<Field.Root invalid={Boolean(errors.conf)}>
260210
<JsonEditor
@@ -286,7 +236,6 @@ const ConnectionForm = ({ connectionTypeMeta, connectionTypes, onClose }: AddCon
286236
</HStack>
287237
</Box>
288238
</>
289-
// eslint-disable-next-line max-lines
290239
);
291240
};
292241

0 commit comments

Comments
 (0)