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

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git

commit 77e8f5582d9083483cadc84733e04808f201ef0b
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 30 01:00:17 2026 +0530

    [v3-4-test] Preserve XCom value types when editing in the UI (#75496) 
(#75951)
    
    (cherry picked from commit e0a95793c9540cc53a07b5b6f0dfa0ef77d207c6)
    
    Co-authored-by: Anzin Mohammed <[email protected]>
---
 .../airflow/ui/src/pages/XCom/XComModal.test.tsx   | 60 ++++++++++++++++++++++
 .../src/airflow/ui/src/pages/XCom/XComModal.tsx    |  6 ++-
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.test.tsx 
b/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.test.tsx
index 9c805a9f4cc..e8feb6d102a 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.test.tsx
@@ -18,10 +18,12 @@
  */
 import "@testing-library/jest-dom/vitest";
 import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { http, HttpResponse } from "msw";
 import { afterEach, expect, it, vi } from "vitest";
 
 import { XcomService } from "openapi/requests";
 
+import { server } from "src/mocks/server";
 import { Wrapper } from "src/utils/Wrapper";
 
 import XComModal from "./XComModal";
@@ -80,3 +82,61 @@ it("edits the selected regional XCom without using the 
colliding public map coor
   );
   expect(fetch).toHaveBeenCalledWith(expect.objectContaining({ regionId, 
regionIndex: 3 }));
 });
+
+it.each([
+  { label: "numeric string", value: "123" },
+  { label: "boolean string", value: "false" },
+  { label: "null string", value: "null" },
+  { label: "object string", value: '{"count":1}' },
+  { label: "array string", value: "[1,2]" },
+  { label: "quoted string", value: '"quoted"' },
+  { label: "empty string", value: "" },
+  { label: "multiline string", value: "first\nsecond" },
+])("preserves a stored $label when saving without edits", async ({ value }) => 
{
+  let deserialize: string | null = null;
+  let stringify: string | null = null;
+  const update = vi.fn();
+
+  server.use(
+    
http.get("/api/v2/dags/dag/dagRuns/run/taskInstances/task/xcomEntries/return_value",
 ({ request }) => {
+      const url = new URL(request.url);
+
+      deserialize = url.searchParams.get("deserialize");
+      stringify = url.searchParams.get("stringify");
+
+      return HttpResponse.json({ key: "return_value", value });
+    }),
+    http.patch(
+      
"/api/v2/dags/dag/dagRuns/run/taskInstances/task/xcomEntries/return_value",
+      async ({ request }) => {
+        update(await request.json());
+
+        return HttpResponse.json({ key: "return_value", value });
+      },
+    ),
+  );
+
+  render(
+    <XComModal
+      dagId="dag"
+      isOpen
+      mapIndex={-1}
+      mode="edit"
+      onClose={vi.fn()}
+      runId="run"
+      taskId="task"
+      xcomKey="return_value"
+    />,
+    { wrapper: Wrapper },
+  );
+
+  const editorValue = JSON.stringify(value, undefined, 2);
+
+  await waitFor(() => 
expect(screen.getByTestId("editor").textContent).toBe(editorValue));
+  expect(deserialize).toBe("false");
+  expect(stringify).toBe("false");
+
+  fireEvent.click(screen.getByRole("button", { name: "common:modal.save" }));
+
+  await waitFor(() => expect(update).toHaveBeenCalledWith({ map_index: -1, 
value }));
+});
diff --git a/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.tsx 
b/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.tsx
index 7ab05e32e79..90edfcac8b0 100644
--- a/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/XCom/XComModal.tsx
@@ -72,10 +72,12 @@ const XComModal = ({
     {
       dagId,
       dagRunId: runId,
-      deserialize: true,
+      // Editing needs the stored type; display deserialization can parse 
JSON-looking strings.
+      deserialize: false,
       mapIndex,
       regionId,
       regionIndex,
+      stringify: false,
       taskId,
       xcomKey: xcomKey ?? "",
     },
@@ -88,7 +90,7 @@ const XComModal = ({
     if (isOpen && isEditMode && data) {
       const val = data.value;
 
-      setValue(typeof val === "string" ? val : JSON.stringify(val, undefined, 
2));
+      setValue(JSON.stringify(val, undefined, 2));
     }
   }, [data, isEditMode, isOpen]);
 

Reply via email to