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

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


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new d563b90edd5 [v3-4-test] Hide empty log groups in the task log view 
(#75686) (#75801)
d563b90edd5 is described below

commit d563b90edd584011880c696ea4bbe1536e300046
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Oct 27 11:39:58 2026 -0400

    [v3-4-test] Hide empty log groups in the task log view (#75686) (#75801)
    
    The task runner always opens a "Post Execute" log group after execute()
    returns, but for a plain task nothing is logged at INFO before it is
    closed, so the UI showed a section header that expanded to nothing.
    
    When parsing logs, drop a group header if the group closes with no
    entries in it. A parent containing only empty groups is dropped too.
    Unclosed groups are kept so a running task still shows its current
    section.
    (cherry picked from commit 8f7a8e4ff58104c2bc73b5e59e6a9b29b63d5521)
    
    Co-authored-by: Jed Cunningham 
<[email protected]>
---
 .../src/airflow/ui/src/queries/useLogs.test.ts     | 38 ++++++++++++++++++++++
 .../src/airflow/ui/src/queries/useLogs.tsx         | 11 +++++--
 2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/queries/useLogs.test.ts 
b/airflow-core/src/airflow/ui/src/queries/useLogs.test.ts
index 958bb86d515..68d824b02be 100644
--- a/airflow-core/src/airflow/ui/src/queries/useLogs.test.ts
+++ b/airflow-core/src/airflow/ui/src/queries/useLogs.test.ts
@@ -31,6 +31,13 @@ import { getLogLineText, parseLogs } from "./useLogs";
 
 const translate = ((key: string) => key) as unknown as TFunction;
 
+const msg = (event: string) =>
+  ({ event, level: "info", timestamp: "2026-01-01T00:00:00Z" }) as 
StructuredLogMessage;
+const headers = (data: Array<StructuredLogMessage>) =>
+  (parseLogs({ data, translate, tryNumber: 1 }).parsedLogs ?? [])
+    .filter((entry) => entry.group?.type === "header")
+    .map((entry) => entry.element);
+
 describe("getLogLineText", () => {
   it("renders a structured line with timestamp and level, stripping ANSI 
codes", () => {
     const logMessage = {
@@ -108,3 +115,34 @@ describe("parseLogs line links", () => {
     expect(screen.getByRole("link", { name: "0" })).toHaveAttribute("href", 
href);
   });
 });
+
+describe("parseLogs log groups", () => {
+  it("drops a closed group with no entries", () => {
+    expect(
+      headers([
+        msg("::group::Pre Execute"),
+        msg("starting"),
+        msg("::endgroup::"),
+        msg("Done."),
+        msg("::group::Post Execute"),
+        msg("::endgroup::"),
+      ]),
+    ).toEqual(["Pre Execute"]);
+  });
+
+  it("keeps a closed group that has entries", () => {
+    expect(headers([msg("::group::Post Execute"), msg("post hook ran"), 
msg("::endgroup::")])).toEqual([
+      "Post Execute",
+    ]);
+  });
+
+  it("drops a parent group that only contains empty groups", () => {
+    expect(
+      headers([msg("::group::Outer"), msg("::group::Inner"), 
msg("::endgroup::"), msg("::endgroup::")]),
+    ).toEqual([]);
+  });
+
+  it("keeps an unclosed empty group so a running task still shows its current 
section", () => {
+    expect(headers([msg("::group::Post Execute")])).toEqual(["Post Execute"]);
+  });
+});
diff --git a/airflow-core/src/airflow/ui/src/queries/useLogs.tsx 
b/airflow-core/src/airflow/ui/src/queries/useLogs.tsx
index 759fa1a636b..ba8a7969da8 100644
--- a/airflow-core/src/airflow/ui/src/queries/useLogs.tsx
+++ b/airflow-core/src/airflow/ui/src/queries/useLogs.tsx
@@ -195,7 +195,7 @@ export const parseLogs = ({
   }
 
   const flatEntries: Array<ParsedLogEntry> = (() => {
-    type Group = { id: number; level: number; name: string };
+    type Group = { headerIndex: number; id: number; level: number; name: 
string };
     const groupStack: Array<Group> = [];
     const result: Array<ParsedLogEntry> = [];
     let nextGroupId = 0;
@@ -221,7 +221,7 @@ export const parseLogs = ({
         const level = groupStack.length;
         const parentGroup = groupStack[groupStack.length - 1];
 
-        groupStack.push({ id, level, name: groupName });
+        groupStack.push({ headerIndex: result.length, id, level, name: 
groupName });
         result.push({
           element: groupName,
           group: { id, level, parentId: parentGroup?.id, type: "header" },
@@ -231,7 +231,12 @@ export const parseLogs = ({
       }
 
       if (text.includes("::endgroup::")) {
-        groupStack.pop();
+        const closedGroup = groupStack.pop();
+
+        // Hide groups that closed with nothing in them.
+        if (closedGroup !== undefined && result.length === 
closedGroup.headerIndex + 1) {
+          result.pop();
+        }
 
         return;
       }

Reply via email to