feat(ws): add filter to workspaces table (#157)
* Filter workspaces table Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com> * feat(ws) add filter to workspaces table Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com> --------- Signed-off-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com> Co-authored-by: Elay Aharoni (EXT-Nokia) <elay.aharoni.ext@nokia.com>
This commit is contained in:
parent
9a91f66972
commit
2d1298913a
|
@ -36,7 +36,7 @@
|
|||
"core-js": "^3.39.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"cypress": "^13.15.0",
|
||||
"cypress": "^13.16.1",
|
||||
"cypress-axe": "^1.5.0",
|
||||
"cypress-high-resolution": "^1.0.0",
|
||||
"cypress-mochawesome-reporter": "^3.8.2",
|
||||
|
@ -227,21 +227,6 @@
|
|||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz",
|
||||
"integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
|
||||
|
@ -298,15 +283,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-regexp-features-plugin": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz",
|
||||
"integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz",
|
||||
"integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.25.9",
|
||||
"regexpu-core": "^6.1.1",
|
||||
"regexpu-core": "^6.2.0",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -317,9 +302,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/helper-define-polyfill-provider": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz",
|
||||
"integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz",
|
||||
"integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
@ -462,21 +447,6 @@
|
|||
"@babel/core": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-simple-access": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz",
|
||||
"integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/traverse": "^7.25.9",
|
||||
"@babel/types": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz",
|
||||
|
@ -1152,14 +1122,13 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz",
|
||||
"integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz",
|
||||
"integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9",
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
|
@ -1310,16 +1279,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-modules-commonjs": {
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz",
|
||||
"integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==",
|
||||
"version": "7.26.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz",
|
||||
"integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.25.9",
|
||||
"@babel/helper-plugin-utils": "^7.25.9",
|
||||
"@babel/helper-simple-access": "^7.25.9"
|
||||
"@babel/helper-module-transforms": "^7.26.0",
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
@ -3380,6 +3348,18 @@
|
|||
"integrity": "sha512-xd0ynDkiIW2rp8jz4TNvR4Dyaw9kSMkZdsuYcLlFXCVmvX//Mnl4rhBnid/2j2TaqK0NbkyTTPnPY/BU7SfLVQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@pkgjs/parseargs": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@pkgr/core": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz",
|
||||
|
@ -3960,24 +3940,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||
"integrity": "sha512-GE44+DNEyxxh2Kc6ro/VkIj+9ma0pO0bwv9+uHSyBrikYOHr8zYcdPvnBOp1aw8s+CjRvuSx7CyWqRrNFQ59mA==",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
"@types/json-schema": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "0.0.51",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz",
|
||||
"integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/express": {
|
||||
"version": "4.17.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
|
||||
|
@ -4098,7 +4060,7 @@
|
|||
"version": "7.0.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
|
||||
"devOptional": true
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/json5": {
|
||||
"version": "0.0.29",
|
||||
|
@ -4122,14 +4084,6 @@
|
|||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
|
||||
|
@ -5370,52 +5324,16 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-macros": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||
"integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"cosmiconfig": "^7.0.0",
|
||||
"resolve": "^1.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10",
|
||||
"npm": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-macros/node_modules/resolve": {
|
||||
"version": "1.22.2",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
|
||||
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-core-module": "^2.11.0",
|
||||
"path-parse": "^1.0.7",
|
||||
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"resolve": "bin/resolve"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs2": {
|
||||
"version": "0.4.11",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz",
|
||||
"integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==",
|
||||
"version": "0.4.12",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz",
|
||||
"integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.6",
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.2",
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.3",
|
||||
"semver": "^6.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
@ -5438,14 +5356,14 @@
|
|||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-regenerator": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz",
|
||||
"integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==",
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz",
|
||||
"integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.2"
|
||||
"@babel/helper-define-polyfill-provider": "^0.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
|
||||
|
@ -6878,24 +6796,6 @@
|
|||
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
|
||||
"integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/parse-json": "^4.0.0",
|
||||
"import-fresh": "^3.2.1",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0",
|
||||
"yaml": "^1.10.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/create-jest": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
|
||||
|
@ -7361,9 +7261,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "13.15.2",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz",
|
||||
"integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==",
|
||||
"version": "13.16.1",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz",
|
||||
"integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
|
@ -10333,20 +10233,6 @@
|
|||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsu": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fsu/-/fsu-1.1.1.tgz",
|
||||
|
@ -14801,9 +14687,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/mocha": {
|
||||
"version": "10.8.2",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz",
|
||||
"integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==",
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mocha/-/mocha-11.0.1.tgz",
|
||||
"integrity": "sha512-+3GkODfsDG71KSCQhc4IekSW+ItCK/kiez1Z28ksWvYhKXV/syxMlerR/sC7whDp7IyreZ4YxceMLdTs5hQE8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
@ -14815,7 +14701,7 @@
|
|||
"diff": "^5.2.0",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"find-up": "^5.0.0",
|
||||
"glob": "^8.1.0",
|
||||
"glob": "^10.4.5",
|
||||
"he": "^1.2.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"log-symbols": "^4.1.0",
|
||||
|
@ -14834,7 +14720,7 @@
|
|||
"mocha": "bin/mocha.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/brace-expansion": {
|
||||
|
@ -14894,22 +14780,39 @@
|
|||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/glob": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
|
||||
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^5.0.1",
|
||||
"once": "^1.3.0"
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^3.1.2",
|
||||
"minimatch": "^9.0.4",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^1.11.1"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/glob/node_modules/minimatch": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
|
@ -14926,6 +14829,23 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/jackspeak": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@pkgjs/parseargs": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/locate-path": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
|
@ -14943,6 +14863,14 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/lru-cache": {
|
||||
"version": "10.4.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/mocha/node_modules/minimatch": {
|
||||
"version": "5.1.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||
|
@ -14991,6 +14919,24 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/path-scurry": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^10.2.0",
|
||||
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/mocha/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
|
@ -17653,9 +17599,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/regexpu-core": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz",
|
||||
"integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==",
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz",
|
||||
"integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
@ -17663,7 +17609,7 @@
|
|||
"regenerate": "^1.4.2",
|
||||
"regenerate-unicode-properties": "^10.2.0",
|
||||
"regjsgen": "^0.8.0",
|
||||
"regjsparser": "^0.11.0",
|
||||
"regjsparser": "^0.12.0",
|
||||
"unicode-match-property-ecmascript": "^2.0.0",
|
||||
"unicode-match-property-value-ecmascript": "^2.1.0"
|
||||
},
|
||||
|
@ -17704,9 +17650,9 @@
|
|||
"peer": true
|
||||
},
|
||||
"node_modules/regjsparser": {
|
||||
"version": "0.11.2",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz",
|
||||
"integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==",
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz",
|
||||
"integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"peer": true,
|
||||
|
@ -21419,17 +21365,6 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
"core-js": "^3.39.0",
|
||||
"css-loader": "^6.11.0",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"cypress": "^13.15.0",
|
||||
"cypress": "^13.16.1",
|
||||
"cypress-axe": "^1.5.0",
|
||||
"cypress-high-resolution": "^1.0.0",
|
||||
"cypress-mochawesome-reporter": "^3.8.2",
|
||||
|
|
|
@ -48,7 +48,7 @@ export default defineConfig({
|
|||
},
|
||||
defaultCommandTimeout: 10000,
|
||||
e2e: {
|
||||
baseUrl: BASE_URL,
|
||||
baseUrl: env.CY_MOCK ? BASE_URL : 'http://localhost:9000',
|
||||
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
|
||||
experimentalInteractiveRunEvents: true,
|
||||
setupNodeEvents(on, config) {
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import { home } from '~/__tests__/cypress/cypress/pages/home';
|
||||
|
||||
const useFilter = (filterName: string, searchValue: string) => {
|
||||
cy.get("[id$='filter-workspaces-dropdown']").click();
|
||||
cy.get(`[id$='filter-workspaces-dropdown-${filterName}']`).click();
|
||||
cy.get("[id$='filter-workspaces-search-input']").type(searchValue);
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").contains(filterName);
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").contains(searchValue);
|
||||
};
|
||||
|
||||
describe('Application', () => {
|
||||
it('filter rows with single filter', () => {
|
||||
home.visit();
|
||||
useFilter('Name', 'My');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2);
|
||||
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
|
||||
cy.get("[id$='workspaces-table-row-2']").contains('My Other Jupyter Notebook');
|
||||
});
|
||||
|
||||
it('filter rows with multiple filters', () => {
|
||||
home.visit();
|
||||
useFilter('Name', 'My');
|
||||
useFilter('Pod Config', 'Small');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
|
||||
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
|
||||
});
|
||||
|
||||
it('filter rows with multiple filters and remove one', () => {
|
||||
home.visit();
|
||||
useFilter('Name', 'My');
|
||||
useFilter('Pod Config', 'Small');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
|
||||
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
|
||||
cy.get("[class$='pf-v6-c-label-group__close']").eq(1).click();
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2);
|
||||
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
|
||||
cy.get("[id$='workspaces-table-row-2']").contains('My Other Jupyter Notebook');
|
||||
});
|
||||
|
||||
it('filter rows with multiple filters and remove all', () => {
|
||||
home.visit();
|
||||
useFilter('Name', 'My');
|
||||
useFilter('Pod Config', 'Small');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 1);
|
||||
cy.get("[id$='workspaces-table-row-1']").contains('My Jupyter Notebook');
|
||||
cy.get('*').contains('Clear all filters').click();
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Pod Config');
|
||||
cy.get("[class$='pf-v6-c-toolbar__group']").should('not.contain', 'Name');
|
||||
cy.get("[id$='workspaces-table-content']").find('tr').should('have.length', 2);
|
||||
});
|
||||
});
|
|
@ -4,27 +4,14 @@ import {
|
|||
DrawerContent,
|
||||
DrawerContentBody,
|
||||
PageSection,
|
||||
MenuToggle,
|
||||
TimestampTooltipVariant,
|
||||
Timestamp,
|
||||
Label,
|
||||
Title,
|
||||
Popper,
|
||||
MenuToggleElement,
|
||||
Menu,
|
||||
MenuContent,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarToggleGroup,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
ToolbarFilter,
|
||||
SearchInput,
|
||||
Button,
|
||||
PaginationVariant,
|
||||
Pagination,
|
||||
Button,
|
||||
Content,
|
||||
} from '@patternfly/react-core';
|
||||
import {
|
||||
Table,
|
||||
|
@ -37,15 +24,16 @@ import {
|
|||
ActionsColumn,
|
||||
IActions,
|
||||
} from '@patternfly/react-table';
|
||||
import { FilterIcon } from '@patternfly/react-icons';
|
||||
import { useState } from 'react';
|
||||
import { Workspace, WorkspacesColumnNames, WorkspaceState } from '~/shared/types';
|
||||
import { WorkspaceDetails } from '~/app/pages/Workspaces/Details/WorkspaceDetails';
|
||||
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
|
||||
import Filter, { FilteredColumn } from 'shared/components/Filter';
|
||||
import { formatRam } from 'shared/utilities/WorkspaceResources';
|
||||
|
||||
export const Workspaces: React.FunctionComponent = () => {
|
||||
/* Mocked workspaces, to be removed after fetching info from backend */
|
||||
const workspaces: Workspace[] = [
|
||||
const mockWorkspaces: Workspace[] = [
|
||||
{
|
||||
name: 'My Jupyter Notebook',
|
||||
namespace: 'namespace1',
|
||||
|
@ -156,7 +144,20 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
lastActivity: 'Last Activity',
|
||||
};
|
||||
|
||||
// Selected workspace
|
||||
const filterableColumns: WorkspacesColumnNames = {
|
||||
name: 'Name',
|
||||
kind: 'Kind',
|
||||
image: 'Image',
|
||||
podConfig: 'Pod Config',
|
||||
state: 'State',
|
||||
homeVol: 'Home Vol',
|
||||
lastActivity: 'Last Activity',
|
||||
};
|
||||
|
||||
// change when fetch workspaces is implemented
|
||||
const initialWorkspaces = mockWorkspaces;
|
||||
const [workspaces, setWorkspaces] = useState(initialWorkspaces);
|
||||
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
||||
const [selectedWorkspace, setSelectedWorkspace] = React.useState<Workspace | null>(null);
|
||||
|
||||
const selectWorkspace = React.useCallback(
|
||||
|
@ -169,17 +170,6 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
},
|
||||
[selectedWorkspace],
|
||||
);
|
||||
|
||||
// Filter
|
||||
const [activeAttributeMenu, setActiveAttributeMenu] = React.useState<string>(columnNames.name);
|
||||
const [isAttributeMenuOpen, setIsAttributeMenuOpen] = React.useState(false);
|
||||
const attributeToggleRef = React.useRef<MenuToggleElement | null>(null);
|
||||
const attributeMenuRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const attributeContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const [searchValue, setSearchValue] = React.useState('');
|
||||
const [expandedWorkspacesNames, setExpandedWorkspacesNames] = React.useState<string[]>([]);
|
||||
|
||||
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
|
||||
setExpandedWorkspacesNames((prevExpanded) => {
|
||||
const newExpandedWorkspacesNames = prevExpanded.filter((wsName) => wsName !== workspace.name);
|
||||
|
@ -191,173 +181,42 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
const isWorkspaceExpanded = (workspace: Workspace) =>
|
||||
expandedWorkspacesNames.includes(workspace.name);
|
||||
|
||||
const searchInput = (
|
||||
<SearchInput
|
||||
placeholder="Filter by name"
|
||||
value={searchValue}
|
||||
onChange={(_event, value) => onSearchChange(value)}
|
||||
onClear={() => onSearchChange('')}
|
||||
/>
|
||||
);
|
||||
|
||||
const handleAttributeMenuKeys = React.useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (!isAttributeMenuOpen) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
attributeMenuRef.current?.contains(event.target as Node) ||
|
||||
attributeToggleRef.current?.contains(event.target as Node)
|
||||
) {
|
||||
if (event.key === 'Escape' || event.key === 'Tab') {
|
||||
setIsAttributeMenuOpen(!isAttributeMenuOpen);
|
||||
attributeToggleRef.current?.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
[isAttributeMenuOpen, attributeMenuRef, attributeToggleRef],
|
||||
);
|
||||
|
||||
const handleAttributeClickOutside = React.useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (isAttributeMenuOpen && !attributeMenuRef.current?.contains(event.target as Node)) {
|
||||
setIsAttributeMenuOpen(false);
|
||||
}
|
||||
},
|
||||
[isAttributeMenuOpen, attributeMenuRef],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('keydown', handleAttributeMenuKeys);
|
||||
window.addEventListener('click', handleAttributeClickOutside);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleAttributeMenuKeys);
|
||||
window.removeEventListener('click', handleAttributeClickOutside);
|
||||
};
|
||||
}, [isAttributeMenuOpen, attributeMenuRef, handleAttributeMenuKeys, handleAttributeClickOutside]);
|
||||
|
||||
const onAttributeToggleClick = (ev: React.MouseEvent) => {
|
||||
ev.stopPropagation(); // Stop handleClickOutside from handling
|
||||
setTimeout(() => {
|
||||
if (attributeMenuRef.current) {
|
||||
const firstElement = attributeMenuRef.current.querySelector('li > button:not(:disabled)');
|
||||
if (firstElement) {
|
||||
(firstElement as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
setIsAttributeMenuOpen(!isAttributeMenuOpen);
|
||||
};
|
||||
|
||||
const attributeToggle = (
|
||||
<MenuToggle
|
||||
ref={attributeToggleRef}
|
||||
onClick={onAttributeToggleClick}
|
||||
isExpanded={isAttributeMenuOpen}
|
||||
icon={<FilterIcon />}
|
||||
>
|
||||
{activeAttributeMenu}
|
||||
</MenuToggle>
|
||||
);
|
||||
|
||||
const attributeMenu = (
|
||||
<Menu
|
||||
ref={attributeMenuRef}
|
||||
onSelect={(_ev, itemId) => {
|
||||
setActiveAttributeMenu(itemId?.toString());
|
||||
setIsAttributeMenuOpen(!isAttributeMenuOpen);
|
||||
}}
|
||||
>
|
||||
<MenuContent>
|
||||
<MenuList>
|
||||
<MenuItem itemId="Name">Name</MenuItem>
|
||||
<MenuItem itemId="Kind">Kind</MenuItem>
|
||||
<MenuItem itemId="Image">Image</MenuItem>
|
||||
<MenuItem itemId="Pod Config">Pod Config</MenuItem>
|
||||
<MenuItem itemId="State">State</MenuItem>
|
||||
<MenuItem itemId="Home Vol">Home Vol</MenuItem>
|
||||
<MenuItem itemId="Data Vol">Data Vol</MenuItem>
|
||||
<MenuItem itemId="Last Activity">Last Activity</MenuItem>
|
||||
</MenuList>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
);
|
||||
|
||||
const attributeDropdown = (
|
||||
<div ref={attributeContainerRef}>
|
||||
<Popper
|
||||
trigger={attributeToggle}
|
||||
triggerRef={attributeToggleRef}
|
||||
popper={attributeMenu}
|
||||
popperRef={attributeMenuRef}
|
||||
appendTo={attributeContainerRef.current || undefined}
|
||||
isVisible={isAttributeMenuOpen}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const toolbar = (
|
||||
<Toolbar
|
||||
id="attribute-search-filter-toolbar"
|
||||
clearAllFilters={() => {
|
||||
setSearchValue('');
|
||||
}}
|
||||
>
|
||||
<ToolbarContent>
|
||||
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl">
|
||||
<ToolbarGroup variant="filter-group">
|
||||
<ToolbarItem>{attributeDropdown}</ToolbarItem>
|
||||
<ToolbarFilter
|
||||
labels={searchValue !== '' ? [searchValue] : ([] as string[])}
|
||||
deleteLabel={() => setSearchValue('')}
|
||||
deleteLabelGroup={() => setSearchValue('')}
|
||||
categoryName={activeAttributeMenu}
|
||||
>
|
||||
{searchInput}
|
||||
</ToolbarFilter>
|
||||
<Button variant="primary" ouiaId="Primary">
|
||||
Create Workspace
|
||||
</Button>
|
||||
</ToolbarGroup>
|
||||
</ToolbarToggleGroup>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
);
|
||||
|
||||
const onSearchChange = (value: string) => {
|
||||
setSearchValue(value);
|
||||
};
|
||||
|
||||
const onFilter = (workspace: Workspace) => {
|
||||
// filter function to pass to the filter component
|
||||
const onFilter = (filters: FilteredColumn[]) => {
|
||||
// Search name with search value
|
||||
let searchValueInput: RegExp;
|
||||
try {
|
||||
searchValueInput = new RegExp(searchValue, 'i');
|
||||
} catch {
|
||||
searchValueInput = new RegExp(searchValue.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
||||
}
|
||||
let filteredWorkspaces = initialWorkspaces;
|
||||
filters.forEach((filter) => {
|
||||
let searchValueInput: RegExp;
|
||||
try {
|
||||
searchValueInput = new RegExp(filter.value, 'i');
|
||||
} catch {
|
||||
searchValueInput = new RegExp(filter.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'i');
|
||||
}
|
||||
|
||||
return (
|
||||
searchValue === '' ||
|
||||
(activeAttributeMenu === 'Name' && workspace.name.search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'Kind' && workspace.kind.search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'Image' &&
|
||||
workspace.options.imageConfig.search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'Pod Config' &&
|
||||
workspace.options.podConfig.search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'State' &&
|
||||
WorkspaceState[workspace.status.state].search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'Home Vol' &&
|
||||
workspace.podTemplate.volumes.home.search(searchValueInput) >= 0) ||
|
||||
(activeAttributeMenu === 'Data Vol' &&
|
||||
workspace.podTemplate.volumes.data.some(
|
||||
(dataVol) =>
|
||||
dataVol.pvcName.search(searchValueInput) >= 0 ||
|
||||
dataVol.mountPath.search(searchValueInput) >= 0,
|
||||
))
|
||||
);
|
||||
filteredWorkspaces = filteredWorkspaces.filter((workspace) => {
|
||||
if (filter.value === '') {
|
||||
return true;
|
||||
}
|
||||
switch (filter.columnName) {
|
||||
case columnNames.name:
|
||||
return workspace.name.search(searchValueInput) >= 0;
|
||||
case columnNames.kind:
|
||||
return workspace.kind.search(searchValueInput) >= 0;
|
||||
case columnNames.image:
|
||||
return workspace.options.imageConfig.search(searchValueInput) >= 0;
|
||||
case columnNames.podConfig:
|
||||
return workspace.options.podConfig.search(searchValueInput) >= 0;
|
||||
case columnNames.state:
|
||||
return WorkspaceState[workspace.status.state].search(searchValueInput) >= 0;
|
||||
case columnNames.homeVol:
|
||||
return workspace.podTemplate.volumes.home.search(searchValueInput) >= 0;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
});
|
||||
});
|
||||
setWorkspaces(filteredWorkspaces);
|
||||
};
|
||||
const filteredWorkspaces = workspaces.filter(onFilter);
|
||||
|
||||
// Column sorting
|
||||
|
||||
|
@ -379,7 +238,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
return [name, kind, image, podConfig, state, homeVol, cpu, ram, lastActivity];
|
||||
};
|
||||
|
||||
let sortedWorkspaces = filteredWorkspaces;
|
||||
let sortedWorkspaces = workspaces;
|
||||
if (activeSortIndex !== null) {
|
||||
sortedWorkspaces = workspaces.sort((a, b) => {
|
||||
const aValue = getSortableRowValues(a)[activeSortIndex];
|
||||
|
@ -515,30 +374,31 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
<PageSection isFilled>
|
||||
<Title headingLevel="h1">Kubeflow Workspaces</Title>
|
||||
<p>View your existing workspaces or create new workspaces.</p>
|
||||
{toolbar}
|
||||
<Content style={{ display: 'flex', alignItems: 'flex-start', columnGap: '20px' }}>
|
||||
<Filter id="filter-workspaces" onFilter={onFilter} columnNames={filterableColumns} />
|
||||
<Button variant="primary" ouiaId="Primary">
|
||||
Create Workspace
|
||||
</Button>
|
||||
</Content>
|
||||
<Table aria-label="Sortable table" ouiaId="SortableTable">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th sort={getSortParams(0)}>{columnNames.name}</Th>
|
||||
<Th sort={getSortParams(1)}>{columnNames.kind}</Th>
|
||||
<Th sort={getSortParams(2)}>{columnNames.image}</Th>
|
||||
<Th sort={getSortParams(3)}>{columnNames.podConfig}</Th>
|
||||
<Th sort={getSortParams(4)}>{columnNames.state}</Th>
|
||||
<Th sort={getSortParams(5)}>{columnNames.homeVol}</Th>
|
||||
<Th sort={getSortParams(6)} info={{ tooltip: 'Workspace CPU usage' }}>
|
||||
{columnNames.cpu}
|
||||
</Th>
|
||||
<Th sort={getSortParams(7)} info={{ tooltip: 'Workspace memory usage' }}>
|
||||
{columnNames.ram}
|
||||
</Th>
|
||||
<Th sort={getSortParams(8)}>{columnNames.lastActivity}</Th>
|
||||
{Object.values(columnNames).map((columnName, index) => (
|
||||
<Th key={`${columnName}-col-name`} sort={getSortParams(index)}>
|
||||
{columnName}
|
||||
</Th>
|
||||
))}
|
||||
<Th screenReaderText="Primary action" />
|
||||
</Tr>
|
||||
</Thead>
|
||||
{sortedWorkspaces.map((workspace, rowIndex) => (
|
||||
<Tbody key={rowIndex} isExpanded={isWorkspaceExpanded(workspace)}>
|
||||
<Tr>
|
||||
<Tbody
|
||||
id="workspaces-table-content"
|
||||
key={rowIndex}
|
||||
isExpanded={isWorkspaceExpanded(workspace)}
|
||||
>
|
||||
<Tr id={`workspaces-table-row-${rowIndex + 1}`}>
|
||||
<Td
|
||||
expand={{
|
||||
rowIndex,
|
||||
|
|
|
@ -0,0 +1,228 @@
|
|||
import * as React from 'react';
|
||||
import {
|
||||
Menu,
|
||||
MenuContent,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
MenuToggle,
|
||||
MenuToggleElement,
|
||||
Popper,
|
||||
SearchInput,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarFilter,
|
||||
ToolbarGroup,
|
||||
ToolbarItem,
|
||||
ToolbarToggleGroup,
|
||||
} from '@patternfly/react-core';
|
||||
import { FilterIcon } from '@patternfly/react-icons';
|
||||
|
||||
export interface FilterProps {
|
||||
id: string;
|
||||
onFilter: (filters: FilteredColumn[]) => void;
|
||||
columnNames: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface FilteredColumn {
|
||||
columnName: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
const Filter: React.FC<FilterProps> = ({ id, onFilter, columnNames }) => {
|
||||
const [activeFilter, setActiveFilter] = React.useState<FilteredColumn>({
|
||||
columnName: Object.values(columnNames)[0],
|
||||
value: '',
|
||||
});
|
||||
const [searchValue, setSearchValue] = React.useState<string>('');
|
||||
const [isFilterMenuOpen, setIsFilterMenuOpen] = React.useState<boolean>(false);
|
||||
const [filters, setFilters] = React.useState<FilteredColumn[]>([]);
|
||||
|
||||
const filterToggleRef = React.useRef<MenuToggleElement | null>(null);
|
||||
const filterMenuRef = React.useRef<HTMLDivElement | null>(null);
|
||||
const filterContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleFilterMenuKeys = React.useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
if (!isFilterMenuOpen) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
filterMenuRef.current?.contains(event.target as Node) ||
|
||||
filterToggleRef.current?.contains(event.target as Node)
|
||||
) {
|
||||
if (event.key === 'Escape' || event.key === 'Tab') {
|
||||
setIsFilterMenuOpen(!isFilterMenuOpen);
|
||||
filterToggleRef.current?.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
[isFilterMenuOpen, filterMenuRef, filterToggleRef],
|
||||
);
|
||||
|
||||
const handleClickOutside = React.useCallback(
|
||||
(event: MouseEvent) => {
|
||||
if (isFilterMenuOpen && !filterMenuRef.current?.contains(event.target as Node)) {
|
||||
setIsFilterMenuOpen(false);
|
||||
}
|
||||
},
|
||||
[isFilterMenuOpen, filterMenuRef],
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener('keydown', handleFilterMenuKeys);
|
||||
window.addEventListener('click', handleClickOutside);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleFilterMenuKeys);
|
||||
window.removeEventListener('click', handleClickOutside);
|
||||
};
|
||||
}, [isFilterMenuOpen, filterMenuRef, handleFilterMenuKeys, handleClickOutside]);
|
||||
|
||||
const onFilterToggleClick = React.useCallback(
|
||||
(ev: React.MouseEvent) => {
|
||||
ev.stopPropagation(); // Stop handleClickOutside from handling
|
||||
if (filterMenuRef.current) {
|
||||
const firstElement = filterMenuRef.current.querySelector('li > button:not(:disabled)');
|
||||
if (firstElement) {
|
||||
(firstElement as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
setIsFilterMenuOpen(!isFilterMenuOpen);
|
||||
},
|
||||
[isFilterMenuOpen],
|
||||
);
|
||||
|
||||
const addFilter = React.useCallback(
|
||||
(filterObj: FilteredColumn) => {
|
||||
const index = filters.findIndex((filter) => filter.columnName === filterObj.columnName);
|
||||
const newFilters = filters;
|
||||
if (index !== -1) {
|
||||
newFilters[index] = filterObj;
|
||||
} else {
|
||||
newFilters.push(filterObj);
|
||||
}
|
||||
setFilters(newFilters);
|
||||
},
|
||||
[filters],
|
||||
);
|
||||
|
||||
const onSearchChange = React.useCallback(
|
||||
(value: string) => {
|
||||
const newFilter = { columnName: activeFilter.columnName, value };
|
||||
setSearchValue(value);
|
||||
setActiveFilter(newFilter);
|
||||
addFilter(newFilter);
|
||||
onFilter(filters);
|
||||
},
|
||||
[activeFilter.columnName, addFilter, filters, onFilter],
|
||||
);
|
||||
|
||||
const onDeleteLabelGroup = React.useCallback(
|
||||
(filter: FilteredColumn) => {
|
||||
const newFilters = filters.filter((filter1) => filter1.columnName !== filter.columnName);
|
||||
setFilters(newFilters);
|
||||
if (filter.columnName === activeFilter.columnName) {
|
||||
setSearchValue('');
|
||||
}
|
||||
onFilter(newFilters);
|
||||
},
|
||||
[activeFilter.columnName, filters, onFilter],
|
||||
);
|
||||
|
||||
const onFilterSelect = React.useCallback(
|
||||
(itemId: string | number | undefined) => {
|
||||
setIsFilterMenuOpen(!isFilterMenuOpen);
|
||||
const index = filters.findIndex((filter) => filter.columnName === itemId);
|
||||
setSearchValue(index === -1 ? '' : filters[index].value);
|
||||
setActiveFilter({
|
||||
columnName: itemId ? itemId.toString() : Object.values(columnNames)[0],
|
||||
value: searchValue,
|
||||
});
|
||||
},
|
||||
[columnNames, filters, isFilterMenuOpen, searchValue],
|
||||
);
|
||||
|
||||
const filterMenuToggle = React.useMemo(
|
||||
() => (
|
||||
<MenuToggle
|
||||
ref={filterToggleRef}
|
||||
onClick={onFilterToggleClick}
|
||||
isExpanded={isFilterMenuOpen}
|
||||
icon={<FilterIcon />}
|
||||
>
|
||||
{activeFilter.columnName}
|
||||
</MenuToggle>
|
||||
),
|
||||
[activeFilter.columnName, isFilterMenuOpen, onFilterToggleClick],
|
||||
);
|
||||
|
||||
const filterMenu = React.useMemo(
|
||||
() => (
|
||||
<Menu ref={filterMenuRef} onSelect={(_ev, itemId) => onFilterSelect(itemId)}>
|
||||
<MenuContent>
|
||||
<MenuList>
|
||||
{Object.values(columnNames).map((name: string) => (
|
||||
<MenuItem id={`${id}-dropdown-${name}`} key={name} itemId={name}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</MenuList>
|
||||
</MenuContent>
|
||||
</Menu>
|
||||
),
|
||||
[columnNames, id, onFilterSelect],
|
||||
);
|
||||
|
||||
const filterDropdown = React.useMemo(
|
||||
() => (
|
||||
<div ref={filterContainerRef}>
|
||||
<Popper
|
||||
trigger={filterMenuToggle}
|
||||
triggerRef={filterToggleRef}
|
||||
popper={filterMenu}
|
||||
popperRef={filterMenuRef}
|
||||
appendTo={filterContainerRef.current || undefined}
|
||||
isVisible={isFilterMenuOpen}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
[filterMenuToggle, filterMenu, isFilterMenuOpen],
|
||||
);
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
id="attribute-search-filter-toolbar"
|
||||
clearAllFilters={() => {
|
||||
setFilters([]);
|
||||
setSearchValue('');
|
||||
onFilter([]);
|
||||
}}
|
||||
>
|
||||
<ToolbarContent>
|
||||
<ToolbarToggleGroup toggleIcon={<FilterIcon />} breakpoint="xl">
|
||||
<ToolbarItem id={`${id}-dropdown`}>{filterDropdown}</ToolbarItem>
|
||||
<ToolbarGroup variant="filter-group">
|
||||
{filters.map((filter) => (
|
||||
<ToolbarFilter
|
||||
key={`${filter.columnName}-filter`}
|
||||
labels={filter.value !== '' ? [filter.value] : ['']}
|
||||
deleteLabel={() => onDeleteLabelGroup(filter)}
|
||||
deleteLabelGroup={() => onDeleteLabelGroup(filter)}
|
||||
categoryName={filter.columnName}
|
||||
>
|
||||
{undefined}
|
||||
</ToolbarFilter>
|
||||
))}
|
||||
</ToolbarGroup>
|
||||
<SearchInput
|
||||
id={`${id}-search-input`}
|
||||
placeholder={`Filter by ${activeFilter.columnName}`}
|
||||
value={searchValue}
|
||||
onChange={(_event, value) => onSearchChange(value)}
|
||||
onClear={() => onSearchChange('')}
|
||||
/>
|
||||
</ToolbarToggleGroup>
|
||||
</ToolbarContent>
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
export default Filter;
|
Loading…
Reference in New Issue