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.

Reply via email to