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

pierrejeambrun 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 63b5cd63e63 Add support for changing graph orientation (#47923)
63b5cd63e63 is described below

commit 63b5cd63e63a95eef5f8a76d7a4aba30f7664de9
Author: Aritra Basu <[email protected]>
AuthorDate: Wed Mar 26 19:13:46 2025 +0530

    Add support for changing graph orientation (#47923)
    
    * WIP: Initial graph
    
    * Add icon and rerender
    
    * Implemented direction dropdown
---
 .../ui/src/components/Graph/useGraphLayout.ts      | 13 ++++++++--
 .../airflow/ui/src/layouts/Details/Graph/Graph.tsx |  5 ++--
 .../ui/src/layouts/Details/PanelButtons.tsx        | 29 ++++++++++++++++++++++
 3 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts 
b/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
index bb46eb76996..9c74318db75 100644
--- a/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
+++ b/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+import { createListCollection } from "@chakra-ui/react";
 import { useQuery } from "@tanstack/react-query";
 import ELK, { type ElkNode, type ElkExtendedEdge, type ElkShape } from "elkjs";
 
@@ -23,7 +24,15 @@ import type { EdgeResponse, NodeResponse, 
StructureDataResponse } from "openapi/
 
 import { flattenGraph, formatFlowEdges } from "./reactflowUtils";
 
-type Direction = "BOTTOM" | "LEFT" | "RIGHT" | "TOP";
+export type Direction = "DOWN" | "LEFT" | "RIGHT" | "UP";
+export const directionOptions = createListCollection({
+  items: [
+    { label: "Left to Right", value: "RIGHT" as Direction },
+    { label: "Right to Left", value: "LEFT" as Direction },
+    { label: "Bottom to Top", value: "UP" as Direction },
+    { label: "Top to Bottom", value: "DOWN" as Direction },
+  ],
+});
 
 type EdgeLabel = {
   height: number;
@@ -258,5 +267,5 @@ export const useGraphLayout = ({
 
       return { edges: formattedEdges, nodes: flattenedData.nodes };
     },
-    queryKey: ["graphLayout", nodes, openGroupIds, versionNumber, edges],
+    queryKey: ["graphLayout", nodes, openGroupIds, versionNumber, edges, 
direction],
   });
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx
index cd2dadf0a55..0497c455b57 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Graph/Graph.tsx
@@ -36,7 +36,7 @@ import Edge from "src/components/Graph/Edge";
 import { JoinNode } from "src/components/Graph/JoinNode";
 import { TaskNode } from "src/components/Graph/TaskNode";
 import type { CustomNodeProps } from "src/components/Graph/reactflowUtils";
-import { useGraphLayout } from "src/components/Graph/useGraphLayout";
+import { type Direction, useGraphLayout } from 
"src/components/Graph/useGraphLayout";
 import { useColorMode } from "src/context/colorMode";
 import { useOpenGroups } from "src/context/openGroups";
 import useSelectedVersion from "src/hooks/useSelectedVersion";
@@ -95,6 +95,7 @@ export const Graph = () => {
   const refetchInterval = useAutoRefresh({ dagId });
 
   const [dependencies] = useLocalStorage<"all" | "immediate" | 
"tasks">(`dependencies-${dagId}`, "immediate");
+  const [direction] = useLocalStorage<Direction>(`direction-${dagId}`, 
"RIGHT");
 
   const selectedColor = colorMode === "dark" ? selectedDarkColor : 
selectedLightColor;
 
@@ -121,7 +122,7 @@ export const Graph = () => {
   const dagDepNodes = dependencies === "all" ? dagDependencies.nodes : [];
 
   const { data } = useGraphLayout({
-    direction: "RIGHT",
+    direction,
     edges: [...graphData.edges, ...dagDepEdges],
     nodes: dagDepNodes.length
       ? dagDepNodes.map((node) =>
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
index bae3c7a5957..002ee2e9aca 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/PanelButtons.tsx
@@ -31,6 +31,7 @@ import { useParams } from "react-router-dom";
 import { useLocalStorage } from "usehooks-ts";
 
 import DagVersionSelect from "src/components/DagVersionSelect";
+import { directionOptions, type Direction } from 
"src/components/Graph/useGraphLayout";
 import { Select } from "src/components/ui";
 
 import { DagRunSelect } from "./DagRunSelect";
@@ -60,6 +61,7 @@ export const PanelButtons = ({ dagView, limit, setDagView, 
setLimit, ...rest }:
     `dependencies-${dagId}`,
     "immediate",
   );
+  const [direction, setDirection] = 
useLocalStorage<Direction>(`direction-${dagId}`, "RIGHT");
   const displayRunOptions = createListCollection({
     items: [
       { label: "5", value: "5" },
@@ -84,6 +86,14 @@ export const PanelButtons = ({ dagView, limit, setDagView, 
setLimit, ...rest }:
     }
   };
 
+  const handleDirectionUpdate = (
+    event: SelectValueChangeDetails<{ label: string; value: Array<string> }>,
+  ) => {
+    if (event.value[0] !== undefined) {
+      setDirection(event.value[0] as Direction);
+    }
+  };
+
   return (
     <HStack
       alignItems="flex-start"
@@ -140,6 +150,25 @@ export const PanelButtons = ({ dagView, limit, setDagView, 
setLimit, ...rest }:
         </HStack>
         {dagView === "graph" ? (
           <>
+            <Select.Root
+              bg="bg"
+              collection={directionOptions}
+              onValueChange={handleDirectionUpdate}
+              size="sm"
+              value={[direction]}
+              width="150px"
+            >
+              <Select.Trigger>
+                <Select.ValueText />
+              </Select.Trigger>
+              <Select.Content>
+                {directionOptions.items.map((option) => (
+                  <Select.Item item={option} key={option.value}>
+                    {option.label}
+                  </Select.Item>
+                ))}
+              </Select.Content>
+            </Select.Root>
             <Select.Root
               bg="bg"
               collection={options}

Reply via email to