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:
ElayAharoni 2025-01-20 15:26:02 +02:00 committed by GitHub
parent 9a91f66972
commit 2d1298913a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 474 additions and 399 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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) {

View File

@ -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);
});
});

View File

@ -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,

View File

@ -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;