Repository: kestra-io/kestra
Issue: Change icon topology vertically/horizontally.
Labels: frontend

### Expected Behavior

![image](https://github.com/kestra-io/kestra/assets/125994028/ef7b39b6-04f6-415b-881e-8b81e561d5fb)
Change the icons for a better understanding of the display mode of the topology. Thank you.

### Actual Behaviour

_No response_

### Steps To Reproduce

_No response_

### Environment Information

- Kestra Version:
- Operating System (OS / Docker / Kubernetes):
- Java Version (If not docker):


### Example flow

_No response_

Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/ui/src/assets/icons/SplitCellsHorizontal.vue b/ui/src/assets/icons/SplitCellsHorizontal.vue
new file mode 100644
index 0000000000..f9701ecb7b
--- /dev/null
+++ b/ui/src/assets/icons/SplitCellsHorizontal.vue
@@ -0,0 +1,24 @@
+<template>
+    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <g clip-path="url(#clip0_796_3996)">
+            <path d="M13.367 1.979C13.7368 1.979 14.0366 2.27881 14.0366 2.64863V13.3626C14.0366 13.7324 13.7368 14.0322 13.367 14.0322H2.65302C2.2832 14.0322 1.9834 13.7324 1.9834 13.3626V2.64863C1.9834 2.27881 2.2832 1.979 2.65302 1.979H13.367ZM7.34039 3.31825H3.32265V12.693H7.34039V10.0145H8.67963V12.693H12.6974V3.31825H8.67963V5.99675H7.34039V3.31825ZM10.0189 5.99675L12.0278 8.00562L10.0189 10.0145V8.67524H6.00114V10.0145L3.99227 8.00562L6.00114 5.99675V7.33599H10.0189V5.99675Z" fill="#CAC5DA" />
+        </g>
+        <defs>
+            <clipPath id="clip0_796_3996">
+                <rect width="16" height="16" fill="white" />
+            </clipPath>
+        </defs>
+    </svg>
+</template>
+
+<script>
+    export default {
+        name: "SplitCellsHorizontalIcon",
+        emits: ["click"]
+    }
+</script>
+<style scoped>
+    svg {
+        transform: scale(1.5);
+    }
+</style>
\ No newline at end of file
diff --git a/ui/src/assets/icons/SplitCellsVertical.vue b/ui/src/assets/icons/SplitCellsVertical.vue
new file mode 100644
index 0000000000..5da0ddf729
--- /dev/null
+++ b/ui/src/assets/icons/SplitCellsVertical.vue
@@ -0,0 +1,24 @@
+<template>
+    <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+        <g clip-path="url(#clip0_1111_7266)">
+            <path d="M1.97852 2.63349C1.97852 2.26366 2.27832 1.96387 2.64814 1.96387L13.3621 1.96387C13.7319 1.96387 14.0317 2.26366 14.0317 2.63349L14.0317 13.3475C14.0317 13.7173 13.7319 14.0171 13.3621 14.0171L2.64814 14.0171C2.27832 14.0171 1.97852 13.7173 1.97852 13.3475L1.97852 2.63349ZM3.31776 8.6601L3.31776 12.6778L12.6925 12.6778L12.6925 8.6601L10.014 8.6601L10.014 7.32085L12.6925 7.32085L12.6925 3.30311L3.31776 3.30311L3.31776 7.32086L5.99626 7.32085L5.99626 8.6601L3.31776 8.6601ZM5.99626 5.98161L8.00513 3.97274L10.014 5.98161L8.67475 5.98161L8.67475 9.99935L10.014 9.99935L8.00513 12.0082L5.99626 9.99935L7.3355 9.99935L7.3355 5.98161L5.99626 5.98161Z" fill="#CAC5DA" />
+        </g>
+        <defs>
+            <clipPath id="clip0_1111_7266">
+                <rect width="16" height="16" fill="white" transform="translate(0 16) rotate(-90)" />
+            </clipPath>
+        </defs>
+    </svg>
+</template>
+
+<script>
+    export default {
+        name: "SplitCellsVerticalIcon",
+        emits: ["click"]
+    }
+</script>
+<style scoped>
+    svg {
+        transform: scale(1.5);
+    }
+</style>
diff --git a/ui/src/components/graph/Topology.vue b/ui/src/components/graph/Topology.vue
index a9e903fd3f..e8292207bf 100644
--- a/ui/src/components/graph/Topology.vue
+++ b/ui/src/components/graph/Topology.vue
@@ -13,6 +13,8 @@
     import DotsVertical from "vue-material-design-icons/DotsVertical.vue";
     import ContentCopy from "vue-material-design-icons/ContentCopy.vue";
     import Delete from "vue-material-design-icons/Delete.vue";
+    import SplitCellsHorizontal from "../../assets/icons/SplitCellsHorizontal.vue"
+    import SplitCellsVertical from "../../assets/icons/SplitCellsVertical.vue"
 
     import BottomLine from "../../components/layout/BottomLine.vue";
     import TriggerFlow from "../../components/flows/TriggerFlow.vue";
@@ -109,6 +111,7 @@
 
     const editorWidthStorageKey = "editor-width";
     const editorWidthPercentage = ref(localStorage.getItem(editorWidthStorageKey));
+    const isHorizontal = ref(localStorage.getItem("topology-orientation") !== "0");
     const isLoading = ref(false);
     const elements = ref([])
     const haveChange = ref(false)
@@ -146,7 +149,7 @@
     const generateDagreGraph = () => {
         const dagreGraph = new dagre.graphlib.Graph({compound: true})
         dagreGraph.setDefaultEdgeLabel(() => ({}))
-        dagreGraph.setGraph({rankdir: showTopology.value === "topology" ? "LR" : "TB"})
+        dagreGraph.setGraph({rankdir: isHorizontal.value ? "LR" : "TB"})
 
         for (const node of props.flowGraph.nodes) {
             dagreGraph.setNode(node.uid, {
@@ -215,7 +218,7 @@
     };
 
     const getNodeHeight = (node) => {
-        return isTaskNode(node) || isTriggerNode(node) ? 55 : (showTopology.value === "topology" ? 55 : 5);
+        return isTaskNode(node) || isTriggerNode(node) ? 55 : (isHorizontal.value ? 55 : 5);
     };
 
     const getNodePosition = (n, parent, alignTo) => {
@@ -242,6 +245,16 @@
         }
     }
 
+    const toggleOrientation = () => {
+        localStorage.setItem(
+            "topology-orientation",
+            localStorage.getItem("topology-orientation") !== "0" ? "0" : "1"
+        );
+        isHorizontal.value = localStorage.getItem("topology-orientation") === "1";
+        regenerateGraph();
+        fitView();
+    };
+
     const generateGraph = () => {
         isLoading.value = true;
         if (!props.flowGraph) {
@@ -254,8 +267,8 @@
                     width: "5px",
                     height: "5px"
                 },
-                sourcePosition: showTopology.value === "topology" ? Position.Right : Position.Bottom,
-                targetPosition: showTopology.value === "topology" ? Position.Left : Position.Top,
+                sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
+                targetPosition: isHorizontal.value ? Position.Left : Position.Top,
                 parentNode: undefined,
                 draggable: false,
             })
@@ -263,13 +276,13 @@
                 id: "end",
                 label: "",
                 type: "dot",
-                position: showTopology.value === "topology" ? {x: 50, y: 0} : {x: 0, y: 50},
+                position: isHorizontal.value ? {x: 50, y: 0} : {x: 0, y: 50},
                 style: {
                     width: "5px",
                     height: "5px"
                 },
-                sourcePosition: showTopology.value === "topology" ? Position.Right : Position.Bottom,
-                targetPosition: showTopology.value === "topology" ? Position.Left : Position.Top,
+                sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
+                targetPosition: isHorizontal.value ? Position.Left : Position.Top,
                 parentNode: undefined,
                 draggable: false,
             })
@@ -314,8 +327,8 @@
                 parentNode: parentNode,
                 position: getNodePosition(dagreNode, parentNode ? dagreGraph.node(parentNode) : undefined),
                 style: {
-                    width: clusterUid === "Triggers" && showTopology.value === "topology" ? "400px" : dagreNode.width + "px",
-                    height: clusterUid === "Triggers" && !showTopology.value === "topology" ? "250px" : dagreNode.height + "px",
+                    width: clusterUid === "Triggers" && isHorizontal.value ? "400px" : dagreNode.width + "px",
+                    height: clusterUid === "Triggers" && !isHorizontal.value ? "250px" : dagreNode.height + "px",
                 },
             })
         }
@@ -341,8 +354,8 @@
                     width: getNodeWidth(node) + "px",
                     height: getNodeHeight(node) + "px"
                 },
-                sourcePosition: showTopology.value === "topology" ? Position.Right : Position.Bottom,
-                targetPosition: showTopology.value === "topology" ? Position.Left : Position.Top,
+                sourcePosition: isHorizontal.value ? Position.Right : Position.Bottom,
+                targetPosition: isHorizontal.value ? Position.Left : Position.Top,
                 parentNode: clusters[node.uid] ? clusters[node.uid].uid : undefined,
                 draggable: nodeType === "task" && !props.isReadOnly,
                 data: {
@@ -782,6 +795,7 @@
     const switchView = (event) => {
         showTopology.value = event
         if (["topology", "combined"].includes(showTopology.value)) {
+            isHorizontal.value = showTopology.value === "combined" ? false : localStorage.getItem("topology-orientation") === "1";
             if (updatedFromEditor.value) {
                 onEdit(flowYaml.value)
                 updatedFromEditor.value = false;
@@ -997,7 +1011,13 @@
                         :is-allowed-edit="isAllowedEdit()"
                     />
                 </template>
-                <Controls :show-interactive="false" />
+
+                <Controls :show-interactive="false">
+                    <ControlButton @click="toggleOrientation" v-if="showTopology === 'topology'">
+                        <SplitCellsVertical :size="48" v-if="!isHorizontal" />
+                        <SplitCellsHorizontal v-if="isHorizontal" />
+                    </ControlButton>
+                </Controls>
             </VueFlow>
         </div>
         <PluginDocumentation


Test patch: