mirror of
https://github.com/ansible/awx.git
synced 2024-10-31 23:51:09 +03:00
Merge pull request #5379 from mabashian/ui-next-workflows
Workflows pt 1: The Rendering Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
commit
715483c669
289
awx/ui_next/package-lock.json
generated
289
awx/ui_next/package-lock.json
generated
@ -4881,8 +4881,7 @@
|
|||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
|
||||||
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
|
"integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"commondir": {
|
"commondir": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
@ -5333,6 +5332,279 @@
|
|||||||
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
|
"integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"d3": {
|
||||||
|
"version": "5.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3/-/d3-5.12.0.tgz",
|
||||||
|
"integrity": "sha512-flYVMoVuhPFHd9zVCe2BxIszUWqBcd5fvQGMNRmSiBrgdnh6Vlruh60RJQTouAK9xPbOB0plxMvBm4MoyODXNg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "1",
|
||||||
|
"d3-axis": "1",
|
||||||
|
"d3-brush": "1",
|
||||||
|
"d3-chord": "1",
|
||||||
|
"d3-collection": "1",
|
||||||
|
"d3-color": "1",
|
||||||
|
"d3-contour": "1",
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-drag": "1",
|
||||||
|
"d3-dsv": "1",
|
||||||
|
"d3-ease": "1",
|
||||||
|
"d3-fetch": "1",
|
||||||
|
"d3-force": "1",
|
||||||
|
"d3-format": "1",
|
||||||
|
"d3-geo": "1",
|
||||||
|
"d3-hierarchy": "1",
|
||||||
|
"d3-interpolate": "1",
|
||||||
|
"d3-path": "1",
|
||||||
|
"d3-polygon": "1",
|
||||||
|
"d3-quadtree": "1",
|
||||||
|
"d3-random": "1",
|
||||||
|
"d3-scale": "2",
|
||||||
|
"d3-scale-chromatic": "1",
|
||||||
|
"d3-selection": "1",
|
||||||
|
"d3-shape": "1",
|
||||||
|
"d3-time": "1",
|
||||||
|
"d3-time-format": "2",
|
||||||
|
"d3-timer": "1",
|
||||||
|
"d3-transition": "1",
|
||||||
|
"d3-voronoi": "1",
|
||||||
|
"d3-zoom": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-array": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw=="
|
||||||
|
},
|
||||||
|
"d3-axis": {
|
||||||
|
"version": "1.0.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz",
|
||||||
|
"integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ=="
|
||||||
|
},
|
||||||
|
"d3-brush": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-v8bbYyCFKjyCzFk/tdWqXwDykY8YWqhXYjcYxfILIit085VZOpj4XJKOMccTsvWxgzSLMJQg5SiqHjslsipEDg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-drag": "1",
|
||||||
|
"d3-interpolate": "1",
|
||||||
|
"d3-selection": "1",
|
||||||
|
"d3-transition": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-chord": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "1",
|
||||||
|
"d3-path": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-collection": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A=="
|
||||||
|
},
|
||||||
|
"d3-color": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-NHODMBlj59xPAwl2BDiO2Mog6V+PrGRtBfWKqKRrs9MCqlSkIEb0Z/SfY7jW29ReHTDC/j+vwXhnZcXI3+3fbg=="
|
||||||
|
},
|
||||||
|
"d3-contour": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-dispatch": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g=="
|
||||||
|
},
|
||||||
|
"d3-drag": {
|
||||||
|
"version": "1.2.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.4.tgz",
|
||||||
|
"integrity": "sha512-ICPurDETFAelF1CTHdIyiUM4PsyZLaM+7oIBhmyP+cuVjze5vDZ8V//LdOFjg0jGnFIZD/Sfmk0r95PSiu78rw==",
|
||||||
|
"requires": {
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-selection": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-dsv": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.1.1.tgz",
|
||||||
|
"integrity": "sha512-1EH1oRGSkeDUlDRbhsFytAXU6cAmXFzc52YUe6MRlPClmWb85MP1J5x+YJRzya4ynZWnbELdSAvATFW/MbxaXw==",
|
||||||
|
"requires": {
|
||||||
|
"commander": "2",
|
||||||
|
"iconv-lite": "0.4",
|
||||||
|
"rw": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-ease": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-Ct1O//ly5y5lFM9YTdu+ygq7LleSgSE4oj7vUt9tPLHUi8VCV7QoizGpdWRWAwCO9LdYzIrQDg97+hGVdsSGPQ=="
|
||||||
|
},
|
||||||
|
"d3-fetch": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==",
|
||||||
|
"requires": {
|
||||||
|
"d3-dsv": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-force": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-collection": "1",
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-quadtree": "1",
|
||||||
|
"d3-timer": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-format": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-TUswGe6hfguUX1CtKxyG2nymO+1lyThbkS1ifLX0Sr+dOQtAD5gkrffpHnx+yHNKUZ0Bmg5T4AjUQwugPDrm0g=="
|
||||||
|
},
|
||||||
|
"d3-geo": {
|
||||||
|
"version": "1.11.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.6.tgz",
|
||||||
|
"integrity": "sha512-z0J8InXR9e9wcgNtmVnPTj0TU8nhYT6lD/ak9may2PdKqXIeHUr8UbFLoCtrPYNsjv6YaLvSDQVl578k6nm7GA==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-hierarchy": {
|
||||||
|
"version": "1.1.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.8.tgz",
|
||||||
|
"integrity": "sha512-L+GHMSZNwTpiq4rt9GEsNcpLa4M96lXMR8M/nMG9p5hBE0jy6C+3hWtyZMenPQdwla249iJy7Nx0uKt3n+u9+w=="
|
||||||
|
},
|
||||||
|
"d3-interpolate": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==",
|
||||||
|
"requires": {
|
||||||
|
"d3-color": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-path": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-J6EfUNwcMQ+aM5YPOB8ZbgAZu6wc82f/0WFxrxwV6Ll8wBwLaHLKCqQ5Imub02JriCVVdPjgI+6P3a4EWJCxAg=="
|
||||||
|
},
|
||||||
|
"d3-polygon": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-RHhh1ZUJZfhgoqzWWuRhzQJvO7LavchhitSTHGu9oj6uuLFzYZVeBzaWTQ2qSO6bz2w55RMoOCf0MsLCDB6e0w=="
|
||||||
|
},
|
||||||
|
"d3-quadtree": {
|
||||||
|
"version": "1.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.6.tgz",
|
||||||
|
"integrity": "sha512-NUgeo9G+ENQCQ1LsRr2qJg3MQ4DJvxcDNCiohdJGHt5gRhBW6orIB5m5FJ9kK3HNL8g9F4ERVoBzcEwQBfXWVA=="
|
||||||
|
},
|
||||||
|
"d3-random": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ=="
|
||||||
|
},
|
||||||
|
"d3-scale": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==",
|
||||||
|
"requires": {
|
||||||
|
"d3-array": "^1.2.0",
|
||||||
|
"d3-collection": "1",
|
||||||
|
"d3-format": "1",
|
||||||
|
"d3-interpolate": "1",
|
||||||
|
"d3-time": "1",
|
||||||
|
"d3-time-format": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-scale-chromatic": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-color": "1",
|
||||||
|
"d3-interpolate": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-selection": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-EYVwBxQGEjLCKF2pJ4+yrErskDnz5v403qvAid96cNdCMr8rmCYfY5RGzWz24mdIbxmDf6/4EAH+K9xperD5jg=="
|
||||||
|
},
|
||||||
|
"d3-shape": {
|
||||||
|
"version": "1.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.5.tgz",
|
||||||
|
"integrity": "sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==",
|
||||||
|
"requires": {
|
||||||
|
"d3-path": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-time": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw=="
|
||||||
|
},
|
||||||
|
"d3-time-format": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==",
|
||||||
|
"requires": {
|
||||||
|
"d3-time": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-timer": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz",
|
||||||
|
"integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg=="
|
||||||
|
},
|
||||||
|
"d3-transition": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-VJ7cmX/FPIPJYuaL2r1o1EMHLttvoIuZhhuAlRoOxDzogV8iQS6jYulDm3xEU3TqL80IZIhI551/ebmCMrkvhw==",
|
||||||
|
"requires": {
|
||||||
|
"d3-color": "1",
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-ease": "1",
|
||||||
|
"d3-interpolate": "1",
|
||||||
|
"d3-selection": "^1.1.0",
|
||||||
|
"d3-timer": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"d3-voronoi": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg=="
|
||||||
|
},
|
||||||
|
"d3-zoom": {
|
||||||
|
"version": "1.8.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz",
|
||||||
|
"integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==",
|
||||||
|
"requires": {
|
||||||
|
"d3-dispatch": "1",
|
||||||
|
"d3-drag": "1",
|
||||||
|
"d3-interpolate": "1",
|
||||||
|
"d3-selection": "1",
|
||||||
|
"d3-transition": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dagre": {
|
||||||
|
"version": "0.8.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.4.tgz",
|
||||||
|
"integrity": "sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==",
|
||||||
|
"requires": {
|
||||||
|
"graphlib": "^2.1.7",
|
||||||
|
"lodash": "^4.17.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"damerau-levenshtein": {
|
"damerau-levenshtein": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz",
|
||||||
@ -8149,6 +8421,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
|
||||||
},
|
},
|
||||||
|
"graphlib": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==",
|
||||||
|
"requires": {
|
||||||
|
"lodash": "^4.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"growly": {
|
"growly": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
|
||||||
@ -14260,6 +14540,11 @@
|
|||||||
"aproba": "^1.1.1"
|
"aproba": "^1.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rw": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz",
|
||||||
|
"integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q="
|
||||||
|
},
|
||||||
"rx": {
|
"rx": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz",
|
||||||
|
@ -65,6 +65,8 @@
|
|||||||
"ansi-to-html": "^0.6.11",
|
"ansi-to-html": "^0.6.11",
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.1",
|
||||||
"codemirror": "^5.47.0",
|
"codemirror": "^5.47.0",
|
||||||
|
"d3": "^5.12.0",
|
||||||
|
"dagre": "^0.8.4",
|
||||||
"formik": "^1.5.1",
|
"formik": "^1.5.1",
|
||||||
"has-ansi": "^3.0.0",
|
"has-ansi": "^3.0.0",
|
||||||
"html-entities": "^1.2.1",
|
"html-entities": "^1.2.1",
|
||||||
|
@ -5,6 +5,10 @@ class WorkflowJobTemplates extends Base {
|
|||||||
super(http);
|
super(http);
|
||||||
this.baseUrl = '/api/v2/workflow_job_templates/';
|
this.baseUrl = '/api/v2/workflow_job_templates/';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readNodes(id, params) {
|
||||||
|
return this.http.get(`${this.baseUrl}${id}/workflow_nodes/`, { params });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WorkflowJobTemplates;
|
export default WorkflowJobTemplates;
|
||||||
|
24
awx/ui_next/src/components/AppendBody/AppendBody.jsx
Normal file
24
awx/ui_next/src/components/AppendBody/AppendBody.jsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
class AppendBody extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.el = document.createElement('div');
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
document.body.appendChild(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.body.removeChild(this.el);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props;
|
||||||
|
return ReactDOM.createPortal(children, this.el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppendBody;
|
1
awx/ui_next/src/components/AppendBody/index.js
Normal file
1
awx/ui_next/src/components/AppendBody/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './AppendBody';
|
11
awx/ui_next/src/components/FullPage/FullPage.jsx
Normal file
11
awx/ui_next/src/components/FullPage/FullPage.jsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export default styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: white;
|
||||||
|
z-index: 300;
|
||||||
|
`;
|
1
awx/ui_next/src/components/FullPage/index.js
Normal file
1
awx/ui_next/src/components/FullPage/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './FullPage';
|
@ -350,7 +350,7 @@ class JobTemplateDetail extends Component {
|
|||||||
{summary_fields.user_capabilities.edit && (
|
{summary_fields.user_capabilities.edit && (
|
||||||
<Button
|
<Button
|
||||||
component={Link}
|
component={Link}
|
||||||
to={`/templates/${match.params.templateType}/${match.params.id}/edit`}
|
to={`/templates/job_template/${match.params.id}/edit`}
|
||||||
aria-label={i18n._(t`Edit`)}
|
aria-label={i18n._(t`Edit`)}
|
||||||
>
|
>
|
||||||
{i18n._(t`Edit`)}
|
{i18n._(t`Edit`)}
|
||||||
|
@ -4,7 +4,11 @@ import { withI18n } from '@lingui/react';
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { Card, PageSection } from '@patternfly/react-core';
|
import { Card, PageSection } from '@patternfly/react-core';
|
||||||
|
|
||||||
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api';
|
import {
|
||||||
|
JobTemplatesAPI,
|
||||||
|
UnifiedJobTemplatesAPI,
|
||||||
|
WorkflowJobTemplatesAPI,
|
||||||
|
} from '@api';
|
||||||
import AlertModal from '@components/AlertModal';
|
import AlertModal from '@components/AlertModal';
|
||||||
import DatalistToolbar from '@components/DataListToolbar';
|
import DatalistToolbar from '@components/DataListToolbar';
|
||||||
import ErrorDetail from '@components/ErrorDetail';
|
import ErrorDetail from '@components/ErrorDetail';
|
||||||
@ -106,19 +110,34 @@ class TemplatesList extends Component {
|
|||||||
|
|
||||||
async loadTemplates() {
|
async loadTemplates() {
|
||||||
const { location } = this.props;
|
const { location } = this.props;
|
||||||
const { actions: cachedActions } = this.state;
|
const {
|
||||||
|
jtActions: cachedJTActions,
|
||||||
|
wfjtActions: cachedWFJTActions,
|
||||||
|
} = this.state;
|
||||||
const params = parseQueryString(QS_CONFIG, location.search);
|
const params = parseQueryString(QS_CONFIG, location.search);
|
||||||
|
|
||||||
let optionsPromise;
|
let jtOptionsPromise;
|
||||||
if (cachedActions) {
|
if (cachedJTActions) {
|
||||||
optionsPromise = Promise.resolve({ data: { actions: cachedActions } });
|
jtOptionsPromise = Promise.resolve({
|
||||||
|
data: { actions: cachedJTActions },
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
optionsPromise = JobTemplatesAPI.readOptions();
|
jtOptionsPromise = JobTemplatesAPI.readOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
let wfjtOptionsPromise;
|
||||||
|
if (cachedWFJTActions) {
|
||||||
|
wfjtOptionsPromise = Promise.resolve({
|
||||||
|
data: { actions: cachedWFJTActions },
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
wfjtOptionsPromise = WorkflowJobTemplatesAPI.readOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = Promise.all([
|
const promises = Promise.all([
|
||||||
JobTemplatesAPI.read(params),
|
UnifiedJobTemplatesAPI.read(params),
|
||||||
optionsPromise,
|
jtOptionsPromise,
|
||||||
|
wfjtOptionsPromise,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.setState({ contentError: null, hasContentLoading: true });
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
@ -129,12 +148,16 @@ class TemplatesList extends Component {
|
|||||||
data: { count, results },
|
data: { count, results },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: { actions },
|
data: { actions: jtActions },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: { actions: wfjtActions },
|
||||||
},
|
},
|
||||||
] = await promises;
|
] = await promises;
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
actions,
|
jtActions,
|
||||||
|
wfjtActions,
|
||||||
itemCount: count,
|
itemCount: count,
|
||||||
templates: results,
|
templates: results,
|
||||||
selected: [],
|
selected: [],
|
||||||
@ -154,27 +177,31 @@ class TemplatesList extends Component {
|
|||||||
templates,
|
templates,
|
||||||
itemCount,
|
itemCount,
|
||||||
selected,
|
selected,
|
||||||
actions,
|
jtActions,
|
||||||
|
wfjtActions,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { match, i18n } = this.props;
|
const { match, i18n } = this.props;
|
||||||
const canAdd =
|
const canAddJT =
|
||||||
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
|
jtActions && Object.prototype.hasOwnProperty.call(jtActions, 'POST');
|
||||||
|
const canAddWFJT =
|
||||||
|
wfjtActions && Object.prototype.hasOwnProperty.call(wfjtActions, 'POST');
|
||||||
|
const addButtonOptions = [];
|
||||||
|
if (canAddJT) {
|
||||||
|
addButtonOptions.push({
|
||||||
|
label: i18n._(t`Template`),
|
||||||
|
url: `${match.url}/job_template/add/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (canAddWFJT) {
|
||||||
|
addButtonOptions.push({
|
||||||
|
label: i18n._(t`Workflow Template`),
|
||||||
|
url: `${match.url}/workflow_job_template/add/`,
|
||||||
|
});
|
||||||
|
}
|
||||||
const isAllSelected =
|
const isAllSelected =
|
||||||
selected.length === templates.length && selected.length > 0;
|
selected.length === templates.length && selected.length > 0;
|
||||||
const addButton = (
|
const addButton = (
|
||||||
<AddDropDownButton
|
<AddDropDownButton key="add" dropdownItems={addButtonOptions} />
|
||||||
key="add"
|
|
||||||
dropdownItems={[
|
|
||||||
{
|
|
||||||
label: i18n._(t`Template`),
|
|
||||||
url: `${match.url}/template/add/`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: i18n._(t`Workflow Template`),
|
|
||||||
url: `${match.url}/_workflow/add/`,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
@ -221,7 +248,7 @@ class TemplatesList extends Component {
|
|||||||
itemsToDelete={selected}
|
itemsToDelete={selected}
|
||||||
pluralizedItemName="Templates"
|
pluralizedItemName="Templates"
|
||||||
/>,
|
/>,
|
||||||
canAdd && addButton,
|
(canAddJT || canAddWFJT) && addButton,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -235,7 +262,7 @@ class TemplatesList extends Component {
|
|||||||
isSelected={selected.some(row => row.id === template.id)}
|
isSelected={selected.some(row => row.id === template.id)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
emptyStateControls={canAdd && addButton}
|
emptyStateControls={(canAddJT || canAddWFJT) && addButton}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<AlertModal
|
<AlertModal
|
||||||
|
@ -7,6 +7,7 @@ import { Config } from '@contexts/Config';
|
|||||||
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
import Breadcrumbs from '@components/Breadcrumbs/Breadcrumbs';
|
||||||
import { TemplateList } from './TemplateList';
|
import { TemplateList } from './TemplateList';
|
||||||
import Template from './Template';
|
import Template from './Template';
|
||||||
|
import WorkflowJobTemplate from './WorkflowJobTemplate';
|
||||||
import JobTemplateAdd from './JobTemplateAdd';
|
import JobTemplateAdd from './JobTemplateAdd';
|
||||||
|
|
||||||
class Templates extends Component {
|
class Templates extends Component {
|
||||||
@ -53,11 +54,11 @@ class Templates extends Component {
|
|||||||
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
<Breadcrumbs breadcrumbConfig={breadcrumbConfig} />
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route
|
<Route
|
||||||
path={`${match.path}/:templateType/add`}
|
path={`${match.path}/job_template/add`}
|
||||||
render={() => <JobTemplateAdd />}
|
render={() => <JobTemplateAdd />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={`${match.path}/:templateType/:id`}
|
path={`${match.path}/job_template/:id`}
|
||||||
render={({ match: newRouteMatch }) => (
|
render={({ match: newRouteMatch }) => (
|
||||||
<Config>
|
<Config>
|
||||||
{({ me }) => (
|
{({ me }) => (
|
||||||
@ -72,6 +73,22 @@ class Templates extends Component {
|
|||||||
</Config>
|
</Config>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path={`${match.path}/workflow_job_template/:id`}
|
||||||
|
render={({ match: newRouteMatch }) => (
|
||||||
|
<Config>
|
||||||
|
{({ me }) => (
|
||||||
|
<WorkflowJobTemplate
|
||||||
|
history={history}
|
||||||
|
location={location}
|
||||||
|
setBreadcrumb={this.setBreadCrumbConfig}
|
||||||
|
me={me || {}}
|
||||||
|
match={newRouteMatch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Config>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Route path={`${match.path}`} render={() => <TemplateList />} />
|
<Route path={`${match.path}`} render={() => <TemplateList />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
156
awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
Normal file
156
awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { Card, CardHeader, PageSection } from '@patternfly/react-core';
|
||||||
|
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
|
||||||
|
import AppendBody from '@components/AppendBody';
|
||||||
|
import CardCloseButton from '@components/CardCloseButton';
|
||||||
|
import ContentError from '@components/ContentError';
|
||||||
|
import FullPage from '@components/FullPage';
|
||||||
|
import RoutedTabs from '@components/RoutedTabs';
|
||||||
|
import { WorkflowJobTemplatesAPI } from '@api';
|
||||||
|
import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail';
|
||||||
|
import { Visualizer } from './WorkflowJobTemplateVisualizer';
|
||||||
|
|
||||||
|
class WorkflowJobTemplate extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
contentError: null,
|
||||||
|
hasContentLoading: true,
|
||||||
|
template: null,
|
||||||
|
};
|
||||||
|
this.loadTemplate = this.loadTemplate.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
await this.loadTemplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidUpdate(prevProps) {
|
||||||
|
const { location } = this.props;
|
||||||
|
if (location !== prevProps.location) {
|
||||||
|
await this.loadTemplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadTemplate() {
|
||||||
|
const { setBreadcrumb, match } = this.props;
|
||||||
|
const { id } = match.params;
|
||||||
|
|
||||||
|
this.setState({ contentError: null, hasContentLoading: true });
|
||||||
|
try {
|
||||||
|
const { data } = await WorkflowJobTemplatesAPI.readDetail(id);
|
||||||
|
setBreadcrumb(data);
|
||||||
|
this.setState({ template: data });
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ contentError: err });
|
||||||
|
} finally {
|
||||||
|
this.setState({ hasContentLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { history, i18n, location, match } = this.props;
|
||||||
|
const { contentError, hasContentLoading, template } = this.state;
|
||||||
|
|
||||||
|
const tabsArray = [
|
||||||
|
{ name: i18n._(t`Details`), link: `${match.url}/details` },
|
||||||
|
{ name: i18n._(t`Visualizer`), link: `${match.url}/visualizer` },
|
||||||
|
];
|
||||||
|
|
||||||
|
tabsArray.forEach((tab, n) => {
|
||||||
|
tab.id = n;
|
||||||
|
});
|
||||||
|
|
||||||
|
let cardHeader = hasContentLoading ? null : (
|
||||||
|
<CardHeader style={{ padding: 0 }}>
|
||||||
|
<RoutedTabs history={history} tabsArray={tabsArray} />
|
||||||
|
<CardCloseButton linkTo="/templates" />
|
||||||
|
</CardHeader>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (location.pathname.endsWith('edit')) {
|
||||||
|
cardHeader = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasContentLoading && contentError) {
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card className="awx-c-card">
|
||||||
|
<ContentError error={contentError}>
|
||||||
|
{contentError.response.status === 404 && (
|
||||||
|
<span>
|
||||||
|
{i18n._(`Template not found.`)}{' '}
|
||||||
|
<Link to="/templates">{i18n._(`View all Templates.`)}</Link>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</ContentError>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageSection>
|
||||||
|
<Card className="awx-c-card">
|
||||||
|
{cardHeader}
|
||||||
|
<Switch>
|
||||||
|
<Redirect
|
||||||
|
from="/templates/workflow_job_template/:id"
|
||||||
|
to="/templates/workflow_job_template/:id/details"
|
||||||
|
exact
|
||||||
|
/>
|
||||||
|
{template && (
|
||||||
|
<Route
|
||||||
|
key="wfjt-details"
|
||||||
|
path="/templates/workflow_job_template/:id/details"
|
||||||
|
render={() => (
|
||||||
|
<WorkflowJobTemplateDetail
|
||||||
|
match={match}
|
||||||
|
hasTemplateLoading={hasContentLoading}
|
||||||
|
template={template}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{template && (
|
||||||
|
<Route
|
||||||
|
key="wfjt-visualizer"
|
||||||
|
path="/templates/workflow_job_template/:id/visualizer"
|
||||||
|
render={() => (
|
||||||
|
<AppendBody>
|
||||||
|
<FullPage>
|
||||||
|
<Visualizer template={template} />
|
||||||
|
</FullPage>
|
||||||
|
</AppendBody>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Route
|
||||||
|
key="not-found"
|
||||||
|
path="*"
|
||||||
|
render={() =>
|
||||||
|
!hasContentLoading && (
|
||||||
|
<ContentError isNotFound>
|
||||||
|
{match.params.id && (
|
||||||
|
<Link
|
||||||
|
to={`/templates/workflow_job_template/${match.params.id}/details`}
|
||||||
|
>
|
||||||
|
{i18n._(`View Template Details`)}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</ContentError>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</Card>
|
||||||
|
</PageSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { WorkflowJobTemplate as _WorkflowJobTemplate };
|
||||||
|
export default withI18n()(withRouter(WorkflowJobTemplate));
|
@ -0,0 +1,18 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { CardBody } from '@patternfly/react-core';
|
||||||
|
|
||||||
|
import { DetailList } from '@components/DetailList';
|
||||||
|
|
||||||
|
class WorkflowJobTemplateDetail extends Component {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<CardBody css="padding-top: 20px;">
|
||||||
|
<DetailList gutter="sm" />
|
||||||
|
</CardBody>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { WorkflowJobTemplateDetail as _WorkflowJobTemplateDetail };
|
||||||
|
export default withI18n()(withRouter(WorkflowJobTemplateDetail));
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './WorkflowJobTemplateDetail';
|
@ -0,0 +1,767 @@
|
|||||||
|
import React, { Fragment, useEffect, useRef, useState } from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
import * as dagre from 'dagre';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import WorkflowHelp from './WorkflowHelp';
|
||||||
|
import WorkflowHelpDetails from './WorkflowHelpDetails';
|
||||||
|
|
||||||
|
const SVG = styled.svg`
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
|
||||||
|
.WorkflowChart-tooltip {
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action {
|
||||||
|
height: 25px;
|
||||||
|
width: 25px;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action:hover {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action:not(:last-of-type) {
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action--add:hover {
|
||||||
|
background-color: #58b957;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action--edit:hover,
|
||||||
|
.WorkflowChart-action--link:hover,
|
||||||
|
.WorkflowChart-action--details:hover {
|
||||||
|
background-color: #0279bc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-action--delete:hover {
|
||||||
|
background-color: #d9534f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-tooltipArrows {
|
||||||
|
width: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-tooltipArrows--outer {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-right: 10px solid #c4c4c4;
|
||||||
|
border-top: 10px solid transparent;
|
||||||
|
border-bottom: 10px solid transparent;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-tooltipArrows--inner {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
left: 6px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-right: 10px solid white;
|
||||||
|
border-top: 10px solid transparent;
|
||||||
|
border-bottom: 10px solid transparent;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-tooltipActions {
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid #c4c4c4;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-tooltipContents {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WorkflowChart-nameText {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0px 10px;
|
||||||
|
text-align: center;
|
||||||
|
p {
|
||||||
|
margin-top: 20px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function Graph({ links, nodes, readOnly, i18n }) {
|
||||||
|
const [helpText, setHelpText] = useState();
|
||||||
|
const svgRef = useRef(null);
|
||||||
|
const gRef = useRef(null);
|
||||||
|
const nodeW = 180;
|
||||||
|
const nodeH = 60;
|
||||||
|
// This needs to be dynamic bc the text can be different lengths in different languages
|
||||||
|
const rootW = 72;
|
||||||
|
const rootH = 40;
|
||||||
|
let currentScale = 1;
|
||||||
|
|
||||||
|
// Dagre is going to shift the root node around as nodes are added/removed
|
||||||
|
// This function ensures that the user doesn't experience that
|
||||||
|
const normalizeY = (nodePositions, y) => {
|
||||||
|
return y - nodePositions[1].y;
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is the zoom function called by using the mousewheel/click and drag
|
||||||
|
const zoom = () => {
|
||||||
|
const translation = [d3.event.transform.x, d3.event.transform.y];
|
||||||
|
d3.select(gRef.current).attr(
|
||||||
|
'transform',
|
||||||
|
`translate(${translation}) scale(${d3.event.transform.k})`
|
||||||
|
);
|
||||||
|
currentScale = d3.event.transform.k;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zoomRef = d3
|
||||||
|
.zoom()
|
||||||
|
.scaleExtent([0.1, 2])
|
||||||
|
.on('zoom', zoom);
|
||||||
|
|
||||||
|
// Initialize the zoom
|
||||||
|
useEffect(() => {
|
||||||
|
d3.select(svgRef.current).call(zoomRef);
|
||||||
|
}, [zoomRef]);
|
||||||
|
|
||||||
|
// Draw the graph - this will get triggered whenever
|
||||||
|
// nodes or links changes
|
||||||
|
useEffect(() => {
|
||||||
|
const nodePositions = {};
|
||||||
|
const line = d3
|
||||||
|
.line()
|
||||||
|
.x(d => {
|
||||||
|
return d.x;
|
||||||
|
})
|
||||||
|
.y(d => {
|
||||||
|
return d.y;
|
||||||
|
});
|
||||||
|
const getLinkOverlayPoints = d => {
|
||||||
|
const sourceX =
|
||||||
|
nodePositions[d.source.id].x + nodePositions[d.source.id].width + 1;
|
||||||
|
let sourceY =
|
||||||
|
normalizeY(nodePositions, nodePositions[d.source.id].y) +
|
||||||
|
nodePositions[d.source.id].height / 2;
|
||||||
|
const targetX = nodePositions[d.target.id].x - 1;
|
||||||
|
const targetY =
|
||||||
|
normalizeY(nodePositions, nodePositions[d.target.id].y) +
|
||||||
|
nodePositions[d.target.id].height / 2;
|
||||||
|
|
||||||
|
// There's something off with the math on the root node...
|
||||||
|
if (d.source.id === 1) {
|
||||||
|
sourceY += 10;
|
||||||
|
}
|
||||||
|
const slope = (targetY - sourceY) / (targetX - sourceX);
|
||||||
|
const yIntercept = targetY - slope * targetX;
|
||||||
|
const orthogonalDistance = 8;
|
||||||
|
|
||||||
|
const pt1 = [
|
||||||
|
targetX,
|
||||||
|
slope * targetX +
|
||||||
|
yIntercept +
|
||||||
|
orthogonalDistance * Math.sqrt(1 + slope * slope),
|
||||||
|
].join(',');
|
||||||
|
const pt2 = [
|
||||||
|
sourceX,
|
||||||
|
slope * sourceX +
|
||||||
|
yIntercept +
|
||||||
|
orthogonalDistance * Math.sqrt(1 + slope * slope),
|
||||||
|
].join(',');
|
||||||
|
const pt3 = [
|
||||||
|
sourceX,
|
||||||
|
slope * sourceX +
|
||||||
|
yIntercept -
|
||||||
|
orthogonalDistance * Math.sqrt(1 + slope * slope),
|
||||||
|
].join(',');
|
||||||
|
const pt4 = [
|
||||||
|
targetX,
|
||||||
|
slope * targetX +
|
||||||
|
yIntercept -
|
||||||
|
orthogonalDistance * Math.sqrt(1 + slope * slope),
|
||||||
|
].join(',');
|
||||||
|
|
||||||
|
return [pt1, pt2, pt3, pt4].join(' ');
|
||||||
|
};
|
||||||
|
const lineData = d => {
|
||||||
|
const sourceX =
|
||||||
|
nodePositions[d.source.id].x + nodePositions[d.source.id].width + 1;
|
||||||
|
let sourceY =
|
||||||
|
normalizeY(nodePositions, nodePositions[d.source.id].y) +
|
||||||
|
nodePositions[d.source.id].height / 2;
|
||||||
|
const targetX = nodePositions[d.target.id].x - 1;
|
||||||
|
const targetY =
|
||||||
|
normalizeY(nodePositions, nodePositions[d.target.id].y) +
|
||||||
|
nodePositions[d.target.id].height / 2;
|
||||||
|
|
||||||
|
// There's something off with the math on the root node...
|
||||||
|
if (d.source.id === 1) {
|
||||||
|
sourceY += 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
return line([
|
||||||
|
{
|
||||||
|
x: sourceX,
|
||||||
|
y: sourceY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: targetX,
|
||||||
|
y: targetY,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
const svgGroup = d3.select(gRef.current);
|
||||||
|
|
||||||
|
const g = new dagre.graphlib.Graph();
|
||||||
|
|
||||||
|
g.setGraph({ rankdir: 'LR', nodesep: 30, ranksep: 120 });
|
||||||
|
|
||||||
|
// This is needed for Dagre
|
||||||
|
g.setDefaultEdgeLabel(() => {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
if (node.id === 1) {
|
||||||
|
g.setNode(node.id, { label: '', width: rootW, height: rootH });
|
||||||
|
} else {
|
||||||
|
g.setNode(node.id, { label: '', width: nodeW, height: nodeH });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
links.forEach(link => {
|
||||||
|
g.setEdge(link.source.id, link.target.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
dagre.layout(g);
|
||||||
|
|
||||||
|
g.nodes().forEach(node => {
|
||||||
|
nodePositions[node] = g.node(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
const linkRefs = svgGroup
|
||||||
|
.selectAll('.WorkflowChart-link')
|
||||||
|
.data(links, d => {
|
||||||
|
return `${d.source.id}-${d.target.id}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any stale links
|
||||||
|
linkRefs.exit().remove();
|
||||||
|
|
||||||
|
// Add any new links
|
||||||
|
const linkEnter = linkRefs
|
||||||
|
.enter()
|
||||||
|
.append('g')
|
||||||
|
.attr('class', 'WorkflowChart-link')
|
||||||
|
.attr('id', d => `link-${d.source.id}-${d.target.id}`)
|
||||||
|
.attr('stroke-width', '2px');
|
||||||
|
|
||||||
|
linkEnter
|
||||||
|
.append('polygon', 'g')
|
||||||
|
.attr('class', 'WorkflowChart-linkOverlay')
|
||||||
|
.attr('fill', '#E1E1E1')
|
||||||
|
.style('opacity', '0')
|
||||||
|
.attr('id', d => `link-${d.source.id}-${d.target.id}-overlay`)
|
||||||
|
.attr('points', d => getLinkOverlayPoints(d))
|
||||||
|
.on('mouseenter', d => {
|
||||||
|
setHelpText(d);
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add entering links in the parent’s old position.
|
||||||
|
linkEnter
|
||||||
|
.insert('path', 'g')
|
||||||
|
.attr('class', 'WorkflowChart-linkPath')
|
||||||
|
.attr('d', d => lineData(d))
|
||||||
|
.attr('stroke', d => {
|
||||||
|
if (d.edgeType) {
|
||||||
|
if (d.edgeType === 'failure') {
|
||||||
|
return '#d9534f';
|
||||||
|
}
|
||||||
|
if (d.edgeType === 'success') {
|
||||||
|
return '#5cb85c';
|
||||||
|
}
|
||||||
|
if (d.edgeType === 'always') {
|
||||||
|
return '#337ab7';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '#D7D7D7';
|
||||||
|
});
|
||||||
|
|
||||||
|
linkEnter
|
||||||
|
.append('polygon', 'g')
|
||||||
|
.style('opacity', '0')
|
||||||
|
.attr('points', d => getLinkOverlayPoints(d))
|
||||||
|
.on('mouseenter', d => {
|
||||||
|
setHelpText(d);
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
linkEnter
|
||||||
|
.on('mouseenter', d => {
|
||||||
|
d3.select(`#link-${d.source.id}-${d.target.id}`).raise();
|
||||||
|
d3.select(`#link-${d.source.id}-${d.target.id}-overlay`).style(
|
||||||
|
'opacity',
|
||||||
|
'1'
|
||||||
|
);
|
||||||
|
if (!readOnly) {
|
||||||
|
d3
|
||||||
|
.select(`#link-${d.source.id}-${d.target.id}`)
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('transform', () => {
|
||||||
|
const normalizedSourceY = normalizeY(
|
||||||
|
nodePositions,
|
||||||
|
nodePositions[d.source.id].y
|
||||||
|
);
|
||||||
|
const halfSourceHeight = nodePositions[d.source.id].height / 2;
|
||||||
|
const normalizedTargetY = normalizeY(
|
||||||
|
nodePositions,
|
||||||
|
nodePositions[d.target.id].y
|
||||||
|
);
|
||||||
|
const halfTargetHeight = nodePositions[d.target.id].height / 2;
|
||||||
|
|
||||||
|
let yPos =
|
||||||
|
(normalizedSourceY +
|
||||||
|
halfSourceHeight +
|
||||||
|
normalizedTargetY +
|
||||||
|
halfTargetHeight) /
|
||||||
|
2;
|
||||||
|
|
||||||
|
if (d.source.id === 1) {
|
||||||
|
yPos += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
yPos -= 34;
|
||||||
|
|
||||||
|
return `translate(${(nodePositions[d.source.id].x +
|
||||||
|
nodePositions[d.source.id].width +
|
||||||
|
nodePositions[d.target.id].x) /
|
||||||
|
2}, ${yPos})`;
|
||||||
|
})
|
||||||
|
.attr('width', 52)
|
||||||
|
.attr('height', 68)
|
||||||
|
.attr('class', 'WorkflowChart-tooltip').html(`
|
||||||
|
<div class="WorkflowChart-tooltipContents">
|
||||||
|
<div class="WorkflowChart-tooltipArrows">
|
||||||
|
<div class="WorkflowChart-tooltipArrows--outer"></div>
|
||||||
|
<div class="WorkflowChart-tooltipArrows--inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="WorkflowChart-tooltipActions">
|
||||||
|
<div id="node-add-between" class="WorkflowChart-action WorkflowChart-action--add">
|
||||||
|
<i class="pf-icon pf-icon-add-circle-o"></i>
|
||||||
|
</div>
|
||||||
|
<div id="link-edit" class="WorkflowChart-action WorkflowChart-action--edit">
|
||||||
|
<i class="pf-icon pf-icon-edit"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
|
||||||
|
d3.select('#node-add-between')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Add a new node between these two nodes`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
d3.select('#link-edit')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Edit this link`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('mouseleave', d => {
|
||||||
|
d3.select(`#link-${d.source.id}-${d.target.id}`).lower();
|
||||||
|
d3.select(`#link-${d.source.id}-${d.target.id}-overlay`).style(
|
||||||
|
'opacity',
|
||||||
|
'0'
|
||||||
|
);
|
||||||
|
if (!readOnly) {
|
||||||
|
linkEnter.select('.WorkflowChart-tooltip').remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeRefs = svgGroup
|
||||||
|
.selectAll('.WorkflowChart-node')
|
||||||
|
.data(nodes, d => {
|
||||||
|
return d.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Remove any stale nodes
|
||||||
|
nodeRefs.exit().remove();
|
||||||
|
|
||||||
|
// Add new nodes
|
||||||
|
const nodeEnter = nodeRefs
|
||||||
|
.enter()
|
||||||
|
.append('g')
|
||||||
|
.attr('class', 'WorkflowChart-node')
|
||||||
|
.attr('id', d => `node-${d.id}`)
|
||||||
|
.attr(
|
||||||
|
'transform',
|
||||||
|
d =>
|
||||||
|
`translate(${nodePositions[d.id].x},${normalizeY(
|
||||||
|
nodePositions,
|
||||||
|
nodePositions[d.id].y
|
||||||
|
)})`
|
||||||
|
);
|
||||||
|
|
||||||
|
nodeEnter.each((node, i, nodesArray) => {
|
||||||
|
const nodeRef = d3.select(nodesArray[i]);
|
||||||
|
if (node.id === 1) {
|
||||||
|
nodeRef
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', rootW)
|
||||||
|
.attr('height', rootH)
|
||||||
|
.attr('y', 10)
|
||||||
|
.attr('rx', 2)
|
||||||
|
.attr('ry', 2)
|
||||||
|
.attr('fill', '#0279BC')
|
||||||
|
.attr('class', 'WorkflowChart-rootNode');
|
||||||
|
nodeRef
|
||||||
|
.append('text')
|
||||||
|
.attr('x', 13)
|
||||||
|
.attr('y', 30)
|
||||||
|
.attr('dy', '.35em')
|
||||||
|
.attr('fill', 'white')
|
||||||
|
.attr('class', 'WorkflowChart-startText')
|
||||||
|
.text('START');
|
||||||
|
|
||||||
|
if (!readOnly) {
|
||||||
|
nodeRef
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
nodeRef
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('x', rootW)
|
||||||
|
.attr('y', 11)
|
||||||
|
.attr('width', 52)
|
||||||
|
.attr('height', 37)
|
||||||
|
.attr('class', 'WorkflowChart-tooltip').html(`
|
||||||
|
<div class="WorkflowChart-tooltipContents">
|
||||||
|
<div class="WorkflowChart-tooltipArrows">
|
||||||
|
<div class="WorkflowChart-tooltipArrows--outer"></div>
|
||||||
|
<div class="WorkflowChart-tooltipArrows--inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="WorkflowChart-tooltipActions">
|
||||||
|
<div id="node-add" class="WorkflowChart-action WorkflowChart-action--add">
|
||||||
|
<i class="pf-icon pf-icon-add-circle-o"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
d3.select('#node-add')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Add a new node`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
nodeRef.select('.WorkflowChart-tooltip').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nodeRef
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', nodeW)
|
||||||
|
.attr('height', nodeH)
|
||||||
|
.attr('rx', 2)
|
||||||
|
.attr('ry', 2)
|
||||||
|
.attr('stroke', '#93969A')
|
||||||
|
.attr('stroke-width', '2px')
|
||||||
|
.attr('fill', '#FFFFFF')
|
||||||
|
.attr('class', d => {
|
||||||
|
let classString = 'WorkflowChart-rect';
|
||||||
|
classString += !(d.unifiedJobTemplate && d.unifiedJobTemplate.name)
|
||||||
|
? ' WorkflowChart-dashedNode'
|
||||||
|
: '';
|
||||||
|
return classString;
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeRef
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('width', nodeW)
|
||||||
|
.attr('height', nodeH)
|
||||||
|
.attr('class', 'WorkflowChart-nameText')
|
||||||
|
.html(
|
||||||
|
d =>
|
||||||
|
`<p>${
|
||||||
|
d.unifiedJobTemplate
|
||||||
|
? d.unifiedJobTemplate.name
|
||||||
|
: i18n._(t`DELETED`)
|
||||||
|
}</p>`
|
||||||
|
);
|
||||||
|
|
||||||
|
nodeRef
|
||||||
|
.append('rect')
|
||||||
|
.attr('width', nodeW)
|
||||||
|
.attr('height', nodeH)
|
||||||
|
.style('opacity', '0')
|
||||||
|
.on('mouseenter', d => {
|
||||||
|
setHelpText(d);
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeRef
|
||||||
|
.append('circle')
|
||||||
|
.attr('cy', nodeH)
|
||||||
|
.attr('r', 10)
|
||||||
|
.attr('class', 'WorkflowChart-nodeTypeCircle')
|
||||||
|
.attr('fill', '#393F43')
|
||||||
|
.style('display', d => (d.unifiedJobTemplate ? null : 'none'));
|
||||||
|
|
||||||
|
nodeRef
|
||||||
|
.append('text')
|
||||||
|
.attr('y', nodeH)
|
||||||
|
.attr('dy', '.35em')
|
||||||
|
.attr('text-anchor', 'middle')
|
||||||
|
.attr('fill', '#FFFFFF')
|
||||||
|
.attr('class', 'WorkflowChart-nodeTypeLetter')
|
||||||
|
.text(d => {
|
||||||
|
let nodeTypeLetter;
|
||||||
|
if (d.unifiedJobTemplate && d.unifiedJobTemplate.type) {
|
||||||
|
switch (d.unifiedJobTemplate.type) {
|
||||||
|
case 'job_template':
|
||||||
|
nodeTypeLetter = 'JT';
|
||||||
|
break;
|
||||||
|
case 'project':
|
||||||
|
nodeTypeLetter = 'P';
|
||||||
|
break;
|
||||||
|
case 'inventory_source':
|
||||||
|
nodeTypeLetter = 'I';
|
||||||
|
break;
|
||||||
|
case 'workflow_job_template':
|
||||||
|
nodeTypeLetter = 'W';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nodeTypeLetter = '';
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
d.unifiedJobTemplate &&
|
||||||
|
d.unifiedJobTemplate.unified_job_type
|
||||||
|
) {
|
||||||
|
switch (d.unifiedJobTemplate.unified_job_type) {
|
||||||
|
case 'job':
|
||||||
|
nodeTypeLetter = 'JT';
|
||||||
|
break;
|
||||||
|
case 'project_update':
|
||||||
|
nodeTypeLetter = 'P';
|
||||||
|
break;
|
||||||
|
case 'inventory_update':
|
||||||
|
nodeTypeLetter = 'I';
|
||||||
|
break;
|
||||||
|
case 'workflow_job':
|
||||||
|
nodeTypeLetter = 'W';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nodeTypeLetter = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodeTypeLetter;
|
||||||
|
})
|
||||||
|
.style('font-size', '10px')
|
||||||
|
.style('display', d => {
|
||||||
|
return d.unifiedJobTemplate &&
|
||||||
|
d.unifiedJobTemplate.type !== 'workflow_approval_template' &&
|
||||||
|
d.unifiedJobTemplate.unified_job_type !== 'workflow_approval'
|
||||||
|
? null
|
||||||
|
: 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeRef
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
nodeRef.select('.WorkflowChart-rect').attr('stroke', '#007ABC');
|
||||||
|
nodeRef.raise();
|
||||||
|
if (readOnly) {
|
||||||
|
nodeRef
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('x', nodeW)
|
||||||
|
.attr('y', 11)
|
||||||
|
.attr('width', 52)
|
||||||
|
.attr('height', 37)
|
||||||
|
.attr('class', 'WorkflowChart-tooltip').html(`
|
||||||
|
<div class="WorkflowChart-tooltipContents">
|
||||||
|
<div class="WorkflowChart-tooltipArrows">
|
||||||
|
<div class="WorkflowChart-tooltipArrows--outer"></div>
|
||||||
|
<div class="WorkflowChart-tooltipArrows--inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="WorkflowChart-tooltipActions">
|
||||||
|
<div id="node-details" class="WorkflowChart-action WorkflowChart-action--details">
|
||||||
|
<i class="pf-icon pf-icon-info"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
} else {
|
||||||
|
nodeRef
|
||||||
|
.append('foreignObject')
|
||||||
|
.attr('x', nodeW)
|
||||||
|
.attr('y', -49)
|
||||||
|
.attr('width', 52)
|
||||||
|
.attr('height', 157)
|
||||||
|
.attr('class', 'WorkflowChart-tooltip').html(`
|
||||||
|
<div class="WorkflowChart-tooltipContents">
|
||||||
|
<div class="WorkflowChart-tooltipArrows">
|
||||||
|
<div class="WorkflowChart-tooltipArrows--outer"></div>
|
||||||
|
<div class="WorkflowChart-tooltipArrows--inner"></div>
|
||||||
|
</div>
|
||||||
|
<div class="WorkflowChart-tooltipActions">
|
||||||
|
<div id="node-add" class="WorkflowChart-action WorkflowChart-action--add">
|
||||||
|
<i class="pf-icon pf-icon-add-circle-o"></i>
|
||||||
|
</div>
|
||||||
|
<div id="node-details" class="WorkflowChart-action WorkflowChart-action--details">
|
||||||
|
<i class="pf-icon pf-icon-info"></i>
|
||||||
|
</div>
|
||||||
|
<div id="node-edit" class="WorkflowChart-action WorkflowChart-action--edit">
|
||||||
|
<i class="pf-icon pf-icon-edit"></i>
|
||||||
|
</div>
|
||||||
|
<div id="node-link" class="WorkflowChart-action WorkflowChart-action--link">
|
||||||
|
<i class="pf-icon pf-icon-automation"></i>
|
||||||
|
</div>
|
||||||
|
<div id="node-delete" class="WorkflowChart-action WorkflowChart-action--delete">
|
||||||
|
<i class="pf-icon pf-icon-remove2"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`);
|
||||||
|
d3.select('#node-add')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Add a new node`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
d3.select('#node-edit')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Edit this node`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
d3.select('#node-link')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Link to an available node`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
d3.select('#node-delete')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`Remove this node`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
d3.select('#node-details')
|
||||||
|
.on('mouseenter', () => {
|
||||||
|
setHelpText(i18n._(t`View node details`));
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
setHelpText(null);
|
||||||
|
})
|
||||||
|
.on('click', () => {});
|
||||||
|
})
|
||||||
|
.on('mouseleave', () => {
|
||||||
|
nodeRef.select('.WorkflowChart-rect').attr('stroke', '#93969A');
|
||||||
|
nodeRef.select('.WorkflowChart-tooltip').remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This will make sure that all the link elements appear before the nodes in the dom
|
||||||
|
svgGroup.selectAll('.WorkflowChart-node').order();
|
||||||
|
}, [links, nodes, readOnly, i18n]);
|
||||||
|
|
||||||
|
// Attempt to zoom the graph to fit the available screen space
|
||||||
|
useEffect(() => {
|
||||||
|
// TODO: try to figure out this start node width thing...
|
||||||
|
const startNodeWidth = 60;
|
||||||
|
const gDimensions = d3
|
||||||
|
.select(gRef.current)
|
||||||
|
.node()
|
||||||
|
.getBoundingClientRect();
|
||||||
|
|
||||||
|
const pageHeight = window.innerHeight - 50;
|
||||||
|
const pageWidth = window.innerWidth;
|
||||||
|
|
||||||
|
// For some reason the start node isn't accounted for in the width... add it
|
||||||
|
gDimensions.width += startNodeWidth * currentScale;
|
||||||
|
|
||||||
|
const scaleNeededForMaxHeight =
|
||||||
|
pageHeight / (gDimensions.height / currentScale);
|
||||||
|
const scaleNeededForMaxWidth =
|
||||||
|
pageWidth / (gDimensions.width / currentScale);
|
||||||
|
const lowerScale = Math.min(
|
||||||
|
scaleNeededForMaxHeight,
|
||||||
|
scaleNeededForMaxWidth
|
||||||
|
);
|
||||||
|
|
||||||
|
let scaleToFit;
|
||||||
|
if (lowerScale < 0.5 || lowerScale > 2) {
|
||||||
|
scaleToFit = lowerScale;
|
||||||
|
} else {
|
||||||
|
scaleToFit = Math.floor(lowerScale * 1000) / 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
d3.select(svgRef.current).call(
|
||||||
|
zoomRef.transform,
|
||||||
|
d3.zoomIdentity
|
||||||
|
.translate(0, pageHeight / 2 - (nodeH * scaleToFit) / 2)
|
||||||
|
.scale(scaleToFit)
|
||||||
|
);
|
||||||
|
// We only want this to run once (when the component mounts)
|
||||||
|
// but this rule will throw a warning if we don't include
|
||||||
|
// things like height, width, currentScale in the array
|
||||||
|
// of deps. Including them will cause this hook to fire
|
||||||
|
// as those deps change.
|
||||||
|
// Discussion: https://github.com/facebook/create-react-app/issues/6880
|
||||||
|
// and https://github.com/facebook/react/issues/15865 amongst others
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
{helpText && helpText !== '' && (
|
||||||
|
<WorkflowHelp>
|
||||||
|
{typeof helpText === 'string' && <Fragment>{helpText}</Fragment>}
|
||||||
|
{typeof helpText === 'object' && <WorkflowHelpDetails d={helpText} />}
|
||||||
|
</WorkflowHelp>
|
||||||
|
)}
|
||||||
|
<SVG id="workflow-svg" ref={svgRef}>
|
||||||
|
<g id="workflow-g" ref={gRef} />
|
||||||
|
</SVG>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(Graph);
|
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Button as PFButton } from '@patternfly/react-core';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Button = styled(PFButton)`
|
||||||
|
&& {
|
||||||
|
background-color: #5cb85c;
|
||||||
|
padding: 5px 8px;
|
||||||
|
--pf-global--FontSize--md: 14px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StartPanel = styled.div`
|
||||||
|
padding: 60px 80px;
|
||||||
|
border: 1px solid #c7c7c7;
|
||||||
|
background-color: white;
|
||||||
|
color: var(--pf-global--Color--200);
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StartPanelWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #f6f6f6;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function StartScreen({ i18n }) {
|
||||||
|
return (
|
||||||
|
<div css="flex: 1">
|
||||||
|
<StartPanelWrapper>
|
||||||
|
<StartPanel>
|
||||||
|
<p>{i18n._(t`Please click the Start button to begin.`)}</p>
|
||||||
|
<Button variant="primary" aria-label={i18n._(t`Start`)}>
|
||||||
|
{i18n._(t`Start`)}
|
||||||
|
</Button>
|
||||||
|
</StartPanel>
|
||||||
|
</StartPanelWrapper>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(StartScreen);
|
@ -0,0 +1,93 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router-dom';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import { Badge as PFBadge, Button } from '@patternfly/react-core';
|
||||||
|
import {
|
||||||
|
BookIcon,
|
||||||
|
CompassIcon,
|
||||||
|
DownloadIcon,
|
||||||
|
RocketIcon,
|
||||||
|
TimesIcon,
|
||||||
|
TrashAltIcon,
|
||||||
|
WrenchIcon,
|
||||||
|
} from '@patternfly/react-icons';
|
||||||
|
import VerticalSeparator from '@components/VerticalSeparator';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Badge = styled(PFBadge)`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function Toolbar({ history, i18n, template }) {
|
||||||
|
const handleVisualizerCancel = () => {
|
||||||
|
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
borderBottom: '1px solid grey',
|
||||||
|
height: '56px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
padding: '0px 20px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ display: 'flex' }}>
|
||||||
|
<b>{i18n._(t`Workflow Visualizer`)}</b>
|
||||||
|
<VerticalSeparator />
|
||||||
|
<b>{template.name}</b>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flex: '1',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{i18n._(t`Total Nodes`)}</div>
|
||||||
|
<Badge isRead>0</Badge>
|
||||||
|
<VerticalSeparator />
|
||||||
|
<Button variant="plain">
|
||||||
|
<CompassIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain">
|
||||||
|
<WrenchIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain">
|
||||||
|
<DownloadIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain">
|
||||||
|
<BookIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain">
|
||||||
|
<RocketIcon />
|
||||||
|
</Button>
|
||||||
|
<Button variant="plain">
|
||||||
|
<TrashAltIcon />
|
||||||
|
</Button>
|
||||||
|
<VerticalSeparator />
|
||||||
|
<Button variant="primary" onClick={handleVisualizerCancel}>
|
||||||
|
{i18n._(t`Save`)}
|
||||||
|
</Button>
|
||||||
|
<VerticalSeparator />
|
||||||
|
<Button
|
||||||
|
variant="plain"
|
||||||
|
aria-label={i18n._(t`Close`)}
|
||||||
|
onClick={handleVisualizerCancel}
|
||||||
|
>
|
||||||
|
<TimesIcon />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(withRouter(Toolbar));
|
@ -0,0 +1,205 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import ContentError from '@components/ContentError';
|
||||||
|
import ContentLoading from '@components/ContentLoading';
|
||||||
|
import Graph from './Graph';
|
||||||
|
import StartScreen from './StartScreen';
|
||||||
|
import Toolbar from './Toolbar';
|
||||||
|
import { WorkflowJobTemplatesAPI } from '@api';
|
||||||
|
|
||||||
|
const CenteredContent = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const VisualizerLayout = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fetchWorkflowNodes = async (templateId, pageNo = 1, nodes = []) => {
|
||||||
|
try {
|
||||||
|
const { data } = await WorkflowJobTemplatesAPI.readNodes(templateId, {
|
||||||
|
page_size: 200,
|
||||||
|
page: pageNo,
|
||||||
|
});
|
||||||
|
if (data.next) {
|
||||||
|
return await fetchWorkflowNodes(
|
||||||
|
templateId,
|
||||||
|
pageNo + 1,
|
||||||
|
nodes.concat(data.results)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return nodes.concat(data.results);
|
||||||
|
} catch (error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function Visualizer({ template, i18n }) {
|
||||||
|
const [contentError, setContentError] = useState(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const [graphLinks, setGraphLinks] = useState([]);
|
||||||
|
// We'll also need to store the original set of nodes...
|
||||||
|
const [graphNodes, setGraphNodes] = useState([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const buildGraphArrays = nodes => {
|
||||||
|
const nonRootNodeIds = [];
|
||||||
|
const allNodeIds = [];
|
||||||
|
const arrayOfLinksForChart = [];
|
||||||
|
const nodeIdToChartNodeIdMapping = {};
|
||||||
|
const chartNodeIdToIndexMapping = {};
|
||||||
|
const nodeRef = {};
|
||||||
|
let nodeIdCounter = 1;
|
||||||
|
const arrayOfNodesForChart = [
|
||||||
|
{
|
||||||
|
id: nodeIdCounter,
|
||||||
|
unifiedJobTemplate: {
|
||||||
|
name: i18n._(t`START`),
|
||||||
|
},
|
||||||
|
type: 'node',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
nodeIdCounter++;
|
||||||
|
// Assign each node an ID - 0 is reserved for the start node. We need to
|
||||||
|
// make sure that we have an ID on every node including new nodes so the
|
||||||
|
// ID returned by the api won't do
|
||||||
|
nodes.forEach(node => {
|
||||||
|
node.workflowMakerNodeId = nodeIdCounter;
|
||||||
|
nodeRef[nodeIdCounter] = {
|
||||||
|
originalNodeObject: node,
|
||||||
|
};
|
||||||
|
|
||||||
|
const nodeObj = {
|
||||||
|
index: nodeIdCounter - 1,
|
||||||
|
id: nodeIdCounter,
|
||||||
|
type: 'node',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (node.summary_fields.job) {
|
||||||
|
nodeObj.job = node.summary_fields.job;
|
||||||
|
}
|
||||||
|
if (node.summary_fields.unified_job_template) {
|
||||||
|
nodeRef[nodeIdCounter].unifiedJobTemplate =
|
||||||
|
node.summary_fields.unified_job_template;
|
||||||
|
nodeObj.unifiedJobTemplate = node.summary_fields.unified_job_template;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOfNodesForChart.push(nodeObj);
|
||||||
|
allNodeIds.push(node.id);
|
||||||
|
nodeIdToChartNodeIdMapping[node.id] = node.workflowMakerNodeId;
|
||||||
|
chartNodeIdToIndexMapping[nodeIdCounter] = nodeIdCounter - 1;
|
||||||
|
nodeIdCounter++;
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const sourceIndex = chartNodeIdToIndexMapping[node.workflowMakerNodeId];
|
||||||
|
node.success_nodes.forEach(nodeId => {
|
||||||
|
const targetIndex =
|
||||||
|
chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
|
||||||
|
arrayOfLinksForChart.push({
|
||||||
|
source: arrayOfNodesForChart[sourceIndex],
|
||||||
|
target: arrayOfNodesForChart[targetIndex],
|
||||||
|
edgeType: 'success',
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
nonRootNodeIds.push(nodeId);
|
||||||
|
});
|
||||||
|
node.failure_nodes.forEach(nodeId => {
|
||||||
|
const targetIndex =
|
||||||
|
chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
|
||||||
|
arrayOfLinksForChart.push({
|
||||||
|
source: arrayOfNodesForChart[sourceIndex],
|
||||||
|
target: arrayOfNodesForChart[targetIndex],
|
||||||
|
edgeType: 'failure',
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
nonRootNodeIds.push(nodeId);
|
||||||
|
});
|
||||||
|
node.always_nodes.forEach(nodeId => {
|
||||||
|
const targetIndex =
|
||||||
|
chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[nodeId]];
|
||||||
|
arrayOfLinksForChart.push({
|
||||||
|
source: arrayOfNodesForChart[sourceIndex],
|
||||||
|
target: arrayOfNodesForChart[targetIndex],
|
||||||
|
edgeType: 'always',
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
nonRootNodeIds.push(nodeId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const uniqueNonRootNodeIds = Array.from(new Set(nonRootNodeIds));
|
||||||
|
|
||||||
|
const rootNodes = allNodeIds.filter(
|
||||||
|
nodeId => !uniqueNonRootNodeIds.includes(nodeId)
|
||||||
|
);
|
||||||
|
|
||||||
|
rootNodes.forEach(rootNodeId => {
|
||||||
|
const targetIndex =
|
||||||
|
chartNodeIdToIndexMapping[nodeIdToChartNodeIdMapping[rootNodeId]];
|
||||||
|
arrayOfLinksForChart.push({
|
||||||
|
source: arrayOfNodesForChart[0],
|
||||||
|
target: arrayOfNodesForChart[targetIndex],
|
||||||
|
edgeType: 'always',
|
||||||
|
type: 'link',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setGraphNodes(arrayOfNodesForChart);
|
||||||
|
setGraphLinks(arrayOfLinksForChart);
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
try {
|
||||||
|
const nodes = await fetchWorkflowNodes(template.id);
|
||||||
|
buildGraphArrays(nodes);
|
||||||
|
} catch (error) {
|
||||||
|
setContentError(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchData();
|
||||||
|
}, [template.id, i18n]);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<CenteredContent>
|
||||||
|
<ContentLoading />
|
||||||
|
</CenteredContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentError) {
|
||||||
|
return (
|
||||||
|
<CenteredContent>
|
||||||
|
<ContentError error={contentError} />
|
||||||
|
</CenteredContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VisualizerLayout>
|
||||||
|
<Toolbar template={template} />
|
||||||
|
{graphLinks.length > 0 ? (
|
||||||
|
<Graph
|
||||||
|
links={graphLinks}
|
||||||
|
nodes={graphNodes}
|
||||||
|
readOnly={!template.summary_fields.user_capabilities.edit}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<StartScreen />
|
||||||
|
)}
|
||||||
|
</VisualizerLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(Visualizer);
|
@ -0,0 +1,30 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Outer = styled.div`
|
||||||
|
position: relative;
|
||||||
|
height: 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Inner = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
left: 10px;
|
||||||
|
top: 10px;
|
||||||
|
background-color: #383f44;
|
||||||
|
color: white;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 2px;
|
||||||
|
max-width: 300px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
function WorkflowHelp({ children }) {
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<Outer>
|
||||||
|
<Inner>{children}</Inner>
|
||||||
|
</Outer>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default WorkflowHelp;
|
@ -0,0 +1,95 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const GridDL = styled.dl`
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: max-content;
|
||||||
|
column-gap: 15px;
|
||||||
|
row-gap: 0px;
|
||||||
|
|
||||||
|
dt {
|
||||||
|
grid-column-start: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
grid-column-start: 2;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
function WorkflowHelpDetails({ d, i18n }) {
|
||||||
|
const rows = [];
|
||||||
|
|
||||||
|
if (d.type === 'link') {
|
||||||
|
let linkType;
|
||||||
|
switch (d.edgeType) {
|
||||||
|
case 'always':
|
||||||
|
linkType = i18n._(t`Always`);
|
||||||
|
break;
|
||||||
|
case 'success':
|
||||||
|
linkType = i18n._(t`On Success`);
|
||||||
|
break;
|
||||||
|
case 'failure':
|
||||||
|
linkType = i18n._(t`On Failure`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
linkType = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
label: i18n._(t`Run`),
|
||||||
|
value: linkType,
|
||||||
|
});
|
||||||
|
} else if (d.type === 'node') {
|
||||||
|
if (d.unifiedJobTemplate) {
|
||||||
|
rows.push({
|
||||||
|
label: i18n._(t`Name`),
|
||||||
|
value: d.unifiedJobTemplate.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
let nodeType;
|
||||||
|
switch (d.unifiedJobTemplate.unified_job_type) {
|
||||||
|
case 'job':
|
||||||
|
nodeType = i18n._(t`Job Template`);
|
||||||
|
break;
|
||||||
|
case 'workflow_job':
|
||||||
|
nodeType = i18n._(t`Workflow Job Template`);
|
||||||
|
break;
|
||||||
|
case 'project_update':
|
||||||
|
nodeType = i18n._(t`Project Update`);
|
||||||
|
break;
|
||||||
|
case 'inventory_update':
|
||||||
|
nodeType = i18n._(t`Inventory Update`);
|
||||||
|
break;
|
||||||
|
case 'workflow_approval':
|
||||||
|
nodeType = i18n._(t`Workflow Approval`);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
nodeType = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.push({
|
||||||
|
label: i18n._(t`Type`),
|
||||||
|
value: nodeType,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// todo: this scenario (deleted)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GridDL>
|
||||||
|
{rows.map(row => (
|
||||||
|
<Fragment key={row.label}>
|
||||||
|
<dt>
|
||||||
|
<b>{row.label}</b>
|
||||||
|
</dt>
|
||||||
|
<dd>{row.value}</dd>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</GridDL>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(WorkflowHelpDetails);
|
@ -0,0 +1,6 @@
|
|||||||
|
export { default as Visualizer } from './Visualizer';
|
||||||
|
export { default as Toolbar } from './Toolbar';
|
||||||
|
export { default as Graph } from './Graph';
|
||||||
|
export { default as StartScreen } from './StartScreen';
|
||||||
|
export { default as WorkflowHelp } from './WorkflowHelp';
|
||||||
|
export { default as WorkflowHelpDetails } from './WorkflowHelpDetails';
|
Loading…
Reference in New Issue
Block a user