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

uranusjr 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 563bc494ab Fix UI rendering when XCom is INT, FLOAT, BOOL or NULL 
(#41516)
563bc494ab is described below

commit 563bc494ab5c610c46a60b2fe6beed742e3d588e
Author: Jens Scheffler <[email protected]>
AuthorDate: Tue Aug 20 05:56:03 2024 +0200

    Fix UI rendering when XCom is INT, FLOAT, BOOL or NULL (#41516)
---
 airflow/api_connexion/openapi/v1.yaml              |  1 +
 .../www/static/js/components/RenderedJsonField.tsx | 21 +-----
 airflow/www/static/js/components/utils.test.ts     | 84 ++++++++++++++++++++++
 airflow/www/static/js/components/utils.ts          | 38 ++++++++++
 .../js/dag/details/taskInstance/Xcom/XcomEntry.tsx |  9 ++-
 airflow/www/static/js/types/api-generated.ts       |  2 +-
 6 files changed, 134 insertions(+), 21 deletions(-)

diff --git a/airflow/api_connexion/openapi/v1.yaml 
b/airflow/api_connexion/openapi/v1.yaml
index fbd9a64eac..d0f5db43db 100644
--- a/airflow/api_connexion/openapi/v1.yaml
+++ b/airflow/api_connexion/openapi/v1.yaml
@@ -4134,6 +4134,7 @@ components:
                 - type: array
                   items: {}
                 - type: object
+                  nullable: true
               description: The value(s),
 
     # Python objects
diff --git a/airflow/www/static/js/components/RenderedJsonField.tsx 
b/airflow/www/static/js/components/RenderedJsonField.tsx
index 7000dc17ad..a0356f0a3a 100644
--- a/airflow/www/static/js/components/RenderedJsonField.tsx
+++ b/airflow/www/static/js/components/RenderedJsonField.tsx
@@ -30,32 +30,15 @@ import {
   useTheme,
   FlexProps,
 } from "@chakra-ui/react";
+import jsonParse from "./utils";
 
 interface Props extends FlexProps {
   content: string | object;
   jsonProps?: Omit<ReactJsonViewProps, "src">;
 }
 
-const JsonParse = (content: string | object) => {
-  let contentJson = null;
-  let contentFormatted = "";
-  let isJson = false;
-  try {
-    if (typeof content === "string") {
-      contentJson = JSON.parse(content);
-    } else {
-      contentJson = content;
-    }
-    contentFormatted = JSON.stringify(contentJson, null, 4);
-    isJson = true;
-  } catch (e) {
-    // skip
-  }
-  return [isJson, contentJson, contentFormatted];
-};
-
 const RenderedJsonField = ({ content, jsonProps, ...rest }: Props) => {
-  const [isJson, contentJson, contentFormatted] = JsonParse(content);
+  const [isJson, contentJson, contentFormatted] = jsonParse(content);
   const { onCopy, hasCopied } = useClipboard(contentFormatted);
   const theme = useTheme();
 
diff --git a/airflow/www/static/js/components/utils.test.ts 
b/airflow/www/static/js/components/utils.test.ts
new file mode 100644
index 0000000000..8df5938814
--- /dev/null
+++ b/airflow/www/static/js/components/utils.test.ts
@@ -0,0 +1,84 @@
+/*!
+ * 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 jsonParse from "./utils";
+
+/* global describe, test, expect */
+
+describe("JSON Parsing.", () => {
+  test.each([
+    {
+      testName: "null",
+      testContent: null,
+      expectedIsJson: false,
+    },
+    {
+      testName: "boolean",
+      testContent: true,
+      expectedIsJson: false,
+    },
+    {
+      testName: "int",
+      testContent: 42,
+      expectedIsJson: false,
+    },
+    {
+      testName: "float",
+      testContent: 3.1415,
+      expectedIsJson: false,
+    },
+    {
+      testName: "string",
+      testContent: "hello world",
+      expectedIsJson: false,
+    },
+    {
+      testName: "array",
+      testContent: ["hello world", 42, 3.1515],
+      expectedIsJson: true,
+    },
+    {
+      testName: "array as string",
+      testContent: JSON.stringify(["hello world", 42, 3.1515]),
+      expectedIsJson: true,
+    },
+    {
+      testName: "dict",
+      testContent: { key: 42 },
+      expectedIsJson: true,
+    },
+    {
+      testName: "dict as string",
+      testContent: JSON.stringify({ key: 42 }),
+      expectedIsJson: true,
+    },
+  ])(
+    "Input value is $testName",
+    ({ testName, testContent, expectedIsJson }) => {
+      const [isJson, contentJson, contentFormatted] = jsonParse(testContent);
+
+      expect(testName).not.toBeNull();
+      expect(isJson).toEqual(expectedIsJson);
+      if (expectedIsJson) {
+        expect(contentJson).not.toBeNull();
+        expect(contentFormatted.length).toBeGreaterThan(0);
+      }
+    }
+  );
+});
diff --git a/airflow/www/static/js/components/utils.ts 
b/airflow/www/static/js/components/utils.ts
new file mode 100644
index 0000000000..7ba2ee0573
--- /dev/null
+++ b/airflow/www/static/js/components/utils.ts
@@ -0,0 +1,38 @@
+/*!
+ * 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.
+ */
+
+const jsonParse = (content: any) => {
+  let contentJson = null;
+  let contentFormatted = "";
+  let isJson = false;
+  try {
+    if (typeof content === "string") {
+      contentJson = JSON.parse(content);
+    } else {
+      contentJson = content;
+    }
+    contentFormatted = JSON.stringify(contentJson, null, 4);
+    isJson = contentJson != null && typeof contentJson === "object"; // ensure 
numbers/bool are not treated as JSON
+  } catch (e) {
+    // skip
+  }
+  return [isJson, contentJson, contentFormatted];
+};
+
+export default jsonParse;
diff --git a/airflow/www/static/js/dag/details/taskInstance/Xcom/XcomEntry.tsx 
b/airflow/www/static/js/dag/details/taskInstance/Xcom/XcomEntry.tsx
index 2e9ba769ae..806209bccb 100644
--- a/airflow/www/static/js/dag/details/taskInstance/Xcom/XcomEntry.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/Xcom/XcomEntry.tsx
@@ -60,13 +60,20 @@ const XcomEntry = ({
     content = <Spinner />;
   } else if (error) {
     content = <ErrorAlert error={error} />;
-  } else if (!xcom || !xcom.value) {
+  } else if (!xcom) {
     content = (
       <Alert status="info">
         <AlertIcon />
         No value found for XCom key
       </Alert>
     );
+  } else if (!xcom.value) {
+    content = (
+      <Alert status="info">
+        <AlertIcon />
+        Value is NULL
+      </Alert>
+    );
   } else {
     let xcomString = "";
     if (typeof xcom.value !== "string") {
diff --git a/airflow/www/static/js/types/api-generated.ts 
b/airflow/www/static/js/types/api-generated.ts
index 30948df332..d5c1e06b6e 100644
--- a/airflow/www/static/js/types/api-generated.ts
+++ b/airflow/www/static/js/types/api-generated.ts
@@ -1658,7 +1658,7 @@ export interface components {
         Partial<number> &
         Partial<boolean> &
         Partial<unknown[]> &
-        Partial<{ [key: string]: unknown }>;
+        Partial<{ [key: string]: unknown } | null>;
     };
     /**
      * @description DAG details.

Reply via email to