This is an automated email from the ASF dual-hosted git repository.
jedcunningham 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 bdf2384d0ff [v3-4-test] Show Dag version diff occurrence count on the
path (#75433) (#75441)
bdf2384d0ff is described below
commit bdf2384d0ff18772ca9e3db829636e91a435e14e
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 23 08:14:11 2026 -0600
[v3-4-test] Show Dag version diff occurrence count on the path (#75433)
(#75441)
Anyone who can see values, the overwhelmingly common case, got an
Occurrences column that was 1 on every row: each such record stands for
exactly one change. Even redacted, only a path that hides a task, tag or
group behind "*" merges changes. Drop the column and show a "xN" badge
("xN+" for a truncated diff) after the path when a record stands for
more than one change, with a tooltip explaining the count.
The badge is outlined purple so it stands apart from the impact badges.
Remove the now-unused column label from all locales.
(cherry picked from commit 29bb1401c83bdd28a9bf113fcb6242cdc690023c)
Co-authored-by: Jed Cunningham
<[email protected]>
---
.../src/airflow/ui/public/i18n/locales/ca/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/de/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/el/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/en/dag.json | 5 +++-
.../src/airflow/ui/public/i18n/locales/fr/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/he/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/hi/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/ko/dag.json | 1 -
.../src/airflow/ui/public/i18n/locales/pl/dag.json | 1 -
.../airflow/ui/public/i18n/locales/zh-TW/dag.json | 1 -
.../airflow/ui/src/pages/Dag/Versions/PathText.tsx | 33 ++++++++++++++++++++--
.../ui/src/pages/Dag/Versions/VersionDiff.test.tsx | 27 +++++++++++++++---
.../ui/src/pages/Dag/Versions/VersionDiff.tsx | 19 +++++++------
13 files changed, 69 insertions(+), 24 deletions(-)
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ca/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/ca/dag.json
index d1b82f2692a..33981ccc603 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ca/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ca/dag.json
@@ -275,7 +275,6 @@
"before": "Abans",
"category": "Categoria",
"impact": "Impacte",
- "occurrences": "Ocurrències",
"operation": "Operació",
"path": "Ruta"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/de/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/de/dag.json
index 72e8c6c25ae..9d9ca9325a7 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/de/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/de/dag.json
@@ -323,7 +323,6 @@
"before": "Vor",
"category": "Kategorie",
"impact": "Auswirkung",
- "occurrences": "Auftritte",
"operation": "Einsatz",
"path": "Pfad"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/el/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/el/dag.json
index 5131752f687..410a282d730 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/el/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/el/dag.json
@@ -275,7 +275,6 @@
"before": "Πριν",
"category": "Κατηγορία",
"impact": "Επίδραση",
- "occurrences": "Εμφανίσεις",
"operation": "Διαδικασία",
"path": "Διαδρομή"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
index b098205824e..0adce848164 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dag.json
@@ -323,7 +323,6 @@
"before": "Before",
"category": "Category",
"impact": "Impact",
- "occurrences": "Occurrences",
"operation": "Operation",
"path": "Path"
},
@@ -349,6 +348,10 @@
"changed": "Changed",
"removed": "Removed"
},
+ "pathOccurrences_one": "{{count, number}} change shares this path",
+ "pathOccurrences_other": "{{count, number}} changes share this path",
+ "pathOccurrencesAtLeast_one": "At least {{count, number}} change shares
this path",
+ "pathOccurrencesAtLeast_other": "At least {{count, number}} changes share
this path",
"record_one": "Record",
"record_other": "Records",
"schemaChange": "Serialized schema {{baseSchema}} → {{targetSchema}}",
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/fr/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/fr/dag.json
index 9f0e0809b82..da22e065df5 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/fr/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/fr/dag.json
@@ -284,7 +284,6 @@
"before": "Avant",
"category": "Catégorie",
"impact": "Impact",
- "occurrences": "Occurrences",
"operation": "Opération",
"path": "Chemin"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/he/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/he/dag.json
index 5dcad08944f..8560e873c6b 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/he/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/he/dag.json
@@ -315,7 +315,6 @@
"before": "לפני",
"category": "קטגוריה",
"impact": "השפעה",
- "occurrences": "מופעים",
"operation": "פעולה",
"path": "נתיב"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json
index fc398232ed5..4f7b867e0c1 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/hi/dag.json
@@ -323,7 +323,6 @@
"before": "पहले",
"category": "श्रेणी",
"impact": "प्रभाव",
- "occurrences": "घटनाएँ",
"operation": "संक्रिया",
"path": "पथ"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/ko/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/ko/dag.json
index 8c9f4073fab..f54715a3957 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/ko/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/ko/dag.json
@@ -322,7 +322,6 @@
"before": "변경 전",
"category": "분류",
"impact": "영향",
- "occurrences": "발생 횟수",
"operation": "변경 유형",
"path": "경로"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/pl/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/pl/dag.json
index 6842f221cbb..bdba313ed1b 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/pl/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/pl/dag.json
@@ -293,7 +293,6 @@
"before": "Przed",
"category": "Kategoria",
"impact": "Wpływ",
- "occurrences": "Wystąpienia",
"operation": "Operacja",
"path": "Ścieżka"
},
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json
b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json
index 521b6ed6aaf..22f33b42dd7 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/zh-TW/dag.json
@@ -323,7 +323,6 @@
"before": "變更前",
"category": "類別",
"impact": "影響",
- "occurrences": "出現次數",
"operation": "操作",
"path": "路徑"
},
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/PathText.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/PathText.tsx
index 08a046fb819..1e110a37e15 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/PathText.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/PathText.tsx
@@ -18,20 +18,29 @@
*/
import { Fragment } from "react";
+import { Badge, HStack } from "@chakra-ui/react";
+import { useTranslation } from "react-i18next";
+
+import { Tooltip } from "src/system-components";
+
import { MonoText } from "./MonoText";
type PathTextProps = {
+ readonly occurrences?: number;
readonly path: string;
+ // A truncated diff stopped counting early, so every count is a lower bound.
+ readonly truncated?: boolean;
};
// A path has no spaces, and `overflow-wrap: anywhere` would let it break
after any character,
// which drops the column's min-content width to one glyph — the other columns
then squeeze it
// until the path reads vertically. Offering a break after each separator
instead keeps the column
// at least as wide as the longest segment and breaks where the path already
divides.
-export const PathText = ({ path }: PathTextProps) => {
+export const PathText = ({ occurrences = 1, path, truncated = false }:
PathTextProps) => {
+ const { t: translate } = useTranslation("dag");
let walked = "";
- return (
+ const text = (
<MonoText overflowWrap="break-word">
{path.split("/").map((segment, index) => {
walked += index === 0 ? segment : `/${segment}`;
@@ -46,4 +55,24 @@ export const PathText = ({ path }: PathTextProps) => {
})}
</MonoText>
);
+
+ if (occurrences <= 1) {
+ return text;
+ }
+
+ return (
+ <HStack alignItems="baseline" gap={2}>
+ {text}
+ <Tooltip
+ content={translate(truncated ? "versions.pathOccurrencesAtLeast" :
"versions.pathOccurrences", {
+ count: occurrences,
+ })}
+ portalled
+ >
+ <Badge colorPalette="purple" flexShrink={0} size="sm"
variant="outline">
+ {`×${occurrences}${truncated ? "+" : ""}`}
+ </Badge>
+ </Tooltip>
+ </HStack>
+ );
};
diff --git
a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.test.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.test.tsx
index 18bedaded99..4938d347b6d 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.test.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.test.tsx
@@ -108,6 +108,25 @@ describe("VersionDiff summary", () => {
});
describe("VersionDiff", () => {
+ it("shows a merged record's count beside its path rather than in a column",
() => {
+ renderDiff(redacted);
+
+ expect(screen.getByText("×3")).toBeInTheDocument();
+
expect(screen.queryByText("versions.columns.occurrences")).not.toBeInTheDocument();
+ });
+
+ it("marks a truncated count as a lower bound", () => {
+ renderDiff({ ...redacted, truncated: true });
+
+ expect(screen.getByText("×3+")).toBeInTheDocument();
+ });
+
+ it("adds no count to a record that stands for one change", () => {
+ renderDiff(authorized);
+
+ expect(screen.queryByText(/^×/u)).not.toBeInTheDocument();
+ });
+
it("withholds value columns when the caller may not see values", () => {
renderDiff(redacted);
@@ -194,7 +213,7 @@ describe("VersionDiff", () => {
const cells = within(screen.getAllByRole("row")[1] as
HTMLElement).getAllByRole("cell");
// A dash would read as a value the base version holds, rather than as a
side that is absent.
- expect(cells[5]).toBeEmptyDOMElement();
+ expect(cells[4]).toBeEmptyDOMElement();
expect(screen.queryByText("—")).not.toBeInTheDocument();
});
@@ -289,9 +308,9 @@ describe("VersionDiff", () => {
const cells = within(screen.getAllByRole("row")[1] as
HTMLElement).getAllByRole("cell");
- expect(cells[4]).toHaveTextContent("1");
- expect(cells[5]).toHaveTextContent("alice");
- expect(cells[6]).toHaveTextContent("bob");
+ expect(cells).toHaveLength(6);
+ expect(cells[4]).toHaveTextContent("alice");
+ expect(cells[5]).toHaveTextContent("bob");
});
it("shows no disclosure chrome when the caller may see values", () => {
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.tsx
index da395557d79..0e0a15cdd26 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Versions/VersionDiff.tsx
@@ -65,10 +65,18 @@ const buildValueColumns = (translate: TFunction<"dag">):
ChangeColumns => [
},
];
-const buildColumns = (translate: TFunction<"dag">, valuesShown: boolean):
ChangeColumns => [
+const buildColumns = (
+ translate: TFunction<"dag">,
+ valuesShown: boolean,
+ truncated: boolean,
+): ChangeColumns => [
{
accessorKey: "path",
- cell: ({ row }) => <PathText path={row.original.path} />,
+ // Only a redacted record that merged changes sharing a public path counts
more than one, so
+ // the count rides on the path it qualifies instead of a column that is
almost always 1.
+ cell: ({ row }) => (
+ <PathText occurrences={row.original.occurrence_count}
path={row.original.path} truncated={truncated} />
+ ),
enableSorting: false,
header: translate("versions.columns.path"),
},
@@ -100,11 +108,6 @@ const buildColumns = (translate: TFunction<"dag">,
valuesShown: boolean): Change
enableSorting: false,
header: translate("versions.columns.impact"),
},
- {
- accessorKey: "occurrence_count",
- enableSorting: false,
- header: translate("versions.columns.occurrences"),
- },
...(valuesShown ? buildValueColumns(translate) : []),
];
@@ -119,7 +122,7 @@ export const VersionDiff = ({ baseVersionNumber, diff,
targetVersionNumber }: Ve
return (
<DataTable
- columns={buildColumns(translate, valuesShown)}
+ columns={buildColumns(translate, valuesShown, diff.truncated)}
data={diff.changes}
// The summary heads the table's own header row, so the comparison, what
it leaves out and
// the control over its columns read as one line rather than three
stacked ones.