This is an automated email from the ASF dual-hosted git repository.
jedcunningham pushed a commit to branch ui-hide-empty-log-groups
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/ui-hide-empty-log-groups by
this push:
new 793758c6851 Hide empty log groups in the task log view
793758c6851 is described below
commit 793758c68514af6c5ea1e49cd4c23374fb38d5d3
Author: Jed Cunningham <[email protected]>
AuthorDate: Mon Oct 26 12:11:08 2026 -0600
Hide empty log groups in the task log view
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.
---
.../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;
}