Skip to content

Commit a6058fe

Browse files
Update UI and fix some errors
1 parent a98ffe0 commit a6058fe

File tree

8 files changed

+69
-6
lines changed

8 files changed

+69
-6
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1636,6 +1636,7 @@ repos:
16361636
^airflow-core/src/airflow/api/common/mark_tasks\.py$|
16371637
^airflow-core/src/airflow/api_fastapi/core_api/datamodels/assets\.py$|
16381638
^airflow-core/src/airflow/api_fastapi/core_api/datamodels/connections\.py$|
1639+
^airflow-core/src/airflow/api_fastapi/core_api/services/public/connections\.py$|
16391640
^airflow-core/src/airflow/api_fastapi/core_api/datamodels/hitl\.py$|
16401641
^airflow-core/src/airflow/api_fastapi/core_api/datamodels/variables\.py$|
16411642
^airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid\.py$|

airflow-core/src/airflow/api_fastapi/core_api/services/public/connections.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
from __future__ import annotations
1919

20+
import json
21+
2022
from fastapi import HTTPException, status
2123
from pydantic import ValidationError
2224
from sqlalchemy import select
@@ -32,6 +34,7 @@
3234
from airflow.api_fastapi.core_api.datamodels.connections import ConnectionBody
3335
from airflow.api_fastapi.core_api.services.public.common import BulkService
3436
from airflow.models.connection import Connection
37+
from airflow.sdk.execution_time.secrets_masker import merge
3538

3639

3740
def update_orm_from_pydantic(
@@ -56,11 +59,24 @@ def update_orm_from_pydantic(
5659
if (not update_mask and "password" in pydantic_conn.model_fields_set) or (
5760
update_mask and "password" in update_mask
5861
):
59-
orm_conn.set_password(pydantic_conn.password)
62+
if pydantic_conn.password is None:
63+
orm_conn.set_password(pydantic_conn.password)
64+
return
65+
66+
merged_password = merge(pydantic_conn.password, orm_conn.password, "password")
67+
orm_conn.set_password(merged_password)
6068
if (not update_mask and "extra" in pydantic_conn.model_fields_set) or (
6169
update_mask and "extra" in update_mask
6270
):
63-
orm_conn.set_extra(pydantic_conn.extra)
71+
if pydantic_conn.extra is None or orm_conn.extra is None:
72+
orm_conn.set_extra(pydantic_conn.extra)
73+
return
74+
try:
75+
merged_extra = merge(json.loads(pydantic_conn.extra), json.loads(orm_conn.extra))
76+
orm_conn.set_extra(json.dumps(merged_extra))
77+
except json.JSONDecodeError:
78+
# We can't merge fields in an unstructured `extra`
79+
orm_conn.set_extra(pydantic_conn.extra)
6480

6581

6682
class BulkConnectionService(BulkService[ConnectionBody]):

airflow-core/src/airflow/ui/public/i18n/locales/en/admin.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"extraFields": "Extra Fields",
3737
"extraFieldsJson": "Extra Fields JSON",
3838
"helperText": "Connection type missing? Make sure you have installed the corresponding Airflow Providers Package.",
39+
"helperTextForRedactedFields": "Redacted fields ('***') will remain unchanged if not modified.",
3940
"selectConnectionType": "Select Connection Type",
4041
"standardFields": "Standard Fields"
4142
},

airflow-core/src/airflow/ui/src/components/FlexibleForm/FlexibleForm.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ export type FlexibleFormProps = {
3232
initialParamsDict: { paramsDict: ParamsSpec };
3333
key?: string;
3434
setError: (error: boolean) => void;
35+
subHeader?: string;
3536
};
3637

3738
export const FlexibleForm = ({
3839
flexibleFormDefaultSection,
3940
initialParamsDict,
4041
setError,
42+
subHeader,
4143
}: FlexibleFormProps) => {
4244
const { paramsDict: params, setInitialParamDict, setParamsDict } = useParamStore();
4345
const processedSections = new Map();
@@ -126,6 +128,11 @@ export const FlexibleForm = ({
126128

127129
<Accordion.ItemContent pt={0}>
128130
<Accordion.ItemBody>
131+
{Boolean(subHeader) ? (
132+
<Text color="fg.muted" fontSize="xs" mb={2}>
133+
{subHeader}
134+
</Text>
135+
) : undefined}
129136
<Stack separator={<StackSeparator />}>
130137
{Object.entries(params)
131138
.filter(

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,15 @@ import type { ConnectionBody } from "./Connections";
3737
type AddConnectionFormProps = {
3838
readonly error: unknown;
3939
readonly initialConnection: ConnectionBody;
40+
readonly isEditMode?: boolean;
4041
readonly isPending: boolean;
4142
readonly mutateConnection: (requestBody: ConnectionBody) => void;
4243
};
4344

4445
const ConnectionForm = ({
4546
error,
4647
initialConnection,
48+
isEditMode = false,
4749
isPending,
4850
mutateConnection,
4951
}: AddConnectionFormProps) => {
@@ -202,6 +204,7 @@ const ConnectionForm = ({
202204
initialParamsDict={paramsDic}
203205
key={selectedConnType}
204206
setError={setFormErrors}
207+
subHeader={isEditMode ? translate("connections.form.helperTextForRedactedFields") : undefined}
205208
/>
206209
<Accordion.Item key="extraJson" value="extraJson">
207210
<Accordion.ItemTrigger cursor="button">
@@ -220,6 +223,11 @@ const ConnectionForm = ({
220223
}}
221224
/>
222225
{Boolean(errors.conf) ? <Field.ErrorText>{errors.conf}</Field.ErrorText> : undefined}
226+
{isEditMode ? (
227+
<Field.HelperText>
228+
{translate("connections.form.helperTextForRedactedFields")}
229+
</Field.HelperText>
230+
) : undefined}
223231
</Field.Root>
224232
)}
225233
/>

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ const EditConnectionButton = ({ connection, disabled }: Props) => {
8181
<ConnectionForm
8282
error={error}
8383
initialConnection={initialConnectionValue}
84+
isEditMode
8485
isPending={isPending}
8586
mutateConnection={editConnection}
8687
/>

airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,27 @@ class TestPatchConnection(TestConnectionEndpoint):
468468
"schema": None,
469469
},
470470
),
471+
(
472+
# Sensitive "***" should be ignored.
473+
{
474+
"connection_id": TEST_CONN_ID,
475+
"conn_type": TEST_CONN_TYPE,
476+
"port": 80,
477+
"login": "test_login_patch",
478+
"password": "***",
479+
},
480+
{
481+
"conn_type": TEST_CONN_TYPE,
482+
"connection_id": TEST_CONN_ID,
483+
"description": TEST_CONN_DESCRIPTION,
484+
"extra": None,
485+
"host": TEST_CONN_HOST,
486+
"login": "test_login_patch",
487+
"password": None,
488+
"port": 80,
489+
"schema": None,
490+
},
491+
),
471492
(
472493
{
473494
"connection_id": TEST_CONN_ID,

task-sdk/src/airflow/sdk/execution_time/secrets_masker.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from enum import Enum
2828
from functools import cache, cached_property
2929
from re import Pattern
30-
from typing import Any, TextIO, TypeAlias, TypeVar
30+
from typing import Any, TextIO, TypeAlias, TypeVar, overload
3131

3232
from airflow import settings
3333

@@ -116,9 +116,17 @@ def redact(value: Redactable, name: str | None = None, max_depth: int | None = N
116116
return _secrets_masker().redact(value, name, max_depth)
117117

118118

119+
@overload
120+
def merge(new_value: str, old_value: str, name: str | None = None, max_depth: int | None = None) -> str: ...
121+
122+
123+
@overload
124+
def merge(new_value: dict, old_value: dict, name: str | None = None, max_depth: int | None = None) -> str: ...
125+
126+
119127
def merge(
120128
new_value: Redacted, old_value: Redactable, name: str | None = None, max_depth: int | None = None
121-
) -> Redactable:
129+
) -> Redacted:
122130
"""
123131
Merge a redacted value with its original unredacted counterpart.
124132
@@ -313,7 +321,7 @@ def _merge(
313321
depth: int,
314322
max_depth: int,
315323
force_sensitive: bool = False,
316-
) -> Redactable:
324+
) -> Redacted:
317325
"""Merge a redacted item with its original unredacted counterpart."""
318326
if depth > max_depth:
319327
if isinstance(new_item, str) and new_item == "***":
@@ -394,7 +402,7 @@ def redact(self, item: Redactable, name: str | None = None, max_depth: int | Non
394402

395403
def merge(
396404
self, new_item: Redacted, old_item: Redactable, name: str | None = None, max_depth: int | None = None
397-
) -> Redactable:
405+
) -> Redacted:
398406
"""
399407
Merge a redacted item with its original unredacted counterpart.
400408

0 commit comments

Comments
 (0)