This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 0cfc4ef1e2d UI: Show an empty object for object params with no value 
(#71710)
0cfc4ef1e2d is described below

commit 0cfc4ef1e2dbd65d4a650cd800bb1b2c9e9934b2
Author: Kole Harvey <[email protected]>
AuthorDate: Thu Aug 20 06:39:21 2026 -0400

    UI: Show an empty object for object params with no value (#71710)
    
    A param declared as an object but left without a value pre-filled the
    trigger form's JSON editor with an empty array, so anyone editing it
    started from the wrong JSON literal for the field's declared type.
---
 .../components/FlexibleForm/FieldObject.test.tsx   | 88 ++++++++++++++++++++++
 .../ui/src/components/FlexibleForm/FieldObject.tsx |  2 +-
 2 files changed, 89 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.test.tsx 
b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.test.tsx
new file mode 100644
index 00000000000..7afe79e551c
--- /dev/null
+++ 
b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.test.tsx
@@ -0,0 +1,88 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import "@testing-library/jest-dom";
+import { render, screen } from "@testing-library/react";
+import { describe, it, expect, beforeEach, vi } from "vitest";
+
+import { Wrapper } from "src/utils/Wrapper";
+
+import { FieldObject } from "./FieldObject";
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const mockParamsDict: Record<string, any> = {};
+const mockSetParamsDict = vi.fn();
+
+vi.mock("src/queries/useParamStore", () => ({
+  paramPlaceholder: {
+    schema: {},
+    value: null,
+  },
+  useParamStore: () => ({
+    disabled: false,
+    paramsDict: mockParamsDict,
+    setParamsDict: mockSetParamsDict,
+  }),
+}));
+
+vi.mock("src/components/MonacoEditor", () => ({
+  default: ({
+    onChange,
+    value,
+  }: {
+    readonly onChange?: (value: string | undefined) => void;
+    readonly value?: string;
+  }) => (
+    <textarea aria-label="JSON editor" onChange={(event) => 
onChange?.(event.target.value)} value={value} />
+  ),
+}));
+
+vi.mock("src/context/colorMode", () => ({
+  useMonacoTheme: () => ({ beforeMount: vi.fn(), theme: "airflow-light" }),
+}));
+
+describe("FieldObject", () => {
+  beforeEach(() => {
+    Object.keys(mockParamsDict).forEach((key) => {
+      // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
+      delete mockParamsDict[key];
+    });
+  });
+
+  it("renders an empty object for an object param with no value", () => {
+    mockParamsDict.test_param = {
+      schema: { type: ["object", "null"] },
+      value: null,
+    };
+
+    render(<FieldObject name="test_param" onUpdate={vi.fn()} />, { wrapper: 
Wrapper });
+
+    expect(screen.getByLabelText("JSON editor")).toHaveValue("{}");
+  });
+
+  it("renders the existing value for an object param that has one", () => {
+    mockParamsDict.test_param = {
+      schema: { type: "object" },
+      value: { key: "value" },
+    };
+
+    render(<FieldObject name="test_param" onUpdate={vi.fn()} />, { wrapper: 
Wrapper });
+
+    expect(screen.getByLabelText("JSON editor")).toHaveValue(JSON.stringify({ 
key: "value" }, undefined, 2));
+  });
+});
diff --git 
a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.tsx 
b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
index e6d72132575..3fcbcd779a4 100644
--- a/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
+++ b/airflow-core/src/airflow/ui/src/components/FlexibleForm/FieldObject.tsx
@@ -46,7 +46,7 @@ export const FieldObject = ({ name, namespace = "default", 
onUpdate }: FlexibleF
       editable={!disabled}
       id={`element_${name}`}
       onChange={handleChange}
-      value={JSON.stringify(param.value ?? [], undefined, 2)}
+      value={JSON.stringify(param.value ?? {}, undefined, 2)}
     />
   );
 };

Reply via email to