diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7ea678f268..2912cd3c60 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -126,7 +126,7 @@ "npm-run-all": "^4.1.5", "playwright": "^1.31.2", "storybook": "^7.5.0", - "storybook-addon-react-router-v6": "^0.3.6", + "storybook-addon-react-router-v6": "^2.0.10", "ts-jest": "^28.0.8" } }, @@ -7985,211 +7985,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "optional": true }, - "node_modules/@storybook/addons": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", - "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/api": "6.5.16", - "@storybook/channels": "6.5.16", - "@storybook/client-logger": "6.5.16", - "@storybook/core-events": "6.5.16", - "@storybook/csf": "0.0.2--canary.4566f4d.1", - "@storybook/router": "6.5.16", - "@storybook/theming": "6.5.16", - "@types/webpack-env": "^1.16.0", - "core-js": "^3.8.2", - "global": "^4.4.0", - "regenerator-runtime": "^0.13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/channels": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", - "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", - "optional": true, - "peer": true, - "dependencies": { - "core-js": "^3.8.2", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/client-logger": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", - "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", - "optional": true, - "peer": true, - "dependencies": { - "core-js": "^3.8.2", - "global": "^4.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/csf": { - "version": "0.0.2--canary.4566f4d.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", - "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", - "optional": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/@storybook/addons/node_modules/@storybook/router": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", - "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/client-logger": "6.5.16", - "core-js": "^3.8.2", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "regenerator-runtime": "^0.13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/api": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", - "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/channels": "6.5.16", - "@storybook/client-logger": "6.5.16", - "@storybook/core-events": "6.5.16", - "@storybook/csf": "0.0.2--canary.4566f4d.1", - "@storybook/router": "6.5.16", - "@storybook/semver": "^7.3.2", - "@storybook/theming": "6.5.16", - "core-js": "^3.8.2", - "fast-deep-equal": "^3.1.3", - "global": "^4.4.0", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3", - "regenerator-runtime": "^0.13.7", - "store2": "^2.12.0", - "telejson": "^6.0.8", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/api/node_modules/@storybook/channels": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", - "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", - "optional": true, - "peer": true, - "dependencies": { - "core-js": "^3.8.2", - "ts-dedent": "^2.0.0", - "util-deprecate": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/api/node_modules/@storybook/client-logger": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", - "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", - "optional": true, - "peer": true, - "dependencies": { - "core-js": "^3.8.2", - "global": "^4.4.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - } - }, - "node_modules/@storybook/api/node_modules/@storybook/csf": { - "version": "0.0.2--canary.4566f4d.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", - "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", - "optional": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/@storybook/api/node_modules/@storybook/router": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", - "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", - "optional": true, - "peer": true, - "dependencies": { - "@storybook/client-logger": "6.5.16", - "core-js": "^3.8.2", - "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "regenerator-runtime": "^0.13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/@storybook/api/node_modules/telejson": { - "version": "6.0.8", - "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", - "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", - "optional": true, - "peer": true, - "dependencies": { - "@types/is-function": "^1.0.0", - "global": "^4.4.0", - "is-function": "^1.0.2", - "is-regex": "^1.1.2", - "is-symbol": "^1.0.3", - "isobject": "^4.0.0", - "lodash": "^4.17.21", - "memoizerific": "^1.11.3" - } - }, "node_modules/@storybook/blocks": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.5.2.tgz", @@ -9589,19 +9384,21 @@ } }, "node_modules/@storybook/components": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.16.tgz", - "integrity": "sha512-LzBOFJKITLtDcbW9jXl0/PaG+4xAz25PK8JxPZpIALbmOpYWOAPcO6V9C2heX6e6NgWFMUxjplkULEk9RCQMNA==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/components/-/components-7.6.4.tgz", + "integrity": "sha512-K5RvEObJAnX+SbGJbkM1qrZEk+VR2cUhRCSrFnlfMwsn8/60T3qoH7U8bCXf8krDgbquhMwqev5WzDB+T1VV8g==", "optional": true, "peer": true, "dependencies": { - "@storybook/client-logger": "6.5.16", - "@storybook/csf": "0.0.2--canary.4566f4d.1", - "@storybook/theming": "6.5.16", - "core-js": "^3.8.2", + "@radix-ui/react-select": "^1.2.2", + "@radix-ui/react-toolbar": "^1.0.4", + "@storybook/client-logger": "7.6.4", + "@storybook/csf": "^0.1.2", + "@storybook/global": "^5.0.0", + "@storybook/theming": "7.6.4", + "@storybook/types": "7.6.4", "memoizerific": "^1.11.3", - "qs": "^6.10.0", - "regenerator-runtime": "^0.13.7", + "use-resize-observer": "^9.1.0", "util-deprecate": "^1.0.2" }, "funding": { @@ -9613,29 +9410,54 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/components/node_modules/@storybook/channels": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-7.6.4.tgz", + "integrity": "sha512-Z4PY09/Czl70ap4ObmZ4bgin+EQhPaA3HdrEDNwpnH7A9ttfEO5u5KThytIjMq6kApCCihmEPDaYltoVrfYJJA==", + "optional": true, + "peer": true, + "dependencies": { + "@storybook/client-logger": "7.6.4", + "@storybook/core-events": "7.6.4", + "@storybook/global": "^5.0.0", + "qs": "^6.10.0", + "telejson": "^7.2.0", + "tiny-invariant": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/@storybook/components/node_modules/@storybook/client-logger": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", - "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.4.tgz", + "integrity": "sha512-vJwMShC98tcoFruRVQ4FphmFqvAZX1FqZqjFyk6IxtFumPKTVSnXJjlU1SnUIkSK2x97rgdUMqkdI+wAv/tugQ==", "optional": true, "peer": true, "dependencies": { - "core-js": "^3.8.2", - "global": "^4.4.0" + "@storybook/global": "^5.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/storybook" } }, - "node_modules/@storybook/components/node_modules/@storybook/csf": { - "version": "0.0.2--canary.4566f4d.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", - "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "node_modules/@storybook/components/node_modules/@storybook/types": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/types/-/types-7.6.4.tgz", + "integrity": "sha512-qyiiXPCvol5uVgfubcIMzJBA0awAyFPU+TyUP1mkPYyiTHnsHYel/mKlSdPjc8a97N3SlJXHOCx41Hde4IyJgg==", "optional": true, "peer": true, "dependencies": { - "lodash": "^4.17.15" + "@storybook/channels": "7.6.4", + "@types/babel__core": "^7.0.0", + "@types/express": "^4.7.0", + "file-system-cache": "2.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" } }, "node_modules/@storybook/core-client": { @@ -9759,13 +9581,13 @@ } }, "node_modules/@storybook/core-events": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", - "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-7.6.4.tgz", + "integrity": "sha512-i3xzcJ19ILSy4oJL5Dz9y0IlyApynn5RsGhAMIsW+mcfri+hGfeakq1stNCo0o7jW4Y3A7oluFTtIoK8DOxQdQ==", "optional": true, "peer": true, "dependencies": { - "core-js": "^3.8.2" + "ts-dedent": "^2.0.0" }, "funding": { "type": "opencollective", @@ -10074,9 +9896,9 @@ "optional": true }, "node_modules/@storybook/csf": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.1.tgz", - "integrity": "sha512-4hE3AlNVxR60Wc5KSC68ASYzUobjPqtSKyhV6G+ge0FIXU55N5nTY7dXGRZHQGDBPq+XqchMkIdlkHPRs8nTHg==", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.1.2.tgz", + "integrity": "sha512-ePrvE/pS1vsKR9Xr+o+YwdqNgHUyXvg+1Xjx0h9LrVx7Zq4zNe06pd63F5EvzTbCbJsHj7GHr9tkiaqm7U8WRA==", "optional": true, "dependencies": { "type-fest": "^2.19.0" @@ -10709,79 +10531,6 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@storybook/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", - "optional": true, - "peer": true, - "dependencies": { - "core-js": "^3.6.5", - "find-up": "^4.1.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@storybook/semver/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "optional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/semver/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "optional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@storybook/semver/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "optional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@storybook/semver/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "optional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@storybook/store": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/@storybook/store/-/store-7.4.5.tgz", @@ -11109,17 +10858,88 @@ "ts-dedent": "^2.2.0" } }, + "node_modules/@storybook/testing-library/node_modules/@testing-library/dom": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", + "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", + "optional": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.1.3", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@storybook/testing-library/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@storybook/testing-library/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@storybook/testing-library/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/testing-library/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "optional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@storybook/theming": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", - "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-7.6.4.tgz", + "integrity": "sha512-Z/dcC5EpkIXelYCkt9ojnX6D7qGOng8YHxV/OWlVE9TrEGYVGPOEfwQryR0RhmGpDha1TYESLYrsDb4A8nJ1EA==", "optional": true, "peer": true, "dependencies": { - "@storybook/client-logger": "6.5.16", - "core-js": "^3.8.2", - "memoizerific": "^1.11.3", - "regenerator-runtime": "^0.13.7" + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@storybook/client-logger": "7.6.4", + "@storybook/global": "^5.0.0", + "memoizerific": "^1.11.3" }, "funding": { "type": "opencollective", @@ -11131,14 +10951,13 @@ } }, "node_modules/@storybook/theming/node_modules/@storybook/client-logger": { - "version": "6.5.16", - "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", - "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-7.6.4.tgz", + "integrity": "sha512-vJwMShC98tcoFruRVQ4FphmFqvAZX1FqZqjFyk6IxtFumPKTVSnXJjlU1SnUIkSK2x97rgdUMqkdI+wAv/tugQ==", "optional": true, "peer": true, "dependencies": { - "core-js": "^3.8.2", - "global": "^4.4.0" + "@storybook/global": "^5.0.0" }, "funding": { "type": "opencollective", @@ -11362,10 +11181,10 @@ "optional": true }, "node_modules/@testing-library/dom": { - "version": "8.20.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz", - "integrity": "sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==", - "optional": true, + "version": "9.3.3", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", + "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", + "devOptional": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -11377,14 +11196,14 @@ "pretty-format": "^27.0.2" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@testing-library/dom/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, + "devOptional": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -11399,7 +11218,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "optional": true, + "devOptional": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -11415,7 +11234,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -11424,7 +11243,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, + "devOptional": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -11521,77 +11340,6 @@ "react-dom": "^18.0.0" } }, - "node_modules/@testing-library/react/node_modules/@testing-library/dom": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.3.3.tgz", - "integrity": "sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.1.3", - "chalk": "^4.1.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@testing-library/react/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@testing-library/react/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@testing-library/react/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@testing-library/user-event": { "version": "13.5.0", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.5.0.tgz", @@ -12126,13 +11874,6 @@ "@types/node": "*" } }, - "node_modules/@types/is-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.1.tgz", - "integrity": "sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q==", - "optional": true, - "peer": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", @@ -12207,9 +11948,9 @@ "optional": true }, "node_modules/@types/js-yaml": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", - "integrity": "sha512-ACTuifTSIIbyksx2HTon3aFtCKWcID7/h3XEmRpDYdMCXxPbl+m9GteOJeaAkiAta/NJaSFuA7ahZ0NkwajDSw==", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", "dev": true }, "node_modules/@types/jsdom": { @@ -12514,13 +12255,6 @@ "@types/node": "*" } }, - "node_modules/@types/webpack-env": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.2.tgz", - "integrity": "sha512-BFqcTHHTrrI8EBmIzNAmLPP3IqtEG9J1IPFWbPeS/F0/TGNmo0pI5svOa7JbMF9vSCXQCvJWT2gxLJNVuf9blw==", - "optional": true, - "peer": true - }, "node_modules/@types/ws": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.6.tgz", @@ -15279,18 +15013,6 @@ "webpack": "^4.37.0 || ^5.0.0" } }, - "node_modules/core-js": { - "version": "3.33.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.0.tgz", - "integrity": "sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==", - "hasInstallScript": true, - "optional": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-js-compat": { "version": "3.33.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", @@ -16663,13 +16385,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "optional": true, - "peer": true - }, "node_modules/domelementtype": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", @@ -19130,17 +18845,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "optional": true, - "peer": true, - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, "node_modules/global-modules": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", @@ -20346,16 +20050,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-dom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz", - "integrity": "sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ==", - "optional": true, - "dependencies": { - "is-object": "^1.0.1", - "is-window": "^1.0.2" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -20385,13 +20079,6 @@ "node": ">=8" } }, - "node_modules/is-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", - "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", - "optional": true, - "peer": true - }, "node_modules/is-generator-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", @@ -20544,15 +20231,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", - "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", - "optional": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-path-cwd": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", @@ -20756,12 +20434,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-window": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-window/-/is-window-1.0.2.tgz", - "integrity": "sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg==", - "optional": true - }, "node_modules/is-windows": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", @@ -20795,16 +20467,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "devOptional": true }, - "node_modules/isobject": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", - "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", @@ -25397,16 +25059,6 @@ "node": ">=6" } }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "optional": true, - "peer": true, - "dependencies": { - "dom-walk": "^0.1.0" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -30072,23 +29724,25 @@ } }, "node_modules/storybook-addon-react-router-v6": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/storybook-addon-react-router-v6/-/storybook-addon-react-router-v6-0.3.6.tgz", - "integrity": "sha512-IYgUXG0QMWWpt4XyHbbbRGsfgQ17HvUzS3MBbXRpAIFiRcqvka4V0ZTuJQyThsLffEKlmML8hqDwsti/yDx20w==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/storybook-addon-react-router-v6/-/storybook-addon-react-router-v6-2.0.10.tgz", + "integrity": "sha512-HnrjbujAW1oGCdgvv7W6QKskETNXvt0U5qR2pKD8GXHlqkV9wmWa6HW3J0ks0aiBJBsRrPdEViatrhnRD5RZZw==", "optional": true, "dependencies": { - "react-inspector": "^5.1.1" + "compare-versions": "^6.0.0", + "react-inspector": "6.0.2" }, "peerDependencies": { - "@storybook/addons": "^6.4.0", - "@storybook/api": "^6.4.0", - "@storybook/components": "^6.4.0", - "@storybook/core-events": "^6.4.0", - "@storybook/theming": "^6.4.0", + "@storybook/blocks": "^7.0.0", + "@storybook/channels": "^7.0.0", + "@storybook/components": "^7.0.0", + "@storybook/core-events": "^7.0.0", + "@storybook/manager-api": "^7.0.0", + "@storybook/preview-api": "^7.0.0", + "@storybook/theming": "^7.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-router": "^6.3.0", - "react-router-dom": "^6.3.0" + "react-router-dom": "^6.4.0" }, "peerDependenciesMeta": { "react": { @@ -30099,19 +29753,11 @@ } } }, - "node_modules/storybook-addon-react-router-v6/node_modules/react-inspector": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-5.1.1.tgz", - "integrity": "sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg==", - "optional": true, - "dependencies": { - "@babel/runtime": "^7.0.0", - "is-dom": "^1.0.0", - "prop-types": "^15.0.0" - }, - "peerDependencies": { - "react": "^16.8.4 || ^17.0.0" - } + "node_modules/storybook-addon-react-router-v6/node_modules/compare-versions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", + "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==", + "optional": true }, "node_modules/stream-shift": { "version": "1.0.1", diff --git a/frontend/package.json b/frontend/package.json index c2d9e4ad7f..da9e5062bf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -155,7 +155,7 @@ "npm-run-all": "^4.1.5", "playwright": "^1.31.2", "storybook": "^7.5.0", - "storybook-addon-react-router-v6": "^0.3.6", + "storybook-addon-react-router-v6": "^2.0.10", "ts-jest": "^28.0.8" }, "overrides": { diff --git a/frontend/src/__mocks__/mock-upload-pipeline-version.yaml b/frontend/src/__mocks__/mock-upload-pipeline-version.yaml new file mode 100644 index 0000000000..7d496abc62 --- /dev/null +++ b/frontend/src/__mocks__/mock-upload-pipeline-version.yaml @@ -0,0 +1 @@ +test-yaml-content diff --git a/frontend/src/__mocks__/mockPipelineVersionTemplateResource.ts b/frontend/src/__mocks__/mockPipelineVersionTemplateResource.ts new file mode 100644 index 0000000000..78be5e1d39 --- /dev/null +++ b/frontend/src/__mocks__/mockPipelineVersionTemplateResource.ts @@ -0,0 +1,4 @@ +export default { + template: + 'apiVersion: tekton.dev/v1beta1\nkind: PipelineRun\nmetadata:\n name: dedup-ray-pipeline\n annotations:\n tekton.dev/output_artifacts: \'{"wow-so-cool": [{"key":\n "artifacts/$PIPELINERUN/wow-so-cool/Output.tgz", "name":\n "wow-so-cool-Output", "path": "/tmp/outputs/Output/data"}],\n "ok-great": [{"key":\n "artifacts/$PIPELINERUN/ok-great/Output.tgz", "name":\n "ok-great-Output", "path": "/tmp/outputs/Output/data"}]}\'\n tekton.dev/input_artifacts: \'{"execute-ray-jobs": [{"name":\n "wow-so-cool-Output", "parent_task":\n "wow-so-cool"}, {"name": "ok-great-Output",\n "parent_task": "ok-great"}]}\'\n tekton.dev/artifact_bucket: mlpipeline\n tekton.dev/artifact_endpoint: minio-service.kubeflow:9000\n tekton.dev/artifact_endpoint_scheme: http://\n tekton.dev/artifact_items: \'{"cleanup-ray-cluster": [],\n "wow-so-cool": [["Output", "$(results.Output.path)"]],\n "execute-ray-jobs": [], "ok-great": [["Output",\n "$(results.Output.path)"]]}\'\n sidecar.istio.io/inject: "false"\n tekton.dev/template: ""\n pipelines.kubeflow.org/big_data_passing_format: $(workspaces.$TASK_NAME.path)/artifacts/$ORIG_PR_NAME/$TASKRUN_NAME/$TASK_PARAM_NAME\n pipelines.kubeflow.org/pipeline_spec: \'{"description": "Pipeline to show how to\n use codeflare sdk to create Ray cluster and run dedup", "inputs":\n [{"default": "kfp-ray-dedup", "name": "name", "optional": true, "type":\n "String"}, {"default": "8", "name": "num_workers", "optional": true,\n "type": "String"}, {"default": "8", "name": "worker_cpu", "optional":\n true, "type": "String"}, {"default": "32", "name": "worker_memory",\n "optional": true, "type": "String"}, {"default": "0", "name":\n "worker_gpu", "optional": true, "type": "String"}, {"default": "1",\n "name": "wait_ready_tmout", "optional": true, "type": "String"},\n {"default": "100", "name": "wait_ready_retries", "optional": true, "type":\n "String"}, {"default": "1", "name": "print_tmout", "optional": true,\n "type": "String"}, {"default": "ray-dedup-template", "name":\n "template_cm", "optional": true, "type": "String"}, {"default":\n "quay.io/ibmdpdev/dedup-exact-ray:2.5.1-py310", "name": "image",\n "optional": true, "type": "String"}, {"default":\n "cos-optimal-llm-pile/blue-pile/0_internet/2_commoncrawl/ete=v0.2",\n "name": "input_path", "optional": true, "type": "String"}, {"default":\n "cos-optimal-llm-pile/boris_test/", "name": "output_path", "optional":\n true, "type": "String"}, {"default": "contents", "name": "content",\n "optional": true, "type": "String"}, {"default": "100", "name":\n "max_files", "optional": true, "type": "String"}, {"default": "5", "name":\n "n_sample", "optional": true, "type": "String"}, {"default": ".75",\n "name": "processor_cpu", "optional": true, "type": "String"}, {"default":\n ".5", "name": "hash_cpu", "optional": true, "type": "String"}], "name":\n "dedup-ray-pipeline"}\'\n labels:\n pipelines.kubeflow.org/pipelinename: ""\n pipelines.kubeflow.org/generation: ""\nspec:\n params:\n - name: content\n value: contents\n - name: hash_cpu\n value: ".5"\n - name: image\n value: quay.io/ibmdpdev/dedup-exact-ray:2.5.1-py310\n - name: input_path\n value: cos-optimal-llm-pile/blue-pile/0_internet/2_commoncrawl/ete=v0.2\n - name: max_files\n value: "100"\n - name: n_sample\n value: "5"\n - name: name\n value: kfp-ray-dedup\n - name: num_workers\n value: "8"\n - name: output_path\n value: cos-optimal-llm-pile/boris_test/\n - name: print_tmout\n value: "1"\n - name: processor_cpu\n value: ".75"\n - name: template_cm\n value: ray-dedup-template\n - name: wait_ready_retries\n value: "100"\n - name: wait_ready_tmout\n value: "1"\n - name: worker_cpu\n value: "8"\n - name: worker_gpu\n value: "0"\n - name: worker_memory\n value: "32"\n pipelineSpec:\n params:\n - name: content\n default: contents\n - name: hash_cpu\n default: ".5"\n - name: image\n default: quay.io/ibmdpdev/dedup-exact-ray:2.5.1-py310\n - name: input_path\n default: cos-optimal-llm-pile/blue-pile/0_internet/2_commoncrawl/ete=v0.2\n - name: max_files\n default: "100"\n - name: n_sample\n default: "5"\n - name: name\n default: kfp-ray-dedup\n - name: num_workers\n default: "8"\n - name: output_path\n default: cos-optimal-llm-pile/boris_test/\n - name: print_tmout\n default: "1"\n - name: processor_cpu\n default: ".75"\n - name: template_cm\n default: ray-dedup-template\n - name: wait_ready_retries\n default: "100"\n - name: wait_ready_tmout\n default: "1"\n - name: worker_cpu\n default: "8"\n - name: worker_gpu\n default: "0"\n - name: worker_memory\n default: "32"\n tasks:\n - name: wow-so-cool\n params:\n - name: content\n value: $(params.content)\n - name: hash_cpu\n value: $(params.hash_cpu)\n - name: input_path\n value: $(params.input_path)\n - name: max_files\n value: $(params.max_files)\n - name: n_sample\n value: $(params.n_sample)\n - name: num_workers\n value: $(params.num_workers)\n - name: processor_cpu\n value: $(params.processor_cpu)\n - name: worker_cpu\n value: $(params.worker_cpu)\n - name: worker_memory\n value: $(params.worker_memory)\n taskSpec:\n steps:\n - name: main\n args:\n - --num-workers\n - $(inputs.params.num_workers)\n - --worker-cpu\n - $(inputs.params.worker_cpu)\n - --worker-memory\n - $(inputs.params.worker_memory)\n - --input-path\n - $(inputs.params.input_path)\n - --content\n - $(inputs.params.content)\n - --n-sample\n - $(inputs.params.n_sample)\n - --max-files\n - $(inputs.params.max_files)\n - --processor-cpu\n - $(inputs.params.processor_cpu)\n - --hash-cpu\n - $(inputs.params.hash_cpu)\n - ----output-paths\n - $(results.Output.path)\n command:\n - sh\n - -ec\n - |\n program_path=$(mktemp)\n printf "%s" "$0" \u003e "$program_path"\n python3 -u "$program_path" "$@"\n - \u003e\n def compute_execution_params(num_workers, # number of workers\n worker_cpu, # number cpus per worker\n worker_memory, # memory per worker\n # Dedup specific parameters\n input_path, # data input directory\n content, # name of the documents column\n n_sample, # number of tables to sample\n max_files, # maximum amount of files to process\n processor_cpu, # Number of CPUs to do table processing\n hash_cpu # Number of CPUs to do table processing\n ):\n # required import\n from pyarrow import fs\n import pyarrow.parquet as pq\n\n import random\n import sys\n import math\n import json\n import os\n\n COS_URL = "https://s3.us-east.cloud-object-storage.appdomain.cloud"\n EXECUTION_OF_KB_DOC = 0.00025\n GB = 1024*1024*1024\n\n def _credentials():\n cos_key = os.getenv(\'COS_KEY\', "")\n cos_secret = os.getenv(\'COS_SECRET\', "")\n if cos_key == "" or cos_secret == "":\n print("Failed to get cos credentials")\n sys.exit(1)\n return cos_key, cos_secret\n\n def _sample_table_data(cos_key, cos_secret, input_path, max_files, n_samples):\n\n # Create fs\n s3 = fs.S3FileSystem(access_key=cos_key, secret_key=cos_secret, request_timeout=20, connect_timeout=20,\n retry_strategy=fs.AwsStandardS3RetryStrategy(max_attempts=20),\n endpoint_override=COS_URL)\n # Get files list\n path_list = [file.path for\n file in s3.get_file_info(fs.FileSelector(input_path)) if file.path.endswith(".parquet")]\n # Cap the amount of files if required\n if max_files \u003e 0:\n cap = min(len(path_list), max_files)\n path_list = path_list[:cap]\n print(f"Number of s3 files is {len(path_list)}")\n\n # Pick files to sample\n if len(path_list) \u003e n_samples:\n # Pick files at random\n files = [int(random.random()*len(path_list)) for _ in range(n_samples)]\n else:\n # use all existing files\n files = range(len(path_list))\n print(f"Using files {files} to sample data")\n\n # Read table and compute number of docs and sizes\n number_of_docs = []\n table_sizes = []\n for f in files:\n f_name = path_list[f]\n table = pq.read_table(f_name, filesystem=s3, columns=[content])\n number_of_docs.append(table.num_rows)\n table_sizes.append(sys.getsizeof(table))\n\n # compute averages\n av_number_docs = sum(number_of_docs) / len(files)\n av_table_size = sum(table_sizes) / len(files) / GB\n av_doc_size = av_table_size * GB / av_number_docs / 1024\n print(f"average number of docs {av_number_docs}, average table size {av_table_size} GB, "\n f"average doc size {av_doc_size} kB")\n\n # compute number of docs\n number_of_docs = av_number_docs * len(path_list)\n print(f"Estimated number of docs {number_of_docs}")\n return av_table_size, av_doc_size, number_of_docs\n\n # get credentials\n cos_key, cos_secret = _credentials()\n\n # sample input data\n av_table_size, av_doc_size, number_of_docs = _sample_table_data(\n cos_key=cos_key, cos_secret=cos_secret, input_path=input_path, max_files=int(max_files),\n n_samples=int(n_sample))\n n_hashes = math.ceil(number_of_docs * 32 / GB)\n print(f"Estimated Required hashes {n_hashes}")\n\n # Validate cluster size\n cluster_cpu = int(num_workers) * int(worker_cpu) * .85\n cluster_mem = int(num_workers) * int(worker_memory) * .85\n required_hash_cpu = n_hashes * float(hash_cpu)\n required_hash_mem = n_hashes * 2\n if required_hash_cpu \u003e cluster_cpu or required_hash_mem \u003e cluster_mem:\n print(f"Cluster is too small - hashes required cpus {required_hash_cpu}; "\n f"hashes required memory {required_hash_mem}")\n sys.exit(1)\n # Define number of workers\n n_workers = int((cluster_cpu - required_hash_cpu)/float(processor_cpu))\n print(f"Number of workers - {n_workers}")\n if n_workers \u003c 5:\n print(f"Cluster is too small - estimated number of workers {n_workers}")\n sys.exit(1)\n r_mem = required_hash_mem * 2 + av_table_size * 4 * n_workers\n print(f"Required execution memory {r_mem} GB")\n if r_mem \u003e cluster_mem:\n print(f"Not enough memory to run de duping, required {r_mem}, available {cluster_mem}")\n print(f"Try to increase the size of the cluster or increase size of the cpu per worker")\n sys.exit(1)\n print(f"Projected execution time {EXECUTION_OF_KB_DOC * av_doc_size * number_of_docs / n_workers / 60} min")\n return json.dumps({\'workers\': n_workers, \'hashes\': n_hashes})\n\n def _serialize_str(str_value: str) -\u003e str:\n if not isinstance(str_value, str):\n raise TypeError(\'Value "{}" has type "{}" instead of str.\'.format(\n str(str_value), str(type(str_value))))\n return str_value\n\n import argparse\n\n _parser = argparse.ArgumentParser(prog=\'Compute execution params\', description=\'\')\n\n _parser.add_argument("--num-workers", dest="num_workers", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--worker-cpu", dest="worker_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--worker-memory", dest="worker_memory", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--input-path", dest="input_path", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--content", dest="content", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--n-sample", dest="n_sample", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--max-files", dest="max_files", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--processor-cpu", dest="processor_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--hash-cpu", dest="hash_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)\n\n _parsed_args = vars(_parser.parse_args())\n\n _output_files = _parsed_args.pop("_output_paths", [])\n\n\n _outputs = compute_execution_params(**_parsed_args)\n\n\n _outputs = [_outputs]\n\n\n _output_serializers = [\n _serialize_str,\n\n ]\n\n\n import os\n\n for idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except OSError:\n pass\n with open(output_file, \'w\') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n env:\n - name: COS_KEY\n valueFrom:\n secretKeyRef:\n key: cos-key\n name: cos-access\n - name: COS_SECRET\n valueFrom:\n secretKeyRef:\n key: cos-secret\n name: cos-access\n image: quay.io/ibmdpdev/kfp-oc:2.5.1-py310\n imagePullPolicy: Always\n params:\n - name: content\n - name: hash_cpu\n - name: input_path\n - name: max_files\n - name: n_sample\n - name: num_workers\n - name: processor_cpu\n - name: worker_cpu\n - name: worker_memory\n results:\n - name: Output\n type: string\n description: /tmp/outputs/Output/data\n metadata:\n labels:\n pipelines.kubeflow.org/cache_enabled: "true"\n annotations:\n pipelines.kubeflow.org/component_spec_digest: \'{"name": "Compute execution\n params", "outputs": [{"name": "Output", "type": "String"}],\n "version": "Compute execution\n params@sha256=441f924ac97baa98300019ea5bc3a1fb109d4c2f80de356fde7984b9505e1c98"}\'\n - name: ok-great\n params:\n - name: image\n value: $(params.image)\n - name: name\n value: $(params.name)\n - name: num_workers\n value: $(params.num_workers)\n - name: template_cm\n value: $(params.template_cm)\n - name: worker_cpu\n value: $(params.worker_cpu)\n - name: worker_gpu\n value: $(params.worker_gpu)\n - name: worker_memory\n value: $(params.worker_memory)\n taskSpec:\n steps:\n - name: main\n args:\n - --name\n - $(inputs.params.name)\n - --num-workers\n - $(inputs.params.num_workers)\n - --worker-cpu\n - $(inputs.params.worker_cpu)\n - --worker-memory\n - $(inputs.params.worker_memory)\n - --worker-gpu\n - $(inputs.params.worker_gpu)\n - --image\n - $(inputs.params.image)\n - ----output-paths\n - $(results.Output.path)\n command:\n - sh\n - -ec\n - |\n program_path=$(mktemp)\n printf "%s" "$0" \u003e "$program_path"\n python3 -u "$program_path" "$@"\n - \u003e\n def start_ray_cluster(\n name, # name of Ray cluster\n num_workers, # number of workers\n worker_cpu, # number cpus per worker\n worker_memory, # memory per worker\n worker_gpu, # number of gpus per worker\n image, # image for Ray cluster\n ):\n # Imports\n import os\n import sys\n import time\n\n import ray\n from codeflare_sdk.cluster.auth import TokenAuthentication\n from codeflare_sdk.cluster.cluster import Cluster, ClusterConfiguration\n\n # Ray routine to get the amount of nodes\n @ray.remote\n def get_cluster_nodes():\n nodes = ray.nodes()\n nnodes = -1\n for n in nodes:\n if n["alive"]:\n nnodes = nnodes + 1\n return nnodes\n\n # get current namespace\n ns = os.getenv("NAMESPACE", "default")\n # change the current directory to ensure that we can write\n os.chdir("/tmp")\n print(f"Executing in namespace {ns}, current working directory is {os.getcwd()}")\n\n # Create authentication object for oc user permissions\n with open("/var/run/secrets/kubernetes.io/serviceaccount/token", "r") as file:\n token = file.read().rstrip()\n auth = TokenAuthentication(token=token, server="https://kubernetes.default:443", skip_tls=True)\n try:\n auth.login()\n except Exception as e:\n print(f"Failed to log into openshift cluster, error {e}. Please check token/server values provided")\n sys.exit(1)\n print("successfully logged in")\n\n # Create and configure our cluster object (and appwrapper)\n cluster = Cluster(\n ClusterConfiguration(\n name=name,\n namespace=ns,\n min_worker=int(num_workers),\n max_worker=int(num_workers),\n min_cpus=int(worker_cpu),\n max_cpus=int(worker_cpu),\n min_memory=int(worker_memory),\n max_memory=int(worker_memory),\n gpu=int(worker_gpu),\n image=image,\n template="/templates/ray_template.yaml",\n instascale=False,\n )\n )\n print(f"Configuration for Ray cluster {name} in namespace {ns} is created")\n\n try:\n # bring up the cluster\n cluster.up()\n print(f"Creating Ray cluster {name} in namespace {ns}...")\n\n # and wait for it being up\n cluster.wait_ready()\n rc = cluster.details(print_to_console=False)\n print("Ray cluster is ready")\n print(rc)\n\n # Get cluster connection point for job submission\n ray_dashboard_uri = cluster.cluster_dashboard_uri()\n ray_cluster_uri = cluster.cluster_uri()\n print(f"Ray_cluster is at {ray_cluster_uri}")\n print(f"Ray_cluster dashboard is at {ray_dashboard_uri}")\n\n # wait to ensure that cluster is really up. Without this we can get occasional 503 error\n time.sleep(5)\n\n # make sure all the nodes are up\n ray.init(address=f"{ray_cluster_uri}")\n running = 0\n while running \u003c int(num_workers):\n running = ray.get(get_cluster_nodes.remote())\n print(f"{running} nodes are currently available")\n time.sleep(1)\n ray.shutdown()\n return ray_dashboard_uri\n except Exception as e:\n print(f"Failed to init Ray cluster, error {e}")\n sys.exit(1)\n\n def _serialize_str(str_value: str) -\u003e str:\n if not isinstance(str_value, str):\n raise TypeError(\'Value "{}" has type "{}" instead of str.\'.format(\n str(str_value), str(type(str_value))))\n return str_value\n\n import argparse\n\n _parser = argparse.ArgumentParser(prog=\'Start ray cluster\', description=\'\')\n\n _parser.add_argument("--name", dest="name", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--num-workers", dest="num_workers", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--worker-cpu", dest="worker_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--worker-memory", dest="worker_memory", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--worker-gpu", dest="worker_gpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--image", dest="image", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("----output-paths", dest="_output_paths", type=str, nargs=1)\n\n _parsed_args = vars(_parser.parse_args())\n\n _output_files = _parsed_args.pop("_output_paths", [])\n\n\n _outputs = start_ray_cluster(**_parsed_args)\n\n\n _outputs = [_outputs]\n\n\n _output_serializers = [\n _serialize_str,\n\n ]\n\n\n import os\n\n for idx, output_file in enumerate(_output_files):\n try:\n os.makedirs(os.path.dirname(output_file))\n except OSError:\n pass\n with open(output_file, \'w\') as f:\n f.write(_output_serializers[idx](_outputs[idx]))\n env:\n - name: NAMESPACE\n valueFrom:\n fieldRef:\n fieldPath: metadata.namespace\n image: quay.io/ibmdpdev/kfp-oc:2.5.1-py310\n imagePullPolicy: Always\n volumeMounts:\n - mountPath: /templates\n name: templates\n params:\n - name: image\n - name: name\n - name: num_workers\n - name: template_cm\n - name: worker_cpu\n - name: worker_gpu\n - name: worker_memory\n results:\n - name: Output\n type: string\n description: /tmp/outputs/Output/data\n volumes:\n - configMap:\n name: $(inputs.params.template_cm)\n name: templates\n metadata:\n labels:\n pipelines.kubeflow.org/cache_enabled: "true"\n annotations:\n pipelines.kubeflow.org/component_spec_digest: \'{"name": "Start ray cluster",\n "outputs": [{"name": "Output", "type": "String"}], "version":\n "Start ray\n cluster@sha256=aa5e200274d1b535332c459aaa022a0209f81336c1ea303b319be92deb2b9347"}\'\n pipelines.kubeflow.org/max_cache_staleness: P0D\n runAfter:\n - wow-so-cool\n - name: execute-ray-jobs\n params:\n - name: wow-so-cool-Output\n value: $(tasks.wow-so-cool.results.Output)\n - name: content\n value: $(params.content)\n - name: hash_cpu\n value: $(params.hash_cpu)\n - name: input_path\n value: $(params.input_path)\n - name: max_files\n value: $(params.max_files)\n - name: output_path\n value: $(params.output_path)\n - name: print_tmout\n value: $(params.print_tmout)\n - name: processor_cpu\n value: $(params.processor_cpu)\n - name: ok-great-Output\n value: $(tasks.ok-great.results.Output)\n - name: wait_ready_retries\n value: $(params.wait_ready_retries)\n - name: wait_ready_tmout\n value: $(params.wait_ready_tmout)\n taskSpec:\n steps:\n - name: main\n args:\n - --ray-dashboard-uri\n - $(inputs.params.ok-great-Output)\n - --wait-ready-tmout\n - $(inputs.params.wait_ready_tmout)\n - --wait-ready-retries\n - $(inputs.params.wait_ready_retries)\n - --print-tmout\n - $(inputs.params.print_tmout)\n - --input-path\n - $(inputs.params.input_path)\n - --output-path\n - $(inputs.params.output_path)\n - --content\n - $(inputs.params.content)\n - --max-files\n - $(inputs.params.max_files)\n - --processor-cpu\n - $(inputs.params.processor_cpu)\n - --hash-cpu\n - $(inputs.params.hash_cpu)\n - --execution-params\n - $(inputs.params.wow-so-cool-Output)\n command:\n - sh\n - -ec\n - |\n program_path=$(mktemp)\n printf "%s" "$0" \u003e "$program_path"\n python3 -u "$program_path" "$@"\n - \u003e\n def execute_ray_jobs(\n ray_dashboard_uri, # ray dashboard uri\n wait_ready_tmout, # wait tmout for job ready\n wait_ready_retries,# wait retries for job ready\n print_tmout, # print timeout\n # Dedup specific parameters\n input_path, # data input directory\n output_path, # data output directory\n content, # name of the documents column\n max_files, # maximum amount of files to process\n processor_cpu, # Number of CPUs to do table processing\n hash_cpu, # Number of CPUs to do table processing\n execution_params # config parameters computed in initial step\n ):\n # required imports\n from pyarrow import fs\n import json\n import sys\n import time\n import os\n\n import requests\n from ray.job_submission import JobStatus\n\n COS_URL = "https://s3.us-east.cloud-object-storage.appdomain.cloud"\n\n # Get credentials\n def _credentials():\n cos_key = os.getenv(\'COS_KEY\', "")\n cos_secret = os.getenv(\'COS_SECRET\', "")\n if cos_key == "" or cos_secret == "":\n print("Failed to get cos credentials")\n sys.exit(1)\n return cos_key, cos_secret\n\n # Get job status\n def _get_job_status(ray_dashboard_uri, job_id):\n resp = requests.get(f"{ray_dashboard_uri}/api/jobs/{job_id}")\n if resp.status_code == 200:\n rst = json.loads(resp.text)\n return rst["status"]\n else:\n print(f"Getting job execution status failed, code {resp.status_code}")\n return JobStatus.PENDING\n\n # Get job log\n def _get_job_log(ray_dashboard_uri, job_id):\n resp = requests.get(f"{ray_dashboard_uri}/api/jobs/{job_id}/logs")\n if resp.status_code == 200:\n rst = json.loads(resp.text)\n return rst["logs"]\n else:\n print(f"Getting job execution log failed, code {resp.status_code}")\n return \'\'\n\n # Print job log\n def _print_log(log, previous_log_len):\n l_to_print = log[previous_log_len:]\n if len(l_to_print) \u003e 0:\n print(log)\n\n # Get parameters necessary for submitting\n execution_params = json.loads(execution_params)\n cos_key, cos_secret = _credentials()\n\n #Submitting job\n job_id = None\n try:\n resp = requests.post(\n f"{ray_dashboard_uri}/api/jobs/",\n json={\n "entrypoint": f"python dedup_exact_files_wf.py --input_path={input_path} --output_path={output_path} "\n f"--content={content} --max_files={int(max_files)} --worker_cpu={float(processor_cpu)} "\n f"--hash_cpu={float(hash_cpu)} --num_hashes={execution_params[\'hashes\']} "\n f"--num_workers={execution_params[\'workers\']}",\n "runtime_env": {\'env_vars\': {\'COS_KEY\': cos_key, \'COS_SECRET\': cos_secret}}\n }\n )\n if resp.status_code == 200:\n rst = json.loads(resp.text)\n job_id = rst["job_id"]\n print(f"Submitted job to Ray with the id: {job_id}")\n else:\n print(f"Failed to submitted job to Ray, code : {resp.status_code}")\n\n except Exception as e:\n print(f"Failed to submit job to Ray cluster, error {e}")\n sys.exit(1)\n\n # Waiting job to start\n status = JobStatus.PENDING\n tries = 0\n try:\n while status != JobStatus.RUNNING:\n tries = tries + 1\n status = _get_job_status(ray_dashboard_uri, job_id)\n if status in {JobStatus.STOPPED, JobStatus.SUCCEEDED, JobStatus.FAILED}:\n break\n if tries \u003e= int(wait_ready_retries):\n print(f"Failed to get job success status in {int(wait_ready_retries)} tries")\n sys.exit(1)\n time.sleep(int(wait_ready_tmout))\n print(f"Job execution status is {status}")\n\n # while job is running get job\'s log\n previous_log_len = 0\n while status == JobStatus.RUNNING:\n log = _get_job_log(ray_dashboard_uri, job_id)\n _print_log(log, previous_log_len)\n previous_log_len = len(log)\n time.sleep(int(print_tmout))\n # Update status\n status = _get_job_status(ray_dashboard_uri, job_id)\n\n # print final log and status\n flog = _get_job_log(ray_dashboard_uri, job_id)\n _print_log(flog, previous_log_len)\n print(f"Job execution status is {status}")\n\n # Save the log to S3\n s3 = fs.S3FileSystem(access_key=cos_key, secret_key=cos_secret, request_timeout=20, connect_timeout=20,\n retry_strategy=fs.AwsStandardS3RetryStrategy(max_attempts=20),endpoint_override=COS_URL)\n with s3.open_output_stream(f"{output_path}execution.log") as stream:\n stream.write(bytes(flog, \'UTF-8\'))\n\n except Exception as e:\n print(f"Failed to get Ray job execution result, error {e}")\n sys.exit(1)\n\n import argparse\n\n _parser = argparse.ArgumentParser(prog=\'Execute ray jobs\', description=\'\')\n\n _parser.add_argument("--ray-dashboard-uri", dest="ray_dashboard_uri", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--wait-ready-tmout", dest="wait_ready_tmout", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--wait-ready-retries", dest="wait_ready_retries", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--print-tmout", dest="print_tmout", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--input-path", dest="input_path", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--output-path", dest="output_path", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--content", dest="content", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--max-files", dest="max_files", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--processor-cpu", dest="processor_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--hash-cpu", dest="hash_cpu", type=str, required=True, default=argparse.SUPPRESS)\n\n _parser.add_argument("--execution-params", dest="execution_params", type=str, required=True, default=argparse.SUPPRESS)\n\n _parsed_args = vars(_parser.parse_args())\n\n\n _outputs = execute_ray_jobs(**_parsed_args)\n env:\n - name: COS_KEY\n valueFrom:\n secretKeyRef:\n key: cos-key\n name: cos-access\n - name: COS_SECRET\n valueFrom:\n secretKeyRef:\n key: cos-secret\n name: cos-access\n image: quay.io/ibmdpdev/kfp-oc:2.5.1-py310\n imagePullPolicy: Always\n params:\n - name: wow-so-cool-Output\n - name: content\n - name: hash_cpu\n - name: input_path\n - name: max_files\n - name: output_path\n - name: print_tmout\n - name: processor_cpu\n - name: ok-great-Output\n - name: wait_ready_retries\n - name: wait_ready_tmout\n metadata:\n labels:\n pipelines.kubeflow.org/cache_enabled: "true"\n annotations:\n pipelines.kubeflow.org/component_spec_digest: \'{"name": "Execute ray jobs",\n "outputs": [], "version": "Execute ray\n jobs@sha256=bbf2878b3121c17550fbf97e668e66f7c733112b15ba468dbbdfbe942fb5f36a"}\'\n pipelines.kubeflow.org/max_cache_staleness: P0D\n finally:\n - name: cleanup-ray-cluster\n params:\n - name: name\n value: $(params.name)\n taskSpec:\n steps:\n - name: main\n args:\n - --name\n - $(inputs.params.name)\n command:\n - sh\n - -ec\n - |\n program_path=$(mktemp)\n printf "%s" "$0" \u003e "$program_path"\n python3 -u "$program_path" "$@"\n - \u003e\n def cleanup_ray_cluster(\n name, # name of Ray cluster\n ):\n # Required imports\n import os\n import sys\n from codeflare_sdk.cluster.auth import TokenAuthentication\n from codeflare_sdk.cluster.cluster import Cluster, ClusterConfiguration\n\n # get current namespace\n ns = os.getenv("NAMESPACE", "default")\n # change the current directory to ensure that we can write\n os.chdir("/tmp")\n print(f"Executing in namespace {ns}, current working directory is {os.getcwd()}")\n\n # Create authentication object for oc user permissions\n with open("/var/run/secrets/kubernetes.io/serviceaccount/token", "r") as file:\n token = file.read().rstrip()\n auth = TokenAuthentication(token=token, server="https://kubernetes.default:443", skip_tls=True)\n try:\n auth.login()\n except Exception as e:\n print(f"Failed to log into openshift cluster, error {e}. Please check token/server values provided")\n sys.exit(1)\n print("successfully logged in")\n # Create and configure our cluster object (and appwrapper)\n cluster = Cluster(\n ClusterConfiguration(\n name=name,\n namespace=ns,\n )\n )\n print(f"Configuration for Ray cluster {name} in namespace {ns} is created")\n # delete cluster\n print("All done. Cleaning up")\n try:\n cluster.down()\n except Exception as e:\n print(f"Failed to down the Ray cluster, error {e}. Please check that the {name} appwrapper is removed")\n\n import argparse\n\n _parser = argparse.ArgumentParser(prog=\'Cleanup ray cluster\', description=\'\')\n\n _parser.add_argument("--name", dest="name", type=str, required=True, default=argparse.SUPPRESS)\n\n _parsed_args = vars(_parser.parse_args())\n\n\n _outputs = cleanup_ray_cluster(**_parsed_args)\n env:\n - name: NAMESPACE\n valueFrom:\n fieldRef:\n fieldPath: metadata.namespace\n image: quay.io/ibmdpdev/kfp-oc:2.5.1-py310\n imagePullPolicy: Always\n params:\n - name: name\n metadata:\n labels:\n pipelines.kubeflow.org/cache_enabled: "true"\n annotations:\n pipelines.kubeflow.org/component_spec_digest: \'{"name": "Cleanup ray cluster",\n "outputs": [], "version": "Cleanup ray\n cluster@sha256=d0289f79abcf5240c250d7b8cffead358d6731a59c31c42b662653591da301a1"}\'\n podTemplate:\n imagePullSecrets:\n - name: quay\n', +}; diff --git a/frontend/src/__mocks__/mockPipelineVersionsProxy.ts b/frontend/src/__mocks__/mockPipelineVersionsProxy.ts index f19b9f9637..70db1b035b 100644 --- a/frontend/src/__mocks__/mockPipelineVersionsProxy.ts +++ b/frontend/src/__mocks__/mockPipelineVersionsProxy.ts @@ -1,7 +1,30 @@ +/* eslint-disable camelcase */ import { PipelineVersionKF, RelationshipKF, ResourceTypeKF } from '~/concepts/pipelines/kfTypes'; -/* eslint-disable camelcase */ -export const mockPipelineVersionsProxy: PipelineVersionKF[] = [ +export const buildMockPipelineVersion = ( + pipelineVersion?: Partial, +): PipelineVersionKF => ({ + id: '8ce2d041-3eb9-41a0-828c-45209fdf1c20', + name: 'version-1', + created_at: '2023-12-07T16:08:01Z', + resource_references: [ + { + key: { type: 'PIPELINE' as ResourceTypeKF, id: 'b2ff4cbf-f7f5-4c8a-b454-906bd9b00510' }, + relationship: 'OWNER' as RelationshipKF, + }, + ], + description: 'test', + ...pipelineVersion, +}); + +export const buildMockPipelineVersions = ( + versions: PipelineVersionKF[] = mockPipelineVersionsList, +) => ({ + versions, + total_size: versions.length, +}); + +export const mockPipelineVersionsList: PipelineVersionKF[] = [ { id: 'ad1b7153-d2fd-4e5e-ae12-30c824b19b03', name: 'flip coin_version_at_2023-12-01T01:42:09.321Z', diff --git a/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.spec.ts b/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.spec.ts index df5ac45f60..799a496b35 100644 --- a/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.spec.ts +++ b/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.spec.ts @@ -1,5 +1,5 @@ import { test, expect } from '@playwright/test'; -import { mockPipelineVersionsProxy } from '~/__mocks__/mockPipelineVersionsProxy'; +import { mockPipelineVersionsList } from '~/__mocks__/mockPipelineVersionsProxy'; import { navigateToStory } from '~/__tests__/integration/utils'; test('Pipeline version selector - Test filter and view more', async ({ page }) => { @@ -8,10 +8,10 @@ test('Pipeline version selector - Test filter and view more', async ({ page }) = await expect(page.locator('[data-id="pipeline-selector-table-list-row"]')).toHaveCount(10); // check the view more button and the label - await expect(page.getByText(`Showing 10/${mockPipelineVersionsProxy.length}`)).toBeVisible(); + await expect(page.getByText(`Showing 10/${mockPipelineVersionsList.length}`)).toBeVisible(); await page.getByText('View more').click(); await expect(page.locator('[data-id="pipeline-selector-table-list-row"]')).toHaveCount( - mockPipelineVersionsProxy.length, + mockPipelineVersionsList.length, ); // test the search @@ -20,7 +20,7 @@ test('Pipeline version selector - Test filter and view more', async ({ page }) = await expect(page.locator('[data-id="pipeline-selector-table-list-row"]')).toHaveCount(10); await page.getByText('View more').click(); await expect(page.locator('[data-id="pipeline-selector-table-list-row"]')).toHaveCount( - mockPipelineVersionsProxy.filter((version) => version.name.startsWith(search)).length, + mockPipelineVersionsList.filter((version) => version.name.startsWith(search)).length, ); await page.getByLabel('Filter pipelines').fill('test-no-result'); await expect(page.getByText('No results match the filter.')).toBeVisible(); diff --git a/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.stories.tsx b/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.stories.tsx index e1f8ca077a..e0c5db3efe 100644 --- a/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.stories.tsx +++ b/frontend/src/__tests__/integration/components/pipelines/PipelineSelector.stories.tsx @@ -3,7 +3,7 @@ import { Meta, StoryObj } from '@storybook/react'; import { userEvent, within } from '@storybook/testing-library'; import PipelineSelector from '~/concepts/pipelines/content/pipelineSelector/PipelineSelector'; import { pipelineVersionSelectorColumns } from '~/concepts/pipelines/content/pipelineSelector/columns'; -import { mockPipelineVersionsProxy } from '~/__mocks__/mockPipelineVersionsProxy'; +import { mockPipelineVersionsList } from '~/__mocks__/mockPipelineVersionsProxy'; export default { component: PipelineSelector, @@ -14,10 +14,10 @@ export const Default: StoryObj = { null} placeHolder="Select a pipeline version" - searchHelperText={`Type a name to search your ${mockPipelineVersionsProxy.length} versions.`} + searchHelperText={`Type a name to search your ${mockPipelineVersionsList.length} versions.`} isLoading={false} /> ), diff --git a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx index 89d52bd500..d0b7bf4153 100644 --- a/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx +++ b/frontend/src/__tests__/integration/pages/modelServing/ModelServingGlobal.stories.tsx @@ -139,8 +139,16 @@ export default { component: ModelServingGlobal, parameters: { reactRouter: { - routePath: '/modelServing/:namespace/*', - routeParams: { namespace: 'test-project' }, + location: { + pathParams: { + namespace: 'test-project', + }, + }, + routing: [ + { + path: '/modelServing/:namespace/*', + }, + ], }, }, } as Meta; diff --git a/frontend/src/__tests__/integration/pages/modelServing/ServingRuntimeList.stories.tsx b/frontend/src/__tests__/integration/pages/modelServing/ServingRuntimeList.stories.tsx index 0757b2fc1d..cf4f791a8f 100644 --- a/frontend/src/__tests__/integration/pages/modelServing/ServingRuntimeList.stories.tsx +++ b/frontend/src/__tests__/integration/pages/modelServing/ServingRuntimeList.stories.tsx @@ -182,8 +182,16 @@ export default { component: ModelServingPlatform, parameters: { reactRouter: { - routePath: '/projects/:namespace/*', - routeParams: { namespace: 'test-project' }, + location: { + pathParams: { + namespace: 'test-project', + }, + }, + routing: [ + { + path: '/projects/:namespace/*', + }, + ], }, }, } as Meta; diff --git a/frontend/src/__tests__/integration/pages/notebookImageSettings/NotebookImageSettings.spec.ts b/frontend/src/__tests__/integration/pages/notebookImageSettings/NotebookImageSettings.spec.ts index ff2f137105..ebbc55faad 100644 --- a/frontend/src/__tests__/integration/pages/notebookImageSettings/NotebookImageSettings.spec.ts +++ b/frontend/src/__tests__/integration/pages/notebookImageSettings/NotebookImageSettings.spec.ts @@ -198,6 +198,8 @@ test('Edit form fields match', async ({ page }) => { navigateToStory('pages-notebookimagesettings-notebookimagesettings', 'edit-modal'), ); + await page.waitForSelector('[data-testid="notebook-image-modal"]'); + // test inputs have correct values expect(await page.getByTestId('byon-image-location-input').inputValue()).toBe( 'test-image:latest', diff --git a/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.spec.ts b/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.spec.ts new file mode 100644 index 0000000000..6a2c93bf3c --- /dev/null +++ b/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '@playwright/test'; +import { navigateToStory } from '~/__tests__/integration/utils'; + +test('page details are updated when a new pipeline version is selected', async ({ page }) => { + await page.goto(navigateToStory('pages-pipelines-pipelinedetails', 'default')); + await page.waitForSelector('[data-testid="pipeline-version-topology-content"]'); + + const toggleButtonInput = await page.getByTestId('pipeline-version-toggle-button'); + + // Verify default version is selected and visible by default + await expect(toggleButtonInput).toHaveText('Pipeline version: version-1'); + + await toggleButtonInput.click(); + await page + .locator('[data-id="pipeline-selector-table-list-row"]', { hasText: 'version-2' }) + .click(); + + // Verify new version is selected and visible after selection + await expect(toggleButtonInput).toHaveText('Pipeline version: version-2'); +}); + +test('page details are updated after uploading a new version', async ({ page }) => { + const newVersionName = 'new-upload-version'; + + await page.goto(navigateToStory('pages-pipelines-pipelinedetails', 'default')); + await page.waitForSelector('[data-testid="pipeline-version-topology-content"]'); + + const toggleButtonInput = await page.getByTestId('pipeline-version-toggle-button'); + + // Verify default version is selected and visible by default + await expect(toggleButtonInput).toHaveText('Pipeline version: version-1'); + + await page.getByRole('button', { name: 'Actions' }).click(); + await page.getByRole('menuitem', { name: 'Upload new version' }).click(); + + const versionNameInput = await page.getByLabel('Pipeline version name'); + await versionNameInput.clear(); + await versionNameInput.fill(newVersionName); + + // Simulate file upload + await page.setInputFiles( + '[data-testid="pipeline-file-upload"] input[type="file"]', + './src/__mocks__/mock-upload-pipeline-version.yaml', + ); + + const submitButton = await page.getByTestId('upload-version-submit-button'); + await submitButton.click(); + await page.waitForSelector('[data-testid="upload-version-modal"]', { state: 'hidden' }); + + // Verify new version is selected and visible after selection + await expect(toggleButtonInput).toHaveText(`Pipeline version: ${newVersionName}`); +}); diff --git a/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.stories.tsx b/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.stories.tsx new file mode 100644 index 0000000000..d2bf62e3fa --- /dev/null +++ b/frontend/src/__tests__/integration/pages/pipelines/PipelineDetails.stories.tsx @@ -0,0 +1,119 @@ +import React from 'react'; + +import { StoryObj } from '@storybook/react'; +import { rest } from 'msw'; + +import PipelineDetails from '~/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails'; +import { mockK8sResourceList } from '~/__mocks__/mockK8sResourceList'; +import { mockProjectK8sResource } from '~/__mocks__/mockProjectK8sResource'; +import { mockDataSciencePipelineApplicationK8sResource } from '~/__mocks__/mockDataSciencePipelinesApplicationK8sResource'; +import { mockRouteK8sResource } from '~/__mocks__/mockRouteK8sResource'; +import { mockSecretK8sResource } from '~/__mocks__/mockSecretK8sResource'; +import { PipelineContextProvider } from '~/concepts/pipelines/context'; +import mockPipelineVersionTemplateResource from '~/__mocks__/mockPipelineVersionTemplateResource'; +import { + buildMockPipelineVersions, + buildMockPipelineVersion, +} from '~/__mocks__/mockPipelineVersionsProxy'; +import { mockPipelinesProxy } from '~/__mocks__/mockPipelinesProxy'; + +const mockPipelineVersions = buildMockPipelineVersions([ + buildMockPipelineVersion({ id: '1', name: 'version-1' }), + buildMockPipelineVersion({ id: '2', name: 'version-2' }), + buildMockPipelineVersion({ id: '3', name: 'version-3' }), +]); + +export default { + component: PipelineDetails, + parameters: { + reactRouter: { + location: { + pathParams: { + namespace: 'test-project', + pipelineVersionId: mockPipelineVersions.versions[0].id, + }, + }, + routing: [ + { + path: '/pipelines/:namespace/pipeline/view/:pipelineVersionId', + }, + ], + }, + msw: { + handlers: [ + rest.get('/api/k8s/apis/project.openshift.io/v1/projects', (req, res, ctx) => + res(ctx.json(mockK8sResourceList([mockProjectK8sResource({})]))), + ), + rest.get( + '/api/k8s/apis/datasciencepipelinesapplications.opendatahub.io/v1alpha1/namespaces/test-project/datasciencepipelinesapplications/pipelines-definition', + (_req, res, ctx) => res(ctx.json(mockDataSciencePipelineApplicationK8sResource({}))), + ), + rest.get( + '/api/k8s/apis/route.openshift.io/v1/namespaces/test-project/routes/ds-pipeline-pipelines-definition', + (_req, res, ctx) => + res( + ctx.json(mockRouteK8sResource({ notebookName: 'ds-pipeline-pipelines-definition' })), + ), + ), + rest.get( + '/api/k8s/api/v1/namespaces/test-project/secrets/ds-pipeline-config', + (_req, res, ctx) => res(ctx.json(mockSecretK8sResource({ name: 'ds-pipeline-config' }))), + ), + rest.get( + '/api/k8s/api/v1/namespaces/test-project/secrets/aws-connection-testdb', + (_req, res, ctx) => + res(ctx.json(mockSecretK8sResource({ name: 'aws-connection-testdb' }))), + ), + rest.post('/api/proxy/apis/v1beta1/pipelines', (req, res, ctx) => + res( + ctx.json({ + ...mockPipelinesProxy, + pipelines: [ + ...mockPipelinesProxy.pipelines, + { + ...mockPipelinesProxy.pipelines[1], + id: mockPipelineVersions.versions[0].resource_references[0].key.id, + }, + ], + }), + ), + ), + rest.post('/api/proxy/apis/v1beta1/pipelines/:pipeline_id', (req, res, ctx) => + res( + ctx.json({ + ...mockPipelinesProxy.pipelines[1], + id: mockPipelineVersions.versions[0].resource_references[0].key.id, + }), + ), + ), + rest.post('/api/proxy/apis/v1beta1/pipeline_versions', (_req, res, ctx) => + res(ctx.json(mockPipelineVersions)), + ), + rest.post('/api/proxy/apis/v1beta1/pipeline_versions/:version_id', (req, res, ctx) => { + const existingMockPipelineVersion = mockPipelineVersions.versions.find( + (version) => version.id === req.params.version_id, + ); + + const newMockPipelineVersion = buildMockPipelineVersion({ + name: 'new-upload-version', + id: 'new-version-id', + }); + + return res(ctx.json(existingMockPipelineVersion || newMockPipelineVersion)); + }), + rest.post( + '/api/proxy/apis/v1beta1/pipeline_versions/:version_id/templates', + (_req, res, ctx) => res(ctx.json(mockPipelineVersionTemplateResource)), + ), + ], + }, + }, +}; + +export const Default: StoryObj = { + render: () => ( + + + + ), +}; diff --git a/frontend/src/__tests__/integration/pages/pipelines/PipelineRunJobDetails.stories.tsx b/frontend/src/__tests__/integration/pages/pipelines/PipelineRunJobDetails.stories.tsx index 0bd2935528..9eb21f1321 100644 --- a/frontend/src/__tests__/integration/pages/pipelines/PipelineRunJobDetails.stories.tsx +++ b/frontend/src/__tests__/integration/pages/pipelines/PipelineRunJobDetails.stories.tsx @@ -18,8 +18,14 @@ export default { component: PipelineRunJobDetails, parameters: { reactRouter: { - routePath: '/pipelineRuns/:namespace/pipelineRunJob/view/:pipelineRunJobId/*', - routeParams: { namespace: 'test-project', pipelineRunJobId: 'test-pipeline-run-job' }, + location: { + pathParams: { namespace: 'test-project', pipelineRunJobId: 'test-pipeline-run-job' }, + }, + routing: [ + { + path: '/pipelineRuns/:namespace/pipelineRunJob/view/:pipelineRunJobId/*', + }, + ], }, msw: { handlers: [ diff --git a/frontend/src/__tests__/integration/pages/projects/ProjectDetails.stories.tsx b/frontend/src/__tests__/integration/pages/projects/ProjectDetails.stories.tsx index e5361d236d..27945c1ac9 100644 --- a/frontend/src/__tests__/integration/pages/projects/ProjectDetails.stories.tsx +++ b/frontend/src/__tests__/integration/pages/projects/ProjectDetails.stories.tsx @@ -184,8 +184,14 @@ export default { component: ProjectDetails, parameters: { reactRouter: { - routePath: '/projects/:namespace/*', - routeParams: { namespace: 'test-project' }, + location: { + pathParams: { namespace: 'test-project' }, + }, + routing: [ + { + path: '/projects/:namespace/*', + }, + ], }, msw: { handlers: handlers(false), diff --git a/frontend/src/api/pipelines/callTypes.ts b/frontend/src/api/pipelines/callTypes.ts index 18210e2e4d..cf13bba03a 100644 --- a/frontend/src/api/pipelines/callTypes.ts +++ b/frontend/src/api/pipelines/callTypes.ts @@ -5,7 +5,7 @@ import { ListPipelineRunJobs, ListPipelineRunsByPipeline, ListPipelines, - ListPipelineTemplates, + ListPipelineVersionTemplates, UploadPipeline, UpdatePipelineRunJob, GetPipelineRun, @@ -47,7 +47,7 @@ export type ListPipelinesAPI = KubeflowAPICall; export type ListPipelinesRunAPI = KubeflowAPICall; export type ListPipelinesRunJobAPI = KubeflowAPICall; export type ListPipelineRunsByPipelineAPI = KubeflowAPICall; -export type ListPipelineTemplatesAPI = KubeflowAPICall; +export type ListPipelineVersionTemplatesAPI = KubeflowAPICall; export type ListPipelineVersionsByPipelineAPI = KubeflowAPICall; export type StopPipelineRunAPI = KubeflowAPICall; export type UpdatePipelineRunJobAPI = KubeflowAPICall; diff --git a/frontend/src/api/pipelines/custom.ts b/frontend/src/api/pipelines/custom.ts index bdfb5d7cef..4434edf20a 100644 --- a/frontend/src/api/pipelines/custom.ts +++ b/frontend/src/api/pipelines/custom.ts @@ -8,7 +8,7 @@ import { ListPipelinesRunAPI, ListPipelinesRunJobAPI, ListPipelinesAPI, - ListPipelineTemplatesAPI, + ListPipelineVersionTemplatesAPI, UploadPipelineAPI, UpdatePipelineRunJobAPI, GetPipelineRunAPI, @@ -129,10 +129,16 @@ export const listPipelineRunsByPipeline: ListPipelineRunsByPipelineAPI = ), ); -export const listPipelineTemplates: ListPipelineTemplatesAPI = (hostPath) => (opts, pipelineId) => - handlePipelineFailures( - proxyGET(hostPath, `/apis/v1beta1/pipelines/${pipelineId}/templates`, {}, opts), - ); +export const listPipelineVersionTemplates: ListPipelineVersionTemplatesAPI = + (hostPath) => (opts, pipelineVersionId) => + handlePipelineFailures( + proxyGET( + hostPath, + `/apis/v1beta1/pipeline_versions/${pipelineVersionId}/templates`, + {}, + opts, + ), + ); export const listPipelineVersionsByPipeline: ListPipelineVersionsByPipelineAPI = (hostPath) => (opts, pipelineId, params) => diff --git a/frontend/src/concepts/pipelines/apiHooks/usePipelineTemplate.ts b/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionTemplates.ts similarity index 71% rename from frontend/src/concepts/pipelines/apiHooks/usePipelineTemplate.ts rename to frontend/src/concepts/pipelines/apiHooks/usePipelineVersionTemplates.ts index b7dc3a1017..482d98e5ad 100644 --- a/frontend/src/concepts/pipelines/apiHooks/usePipelineTemplate.ts +++ b/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionTemplates.ts @@ -4,16 +4,16 @@ import useFetchState, { FetchStateCallbackPromise, NotReadyError } from '~/utili import { usePipelinesAPI } from '~/concepts/pipelines/context'; import { PipelineRunKind } from '~/k8sTypes'; -const usePipelineTemplate = (pipelineId?: string) => { +const usePipelineVersionTemplates = (pipelineVersionId?: string) => { const { api } = usePipelinesAPI(); const call = React.useCallback>( (opts) => { - if (!pipelineId) { - return Promise.reject(new NotReadyError('No pipeline id')); + if (!pipelineVersionId) { + return Promise.reject(new NotReadyError('No pipeline version id')); } - return api.listPipelineTemplate(opts, pipelineId).then(({ template }) => { + return api.listPipelineVersionTemplates(opts, pipelineVersionId).then(({ template }) => { let pipelineRun: PipelineRunKind; try { pipelineRun = YAML.parse(template); @@ -26,10 +26,10 @@ const usePipelineTemplate = (pipelineId?: string) => { return pipelineRun; }); }, - [api, pipelineId], + [api, pipelineVersionId], ); return useFetchState(call, null); }; -export default usePipelineTemplate; +export default usePipelineVersionTemplates; diff --git a/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionsForPipeline.ts b/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionsForPipeline.ts index 4e2e59a2da..cc9c8bd6de 100644 --- a/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionsForPipeline.ts +++ b/frontend/src/concepts/pipelines/apiHooks/usePipelineVersionsForPipeline.ts @@ -7,8 +7,8 @@ import { NotReadyError } from '~/utilities/useFetchState'; const usePipelineVersionsForPipeline = ( pipelineId?: string, - options?: PipelineOptions, - refreshRate?: number, + options: PipelineOptions = {}, + refreshRate = 0, ) => { const { api } = usePipelinesAPI(); diff --git a/frontend/src/concepts/pipelines/content/ViewPipelineServerModal.tsx b/frontend/src/concepts/pipelines/content/ViewPipelineServerModal.tsx index 75e5ff48d2..877b89bf1b 100644 --- a/frontend/src/concepts/pipelines/content/ViewPipelineServerModal.tsx +++ b/frontend/src/concepts/pipelines/content/ViewPipelineServerModal.tsx @@ -112,7 +112,7 @@ const ViewPipelineServerModal: React.FC = ({ )} - {!!pipelineNamespaceCR?.spec.database && + {!!pipelineNamespaceCR?.spec?.database && !!pipelineNamespaceCR.spec.database.externalDB && !!databaseSecret[EXTERNAL_DATABASE_SECRET.KEY] && ( <> diff --git a/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx b/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx index c2092cae05..9d274e2ec8 100644 --- a/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/RunPage.tsx @@ -21,7 +21,11 @@ const RunPage: React.FC = ({ cloneRun, contextPath }) => { const { namespace } = usePipelinesAPI(); const location = useLocation(); - const [formData, setFormDataValue] = useRunFormData(cloneRun, location.state?.lastPipeline); + const [formData, setFormDataValue] = useRunFormData( + cloneRun, + location.state?.lastPipeline, + location.state?.lastVersion, + ); return ( <> diff --git a/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineVersionSection.tsx b/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineVersionSection.tsx index 1c5dd5b3b3..fe7ca09f11 100644 --- a/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineVersionSection.tsx +++ b/frontend/src/concepts/pipelines/content/createRun/contentSections/PipelineVersionSection.tsx @@ -25,7 +25,7 @@ const PipelineVersionSection: React.FC = ({ onChange, }) => { const pipelineId = pipeline?.id; - const [{ items: versions }, loaded] = usePipelineVersionsForPipeline(pipelineId, {}, 0); + const [{ items: versions }, loaded] = usePipelineVersionsForPipeline(pipelineId); React.useEffect(() => { onLoaded(loaded); diff --git a/frontend/src/concepts/pipelines/content/import/PipelineFileUpload.tsx b/frontend/src/concepts/pipelines/content/import/PipelineFileUpload.tsx index 1ab6157a12..bddccd1828 100644 --- a/frontend/src/concepts/pipelines/content/import/PipelineFileUpload.tsx +++ b/frontend/src/concepts/pipelines/content/import/PipelineFileUpload.tsx @@ -35,6 +35,7 @@ const PipelineFileUpload: React.FC = ({ fileContents, o isRequired allowEditingUploadedText={false} browseButtonText="Upload" + data-testid="pipeline-file-upload" /> ); }; diff --git a/frontend/src/concepts/pipelines/content/import/PipelineVersionImportModal.tsx b/frontend/src/concepts/pipelines/content/import/PipelineVersionImportModal.tsx index e25de06b88..39f10243b5 100644 --- a/frontend/src/concepts/pipelines/content/import/PipelineVersionImportModal.tsx +++ b/frontend/src/concepts/pipelines/content/import/PipelineVersionImportModal.tsx @@ -69,6 +69,7 @@ const PipelineVersionImportModal: React.FC = ({ setError(e); }); }} + data-testid="upload-version-submit-button" > Upload , @@ -77,6 +78,7 @@ const PipelineVersionImportModal: React.FC = ({ , ]} variant="medium" + data-testid="upload-version-modal" >
diff --git a/frontend/src/concepts/pipelines/content/pipelineSelector/PipelineSelector.tsx b/frontend/src/concepts/pipelines/content/pipelineSelector/PipelineSelector.tsx index 2b75546dea..9e860d14c9 100644 --- a/frontend/src/concepts/pipelines/content/pipelineSelector/PipelineSelector.tsx +++ b/frontend/src/concepts/pipelines/content/pipelineSelector/PipelineSelector.tsx @@ -31,6 +31,7 @@ type PipelineSelectorProps = { isLoading: boolean; isDisabled?: boolean; maxWidth?: string | number; + minWidth?: string | number; }; const PipelineSelector = ({ @@ -44,6 +45,7 @@ const PipelineSelector = ({ placeHolder, searchHelperText, maxWidth, + minWidth = '300px', }: PipelineSelectorProps) => { const [isOpen, setOpen] = React.useState(false); const [search, setSearch] = React.useState(''); @@ -135,12 +137,13 @@ const PipelineSelector = ({ toggle={ setOpen(!isOpen)} isExpanded={isOpen} isDisabled={isDisabled || data.length === 0} isFullWidth + data-testid="pipeline-version-toggle-button" > {name || placeHolder} diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx index 8ea3f9d4f4..f746e821f7 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetails.tsx @@ -6,21 +6,28 @@ import { Drawer, DrawerContent, DrawerContentBody, + Flex, + FlexItem, Tab, TabContent, Tabs, TabTitleText, } from '@patternfly/react-core'; import ApplicationsPage from '~/pages/ApplicationsPage'; -import usePipelineTemplate from '~/concepts/pipelines/apiHooks/usePipelineTemplate'; +import usePipelineTemplate from '~/concepts/pipelines/apiHooks/usePipelineVersionTemplates'; import { PipelineTopology, usePipelineTaskTopology } from '~/concepts/pipelines/topology'; -import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; import MarkdownView from '~/components/MarkdownView'; import PipelineDetailsYAML from '~/concepts/pipelines/content/pipelinesDetails/PipelineDetailsYAML'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; import PipelineTopologyEmpty from '~/concepts/pipelines/content/pipelinesDetails/PipelineTopologyEmpty'; import { PipelineCoreDetailsPageComponent } from '~/concepts/pipelines/content/types'; import DeletePipelineCoreResourceModal from '~/concepts/pipelines/content/DeletePipelineCoreResourceModal'; +import usePipelineVersionsForPipeline from '~/concepts/pipelines/apiHooks/usePipelineVersionsForPipeline'; +import PipelineSelector from '~/concepts/pipelines/content/pipelineSelector/PipelineSelector'; +import { pipelineVersionSelectorColumns } from '~/concepts/pipelines/content/pipelineSelector/columns'; +import usePipelineVersionById from '~/concepts/pipelines/apiHooks/usePipelineVersionById'; +import usePipelineById from '~/concepts/pipelines/apiHooks/usePipelineById'; +import { RelationshipKF, ResourceTypeKF } from '~/concepts/pipelines/kfTypes'; import PipelineDetailsActions from './PipelineDetailsActions'; import SelectedTaskDrawerContent from './SelectedTaskDrawerContent'; import PipelineNotFound from './PipelineNotFound'; @@ -31,32 +38,48 @@ enum PipelineDetailsTab { } const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) => { - const { pipelineId } = useParams(); + const { pipelineVersionId } = useParams(); const navigate = useNavigate(); - const { namespace } = usePipelinesAPI(); - const [pipeline, pipelineLoad, pipelineLoadError] = usePipelineById(pipelineId); + const [isDeleting, setDeleting] = React.useState(false); - const [pipelineRun, pipelineTemplateLoaded, templateLoadError] = usePipelineTemplate(pipelineId); const [activeTabKey, setActiveTabKey] = React.useState(PipelineDetailsTab.GRAPH); const [selectedId, setSelectedId] = React.useState(null); - const { taskMap, nodes } = usePipelineTaskTopology(pipelineRun); - if (pipelineLoadError) { + + const { namespace } = usePipelinesAPI(); + const [pipelineVersion, isPipelineVersionLoaded, pipelineVersionLoadError] = + usePipelineVersionById(pipelineVersionId); + + const pipelineId = pipelineVersion?.resource_references.find( + (ref) => ref.relationship === RelationshipKF.OWNER && ref.key.type === ResourceTypeKF.PIPELINE, + )?.key.id; + + const [pipeline, isPipelineLoaded, pipelineLoadError] = usePipelineById(pipelineId); + const [{ items: versions }, versionsLoaded] = usePipelineVersionsForPipeline(pipelineId); + const [pipelineVersionRun, isPipelineVersionTemplateLoaded, templateLoadError] = + usePipelineTemplate(pipelineVersionId); + const { taskMap, nodes } = usePipelineTaskTopology(pipelineVersionRun); + const isLoaded = isPipelineVersionLoaded && isPipelineLoaded && isPipelineVersionTemplateLoaded; + + if (pipelineVersionLoadError || pipelineLoadError) { + const errorText = 'Pipeline version not found'; + return ( {breadcrumbPath} - {'Pipeline not found'} + {errorText} } - title={'Pipeline not found'} + title={errorText} empty={false} - loaded={!pipelineLoad} + loaded={!isLoaded} > ); } + return ( <> @@ -73,20 +96,53 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = breadcrumb={ {breadcrumbPath} - {pipeline?.name || 'Loading...'} + {pipeline?.name || 'Loading...'} + {pipelineVersion?.name || 'Loading...'} } - title={pipeline?.name || 'Loading...'} - description={ - pipeline ? : '' - } + title={pipelineVersion?.name || 'Loading...'} + {...(pipelineVersion && { + description: ( + + ), + })} empty={false} - loaded={pipelineLoad && pipelineTemplateLoaded} + loaded={isLoaded} loadError={templateLoadError} headerAction={ - pipelineLoad && - pipelineTemplateLoaded && ( - setDeleting(true)} pipeline={pipeline} /> + isPipelineVersionLoaded && ( + + + navigate(`/pipelines/${namespace}/pipeline/view/${id}`)} + isDisabled={!pipelineId} + isLoading={!!pipelineId && !versionsLoaded} + placeHolder={ + pipelineId && versions.length === 0 + ? 'No versions available' + : 'Select a pipeline version' + } + searchHelperText={`Type a name to search your ${versions.length} versions.`} + /> + + + + {isLoaded && ( + setDeleting(true)} + pipeline={pipeline} + pipelineVersion={pipelineVersion} + /> + )} + + ) } > @@ -120,6 +176,7 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = activeKey={activeTabKey} hidden={PipelineDetailsTab.GRAPH !== activeTabKey} style={{ height: '100%' }} + data-testid="pipeline-version-topology-content" > {nodes.length === 0 ? ( @@ -146,8 +203,8 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = style={{ height: '100%' }} > @@ -157,7 +214,7 @@ const PipelineDetails: PipelineCoreDetailsPageComponent = ({ breadcrumbPath }) = { navigate(`/pipelines/${namespace}`); }} diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx index b33cd7eb1e..d0c54d16fe 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineDetailsActions.tsx @@ -1,55 +1,81 @@ import * as React from 'react'; +import { useNavigate } from 'react-router-dom'; + import { Dropdown, DropdownItem, DropdownSeparator, DropdownToggle, } from '@patternfly/react-core/deprecated'; -import { useNavigate } from 'react-router-dom'; + import { usePipelinesAPI } from '~/concepts/pipelines/context'; -import { PipelineKF } from '~/concepts/pipelines/kfTypes'; +import PipelineVersionImportModal from '~/concepts/pipelines/content/import/PipelineVersionImportModal'; +import { PipelineKF, PipelineVersionKF } from '~/concepts/pipelines/kfTypes'; type PipelineDetailsActionsProps = { onDelete: () => void; pipeline: PipelineKF | null; + pipelineVersion: PipelineVersionKF | null; }; -const PipelineDetailsActions: React.FC = ({ onDelete, pipeline }) => { +const PipelineDetailsActions: React.FC = ({ + onDelete, + pipeline, + pipelineVersion, +}) => { const navigate = useNavigate(); const { namespace } = usePipelinesAPI(); const [open, setOpen] = React.useState(false); + const [isVersionImportModalOpen, setIsVersionImportModalOpen] = React.useState(false); return ( - setOpen(false)} - toggle={ - setOpen(!open)}> - Actions - - } - isOpen={open} - position="right" - dropdownItems={[ - // TODO: Handle path - - navigate(`/pipelineRuns/${namespace}/pipelineRun/create`, { - state: { lastPipeline: pipeline }, - }) + <> + setOpen(false)} + toggle={ + setOpen(!open)}> + Actions + + } + isOpen={open} + position="right" + dropdownItems={[ + setIsVersionImportModalOpen(true)}> + Upload new version + , + , + + navigate(`/pipelineRuns/${namespace}/pipelineRun/create`, { + state: { lastPipeline: pipeline, lastVersion: pipelineVersion }, + }) + } + > + Create run + , + navigate(`/pipelineRuns/${namespace}`)}> + View runs + , + , + onDelete()}> + Delete pipeline + , + ]} + /> + + { + setIsVersionImportModalOpen(false); + + if (pipelineVersion) { + navigate(`/pipelines/${namespace}/pipeline/view/${pipelineVersion.id}`); } - > - Create run - , - navigate(`/pipelineRuns/${namespace}`)}> - View runs - , - , - onDelete()}> - Delete pipeline - , - ]} - /> + }} + /> + ); }; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx index ee13aa8ab9..c8f64b530c 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipeline/PipelineNotFound.tsx @@ -17,7 +17,7 @@ const PipelineNotFound: React.FC = () => { return ( } headingLevel="h4" /> diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx index 97fda624a3..4e64854808 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDetailsActions.tsx @@ -10,12 +10,12 @@ import { usePipelinesAPI } from '~/concepts/pipelines/context'; import useNotification from '~/utilities/useNotification'; import { PipelineRunKF, PipelineRunStatusesKF } from '~/concepts/pipelines/kfTypes'; -type PipelineDetailsActionsProps = { +type PipelineRunDetailsActionsProps = { run?: PipelineRunKF; onDelete: () => void; }; -const PipelineRunDetailsActions: React.FC = ({ onDelete, run }) => { +const PipelineRunDetailsActions: React.FC = ({ onDelete, run }) => { const navigate = useNavigate(); const { namespace, api } = usePipelinesAPI(); const notification = useNotification(); diff --git a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx index 971479ff26..1d29113d97 100644 --- a/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx +++ b/frontend/src/concepts/pipelines/content/tables/pipeline/PipelinesTableRow.tsx @@ -57,7 +57,13 @@ const PipelinesTableRow: React.FC = ({ /> {pipeline.name}} + title={ + + {pipeline.name} + + } description={pipeline.description} descriptionAsMarkdown /> diff --git a/frontend/src/concepts/pipelines/context/useAPIState.ts b/frontend/src/concepts/pipelines/context/useAPIState.ts index ccf2fcb5ce..7bc2c15186 100644 --- a/frontend/src/concepts/pipelines/context/useAPIState.ts +++ b/frontend/src/concepts/pipelines/context/useAPIState.ts @@ -17,7 +17,7 @@ import { listPipelineRuns, listPipelineRunsByPipeline, listPipelines, - listPipelineTemplates, + listPipelineVersionTemplates, listPipelineVersionsByPipeline, stopPipelineRun, updatePipelineRunJob, @@ -73,7 +73,7 @@ const useAPIState = ( listPipelineRuns: listPipelineRuns(path), listPipelineRunJobs: listPipelineRunJobs(path), listPipelineRunsByPipeline: listPipelineRunsByPipeline(path), - listPipelineTemplate: listPipelineTemplates(path), + listPipelineVersionTemplates: listPipelineVersionTemplates(path), listPipelineVersionsByPipeline: listPipelineVersionsByPipeline(path), stopPipelineRun: stopPipelineRun(path), updatePipelineRunJob: updatePipelineRunJob(path), diff --git a/frontend/src/concepts/pipelines/kfTypes.ts b/frontend/src/concepts/pipelines/kfTypes.ts index 719f7d6e7b..7810722aaf 100644 --- a/frontend/src/concepts/pipelines/kfTypes.ts +++ b/frontend/src/concepts/pipelines/kfTypes.ts @@ -233,7 +233,7 @@ export type ListPipelineRunsResourceKF = PipelineKFCallCommon<{ export type ListPipelineRunJobsResourceKF = PipelineKFCallCommon<{ jobs: PipelineRunJobKF[]; }>; -export type ListPipelineTemplateResourceKF = { +export type ListPipelineVersionTemplateResourceKF = { /** YAML template of a PipelineRunKind */ template: string; }; diff --git a/frontend/src/concepts/pipelines/types.ts b/frontend/src/concepts/pipelines/types.ts index ba483c6a62..06dd83c92a 100644 --- a/frontend/src/concepts/pipelines/types.ts +++ b/frontend/src/concepts/pipelines/types.ts @@ -3,7 +3,7 @@ import { ListPipelineRunsResourceKF, ListPipelineRunJobsResourceKF, ListPipelinesResponseKF, - ListPipelineTemplateResourceKF, + ListPipelineVersionTemplateResourceKF, PipelineKF, PipelineRunResourceKF, ListExperimentsResponseKF, @@ -94,10 +94,10 @@ export type ListPipelineRunsByPipeline = ( pipelineId: string, limit?: number, ) => Promise; -export type ListPipelineTemplates = ( +export type ListPipelineVersionTemplates = ( opts: K8sAPIOptions, - pipelineId: string, -) => Promise; + pipelineVersionId: string, +) => Promise; export type ListPipelineVersionsByPipeline = ( opts: K8sAPIOptions, pipelineId: string, @@ -141,7 +141,7 @@ export type PipelineAPIs = { listPipelineRuns: ListPipelineRuns; listPipelineRunJobs: ListPipelineRunJobs; listPipelineRunsByPipeline: ListPipelineRunsByPipeline; - listPipelineTemplate: ListPipelineTemplates; + listPipelineVersionTemplates: ListPipelineVersionTemplates; listPipelineVersionsByPipeline: ListPipelineVersionsByPipeline; stopPipelineRun: StopPipelineRun; updatePipelineRunJob: UpdatePipelineRunJob; diff --git a/frontend/src/pages/ApplicationsPage.tsx b/frontend/src/pages/ApplicationsPage.tsx index 5c070e26a3..f573502f12 100644 --- a/frontend/src/pages/ApplicationsPage.tsx +++ b/frontend/src/pages/ApplicationsPage.tsx @@ -10,12 +10,11 @@ import { EmptyStateIcon, Spinner, EmptyStateBody, - Split, - SplitItem, PageBreadcrumb, StackItem, Stack, EmptyStateHeader, + Flex, } from '@patternfly/react-core'; type ApplicationsPageProps = { @@ -57,16 +56,21 @@ const ApplicationsPage: React.FC = ({ - - + + <> - {title} + + {title} + {jobReferenceName} {description && {description}} - - {headerAction && {headerAction}} - + + {headerAction} + {headerContent && {headerContent}} diff --git a/frontend/src/pages/BYONImages/BYONImageModal/ManageBYONImageModal.tsx b/frontend/src/pages/BYONImages/BYONImageModal/ManageBYONImageModal.tsx index 01d6e259b5..99eb1ca2cb 100644 --- a/frontend/src/pages/BYONImages/BYONImageModal/ManageBYONImageModal.tsx +++ b/frontend/src/pages/BYONImages/BYONImageModal/ManageBYONImageModal.tsx @@ -126,6 +126,7 @@ export const ManageBYONImageModal: React.FC = ({ isCancelDisabled={isEditing} /> } + data-testid="notebook-image-modal" > { diff --git a/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx b/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx index 3eaee0ba09..0ef8c693ee 100644 --- a/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx +++ b/frontend/src/pages/pipelines/GlobalPipelineRunsRoutes.tsx @@ -28,7 +28,7 @@ const GlobalPipelineRunRoutes: React.FC = () => ( > } /> ( > } /> { /> } />