From 18191ed33aff4cbf76ee1816c9d76910d6febb63 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 21 Sep 2024 06:37:48 +0000 Subject: [PATCH 1/7] Update dependency express to v4.20.0 [SECURITY] --- package.json | 2 +- yarn.lock | 220 +++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 161 insertions(+), 61 deletions(-) diff --git a/package.json b/package.json index 53ebeebf..5ab93a35 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "eslint-config-prettier": "9.1.0", "eslint-plugin-cypress": "2.15.1", "eslint-plugin-prettier": "5.1.3", - "express": "4.19.2", + "express": "4.20.0", "husky": "9.0.6", "i18next": "23.8.2", "i18next-browser-languagedetector": "7.2.0", diff --git a/yarn.lock b/yarn.lock index 332b6e4b..ba06f38a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4878,9 +4878,9 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2": - version: 1.20.2 - resolution: "body-parser@npm:1.20.2" +"body-parser@npm:1.20.3": + version: 1.20.3 + resolution: "body-parser@npm:1.20.3" dependencies: bytes: "npm:3.1.2" content-type: "npm:~1.0.5" @@ -4890,11 +4890,11 @@ __metadata: http-errors: "npm:2.0.0" iconv-lite: "npm:0.4.24" on-finished: "npm:2.4.1" - qs: "npm:6.11.0" + qs: "npm:6.13.0" raw-body: "npm:2.5.2" type-is: "npm:~1.6.18" unpipe: "npm:1.0.0" - checksum: 10/3cf171b82190cf91495c262b073e425fc0d9e25cc2bf4540d43f7e7bbca27d6a9eae65ca367b6ef3993eea261159d9d2ab37ce444e8979323952e12eb3df319a + checksum: 10/8723e3d7a672eb50854327453bed85ac48d045f4958e81e7d470c56bf111f835b97e5b73ae9f6393d0011cc9e252771f46fd281bbabc57d33d3986edf1e6aeca languageName: node linkType: hard @@ -5076,13 +5076,16 @@ __metadata: languageName: node linkType: hard -"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": - version: 1.0.2 - resolution: "call-bind@npm:1.0.2" +"call-bind@npm:^1.0.2, call-bind@npm:^1.0.7": + version: 1.0.7 + resolution: "call-bind@npm:1.0.7" dependencies: - function-bind: "npm:^1.1.1" - get-intrinsic: "npm:^1.0.2" - checksum: 10/ca787179c1cbe09e1697b56ad499fd05dc0ae6febe5081d728176ade699ea6b1589240cb1ff1fe11fcf9f61538c1af60ad37e8eb2ceb4ef21cd6085dfd3ccedd + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + set-function-length: "npm:^1.2.1" + checksum: 10/cd6fe658e007af80985da5185bff7b55e12ef4c2b6f41829a26ed1eef254b1f1c12e3dfd5b2b068c6ba8b86aba62390842d81752e67dcbaec4f6f76e7113b6b7 languageName: node linkType: hard @@ -6335,6 +6338,17 @@ __metadata: languageName: node linkType: hard +"define-data-property@npm:^1.1.4": + version: 1.1.4 + resolution: "define-data-property@npm:1.1.4" + dependencies: + es-define-property: "npm:^1.0.0" + es-errors: "npm:^1.3.0" + gopd: "npm:^1.0.1" + checksum: 10/abdcb2505d80a53524ba871273e5da75e77e52af9e15b3aa65d8aad82b8a3a424dad7aee2cc0b71470ac7acf501e08defac362e8b6a73cdb4309f028061df4ae + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -6743,6 +6757,13 @@ __metadata: languageName: node linkType: hard +"encodeurl@npm:~2.0.0": + version: 2.0.0 + resolution: "encodeurl@npm:2.0.0" + checksum: 10/abf5cd51b78082cf8af7be6785813c33b6df2068ce5191a40ca8b1afe6a86f9230af9a9ce694a5ce4665955e5c1120871826df9c128a642e09c58d592e2807fe + languageName: node + linkType: hard + "encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -6858,6 +6879,22 @@ __metadata: languageName: node linkType: hard +"es-define-property@npm:^1.0.0": + version: 1.0.0 + resolution: "es-define-property@npm:1.0.0" + dependencies: + get-intrinsic: "npm:^1.2.4" + checksum: 10/f66ece0a887b6dca71848fa71f70461357c0e4e7249696f81bad0a1f347eed7b31262af4a29f5d726dc026426f085483b6b90301855e647aa8e21936f07293c6 + languageName: node + linkType: hard + +"es-errors@npm:^1.3.0": + version: 1.3.0 + resolution: "es-errors@npm:1.3.0" + checksum: 10/96e65d640156f91b707517e8cdc454dd7d47c32833aa3e85d79f24f9eb7ea85f39b63e36216ef0114996581969b59fe609a94e30316b08f5f4df1d44134cf8d5 + languageName: node + linkType: hard + "es-get-iterator@npm:^1.1.2": version: 1.1.2 resolution: "es-get-iterator@npm:1.1.2" @@ -7514,42 +7551,42 @@ __metadata: languageName: node linkType: hard -"express@npm:4.19.2, express@npm:^4.17.3": - version: 4.19.2 - resolution: "express@npm:4.19.2" +"express@npm:4.20.0, express@npm:^4.17.3": + version: 4.20.0 + resolution: "express@npm:4.20.0" dependencies: accepts: "npm:~1.3.8" array-flatten: "npm:1.1.1" - body-parser: "npm:1.20.2" + body-parser: "npm:1.20.3" content-disposition: "npm:0.5.4" content-type: "npm:~1.0.4" cookie: "npm:0.6.0" cookie-signature: "npm:1.0.6" debug: "npm:2.6.9" depd: "npm:2.0.0" - encodeurl: "npm:~1.0.2" + encodeurl: "npm:~2.0.0" escape-html: "npm:~1.0.3" etag: "npm:~1.8.1" finalhandler: "npm:1.2.0" fresh: "npm:0.5.2" http-errors: "npm:2.0.0" - merge-descriptors: "npm:1.0.1" + merge-descriptors: "npm:1.0.3" methods: "npm:~1.1.2" on-finished: "npm:2.4.1" parseurl: "npm:~1.3.3" - path-to-regexp: "npm:0.1.7" + path-to-regexp: "npm:0.1.10" proxy-addr: "npm:~2.0.7" qs: "npm:6.11.0" range-parser: "npm:~1.2.1" safe-buffer: "npm:5.2.1" - send: "npm:0.18.0" - serve-static: "npm:1.15.0" + send: "npm:0.19.0" + serve-static: "npm:1.16.0" setprototypeof: "npm:1.2.0" statuses: "npm:2.0.1" type-is: "npm:~1.6.18" utils-merge: "npm:1.0.1" vary: "npm:~1.1.2" - checksum: 10/3fcd792536f802c059789ef48db3851b87e78fba103423e524144d79af37da7952a2b8d4e1a007f423329c7377d686d9476ac42e7d9ea413b80345d495e30a3a + checksum: 10/4131f566cf8f6d1611475d5ff5d0dbc5c628ad8b525aa2aa2b3da9a23a041efcce09ede10b8a31315b0258ac4e53208a009fd7669ee1eb385936a0d54adb3cde languageName: node linkType: hard @@ -8001,10 +8038,10 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.1": - version: 1.1.1 - resolution: "function-bind@npm:1.1.1" - checksum: 10/d83f2968030678f0b8c3f2183d63dcd969344eb8b55b4eb826a94ccac6de8b87c95bebffda37a6386c74f152284eb02956ff2c496897f35d32bdc2628ac68ac5 +"function-bind@npm:^1.1.1, function-bind@npm:^1.1.2": + version: 1.1.2 + resolution: "function-bind@npm:1.1.2" + checksum: 10/185e20d20f10c8d661d59aac0f3b63b31132d492e1b11fcc2a93cb2c47257ebaee7407c38513efd2b35cafdf972d9beb2ea4593c1e0f3bf8f2744836928d7454 languageName: node linkType: hard @@ -8064,14 +8101,16 @@ __metadata: languageName: node linkType: hard -"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3": - version: 1.1.3 - resolution: "get-intrinsic@npm:1.1.3" +"get-intrinsic@npm:^1.1.0, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.4": + version: 1.2.4 + resolution: "get-intrinsic@npm:1.2.4" dependencies: - function-bind: "npm:^1.1.1" - has: "npm:^1.0.3" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + has-proto: "npm:^1.0.1" has-symbols: "npm:^1.0.3" - checksum: 10/ab4d7d83d6d08036d197291927442d1c05d5329484f8bdcdf895f5d6ecf158ec99a8ccd9f548bcfe917382ea3b74a423bdf5bee03f5c166359045d2f8a24c7a5 + hasown: "npm:^2.0.0" + checksum: 10/85bbf4b234c3940edf8a41f4ecbd4e25ce78e5e6ad4e24ca2f77037d983b9ef943fd72f00f3ee97a49ec622a506b67db49c36246150377efcda1c9eb03e5f06d languageName: node linkType: hard @@ -8318,12 +8357,19 @@ __metadata: languageName: node linkType: hard -"has-property-descriptors@npm:^1.0.0": - version: 1.0.0 - resolution: "has-property-descriptors@npm:1.0.0" +"has-property-descriptors@npm:^1.0.0, has-property-descriptors@npm:^1.0.2": + version: 1.0.2 + resolution: "has-property-descriptors@npm:1.0.2" dependencies: - get-intrinsic: "npm:^1.1.1" - checksum: 10/a6d3f0a266d0294d972e354782e872e2fe1b6495b321e6ef678c9b7a06a40408a6891817350c62e752adced73a94ac903c54734fee05bf65b1905ee1368194bb + es-define-property: "npm:^1.0.0" + checksum: 10/2d8c9ab8cebb572e3362f7d06139a4592105983d4317e68f7adba320fe6ddfc8874581e0971e899e633fd5f72e262830edce36d5a0bc863dad17ad20572484b2 + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.3 + resolution: "has-proto@npm:1.0.3" + checksum: 10/0b67c2c94e3bea37db3e412e3c41f79d59259875e636ba471e94c009cdfb1fa82bf045deeffafc7dbb9c148e36cae6b467055aaa5d9fad4316e11b41e3ba551a languageName: node linkType: hard @@ -8359,6 +8405,15 @@ __metadata: languageName: node linkType: hard +"hasown@npm:^2.0.0": + version: 2.0.2 + resolution: "hasown@npm:2.0.2" + dependencies: + function-bind: "npm:^1.1.2" + checksum: 10/7898a9c1788b2862cf0f9c345a6bec77ba4a0c0983c7f19d610c382343d4f98fa260686b225dfb1f88393a66679d2ec58ee310c1d6868c081eda7918f32cc70a + languageName: node + linkType: hard + "he@npm:^1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -10678,10 +10733,10 @@ __metadata: languageName: node linkType: hard -"merge-descriptors@npm:1.0.1": - version: 1.0.1 - resolution: "merge-descriptors@npm:1.0.1" - checksum: 10/5abc259d2ae25bb06d19ce2b94a21632583c74e2a9109ee1ba7fd147aa7362b380d971e0251069f8b3eb7d48c21ac839e21fa177b335e82c76ec172e30c31a26 +"merge-descriptors@npm:1.0.3": + version: 1.0.3 + resolution: "merge-descriptors@npm:1.0.3" + checksum: 10/52117adbe0313d5defa771c9993fe081e2d2df9b840597e966aadafde04ae8d0e3da46bac7ca4efc37d4d2b839436582659cd49c6a43eacb3fe3050896a105d1 languageName: node linkType: hard @@ -11169,10 +11224,10 @@ __metadata: languageName: node linkType: hard -"object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": - version: 1.12.3 - resolution: "object-inspect@npm:1.12.3" - checksum: 10/532b0036f0472f561180fac0d04fe328ee01f57637624c83fb054f81b5bfe966cdf4200612a499ed391a7ca3c46b20a0bc3a55fc8241d944abe687c556a32b39 +"object-inspect@npm:^1.12.2, object-inspect@npm:^1.13.1": + version: 1.13.2 + resolution: "object-inspect@npm:1.13.2" + checksum: 10/7ef65583b6397570a17c56f0c1841e0920e83900f2c94638927abb7b81ac08a19c7aae135bd9dcca96208cac0c7332b4650fb927f027b0cf92d71df2990d0561 languageName: node linkType: hard @@ -11531,10 +11586,10 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:0.1.7": - version: 0.1.7 - resolution: "path-to-regexp@npm:0.1.7" - checksum: 10/701c99e1f08e3400bea4d701cf6f03517474bb1b608da71c78b1eb261415b645c5670dfae49808c89e12cea2dccd113b069f040a80de012da0400191c6dbd1c8 +"path-to-regexp@npm:0.1.10": + version: 0.1.10 + resolution: "path-to-regexp@npm:0.1.10" + checksum: 10/894e31f1b20e592732a87db61fff5b95c892a3fe430f9ab18455ebe69ee88ef86f8eb49912e261f9926fc53da9f93b46521523e33aefd9cb0a7b0d85d7096006 languageName: node linkType: hard @@ -12727,6 +12782,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:6.13.0": + version: 6.13.0 + resolution: "qs@npm:6.13.0" + dependencies: + side-channel: "npm:^1.0.6" + checksum: 10/f548b376e685553d12e461409f0d6e5c59ec7c7d76f308e2a888fd9db3e0c5e89902bedd0754db3a9038eda5f27da2331a6f019c8517dc5e0a16b3c9a6e9cef8 + languageName: node + linkType: hard + "query-string@npm:7.1.1": version: 7.1.1 resolution: "query-string@npm:7.1.1" @@ -13804,7 +13868,7 @@ __metadata: eslint-config-react-app: "npm:7.0.0" eslint-plugin-cypress: "npm:2.15.1" eslint-plugin-prettier: "npm:5.1.3" - express: "npm:4.19.2" + express: "npm:4.20.0" husky: "npm:9.0.6" i18next: "npm:23.8.2" i18next-browser-languagedetector: "npm:7.2.0" @@ -13916,6 +13980,27 @@ __metadata: languageName: node linkType: hard +"send@npm:0.19.0": + version: 0.19.0 + resolution: "send@npm:0.19.0" + dependencies: + debug: "npm:2.6.9" + depd: "npm:2.0.0" + destroy: "npm:1.2.0" + encodeurl: "npm:~1.0.2" + escape-html: "npm:~1.0.3" + etag: "npm:~1.8.1" + fresh: "npm:0.5.2" + http-errors: "npm:2.0.0" + mime: "npm:1.6.0" + ms: "npm:2.1.3" + on-finished: "npm:2.4.1" + range-parser: "npm:~1.2.1" + statuses: "npm:2.0.1" + checksum: 10/1f6064dea0ae4cbe4878437aedc9270c33f2a6650a77b56a16b62d057527f2766d96ee282997dd53ec0339082f2aad935bc7d989b46b48c82fc610800dc3a1d0 + languageName: node + linkType: hard + "serialize-javascript@npm:^4.0.0": version: 4.0.0 resolution: "serialize-javascript@npm:4.0.0" @@ -13965,15 +14050,15 @@ __metadata: languageName: node linkType: hard -"serve-static@npm:1.15.0": - version: 1.15.0 - resolution: "serve-static@npm:1.15.0" +"serve-static@npm:1.16.0": + version: 1.16.0 + resolution: "serve-static@npm:1.16.0" dependencies: encodeurl: "npm:~1.0.2" escape-html: "npm:~1.0.3" parseurl: "npm:~1.3.3" send: "npm:0.18.0" - checksum: 10/699b2d4c29807a51d9b5e0f24955346911437aebb0178b3c4833ad30d3eca93385ff9927254f5c16da345903cad39d9cd4a532198c95a5129cc4ed43911b15a4 + checksum: 10/29a01f67e8c64a359d49dd0c46bc95bb4aa99781f97845dccbf0c8cd0284c5fd79ad7fb9433a36fac4b6c58b577d3eab314a379142412413b8b5cd73be3cd551 languageName: node linkType: hard @@ -14005,6 +14090,20 @@ __metadata: languageName: node linkType: hard +"set-function-length@npm:^1.2.1": + version: 1.2.2 + resolution: "set-function-length@npm:1.2.2" + dependencies: + define-data-property: "npm:^1.1.4" + es-errors: "npm:^1.3.0" + function-bind: "npm:^1.1.2" + get-intrinsic: "npm:^1.2.4" + gopd: "npm:^1.0.1" + has-property-descriptors: "npm:^1.0.2" + checksum: 10/505d62b8e088468917ca4e3f8f39d0e29f9a563b97dbebf92f4bd2c3172ccfb3c5b8e4566d5fcd00784a00433900e7cb8fbc404e2dbd8c3818ba05bb9d4a8a6d + languageName: node + linkType: hard + "setprototypeof@npm:1.1.0": version: 1.1.0 resolution: "setprototypeof@npm:1.1.0" @@ -14042,14 +14141,15 @@ __metadata: languageName: node linkType: hard -"side-channel@npm:^1.0.4": - version: 1.0.4 - resolution: "side-channel@npm:1.0.4" +"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6": + version: 1.0.6 + resolution: "side-channel@npm:1.0.6" dependencies: - call-bind: "npm:^1.0.0" - get-intrinsic: "npm:^1.0.2" - object-inspect: "npm:^1.9.0" - checksum: 10/c4998d9fc530b0e75a7fd791ad868fdc42846f072734f9080ff55cc8dc7d3899abcda24fd896aa6648c3ab7021b4bb478073eb4f44dfd55bce9714bc1a7c5d45 + call-bind: "npm:^1.0.7" + es-errors: "npm:^1.3.0" + get-intrinsic: "npm:^1.2.4" + object-inspect: "npm:^1.13.1" + checksum: 10/eb10944f38cebad8ad643dd02657592fa41273ce15b8bfa928d3291aff2d30c20ff777cfe908f76ccc4551ace2d1245822fdc576657cce40e9066c638ca8fa4d languageName: node linkType: hard From a6e5456043f27912b331b140eba86f05b6f7f121 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:26:28 +0000 Subject: [PATCH 2/7] Bump rollup from 2.79.1 to 2.79.2 Bumps [rollup](https://github.com/rollup/rollup) from 2.79.1 to 2.79.2. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.79.1...v2.79.2) --- updated-dependencies: - dependency-name: rollup dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ba06f38a..a1f253e5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13660,8 +13660,8 @@ __metadata: linkType: hard "rollup@npm:^2.43.1": - version: 2.79.1 - resolution: "rollup@npm:2.79.1" + version: 2.79.2 + resolution: "rollup@npm:2.79.2" dependencies: fsevents: "npm:~2.3.2" dependenciesMeta: @@ -13669,7 +13669,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/df087b701304432f30922bbee5f534ab189aa6938bd383b5686c03147e0d00cd1789ea10a462361326ce6b6ebe448ce272ad3f3cc40b82eeb3157df12f33663c + checksum: 10/095ba0a82811b1866a76d826987743278db0a87c45092656986bfff490326b66187d5f9ff0c24cf8d5682bc470aa00c36654e0044d6b6335ac0c1201b8280880 languageName: node linkType: hard From 8066071405c6cb9529cc494940d32e4b5d416d7d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 25 Oct 2024 10:49:39 +0000 Subject: [PATCH 3/7] Bump http-proxy-middleware from 2.0.6 to 2.0.7 Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.6 to 2.0.7. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.7/CHANGELOG.md) - [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.6...v2.0.7) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1f253e5..4bdda75e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8610,8 +8610,8 @@ __metadata: linkType: hard "http-proxy-middleware@npm:^2.0.3": - version: 2.0.6 - resolution: "http-proxy-middleware@npm:2.0.6" + version: 2.0.7 + resolution: "http-proxy-middleware@npm:2.0.7" dependencies: "@types/http-proxy": "npm:^1.17.8" http-proxy: "npm:^1.18.1" @@ -8623,7 +8623,7 @@ __metadata: peerDependenciesMeta: "@types/express": optional: true - checksum: 10/768e7ae5a422bbf4b866b64105b4c2d1f468916b7b0e9c96750551c7732383069b411aa7753eb7b34eab113e4f77fb770122cb7fb9c8ec87d138d5ddaafda891 + checksum: 10/4a51bf612b752ad945701995c1c029e9501c97e7224c0cf3f8bf6d48d172d6a8f2b57c20fec469534fdcac3aa8a6f332224a33c6b0d7f387aa2cfff9b67216fd languageName: node linkType: hard From 3f0c1856776782d38422388a4e92b582351fbfa3 Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 11 Nov 2024 14:53:46 +0000 Subject: [PATCH 4/7] Generate custom admin tabs from plugin routes #1418 - Implemented dynamic generation of admin tabs using plugin routes - Updated admin page to render tabs based on configured plugin routes - Refactored tab generation logic to support custom admin routes for each plugin --- src/App.test.tsx | 6 +- src/adminPage/adminPage.component.test.tsx | 56 ++++++--- src/adminPage/adminPage.component.tsx | 119 ++++++++++-------- src/mainAppBar/mainAppBar.component.test.tsx | 37 ++++-- src/mainAppBar/mainAppBar.component.tsx | 41 +++--- .../mobileOverflowMenu.component.test.tsx | 29 +++-- .../mobileOverflowMenu.component.tsx | 22 ++-- src/mainAppBar/pageLinks.component.tsx | 21 +++- src/routing/routing.component.tsx | 61 ++++++--- src/state/actions/scigateway.actions.tsx | 44 +++---- src/state/scigateway.types.tsx | 5 +- src/state/state.types.tsx | 12 +- 12 files changed, 271 insertions(+), 182 deletions(-) diff --git a/src/App.test.tsx b/src/App.test.tsx index b09d4924..1dbdc25e 100644 --- a/src/App.test.tsx +++ b/src/App.test.tsx @@ -1,11 +1,11 @@ +import { useMediaQuery } from '@mui/material'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import axios from 'axios'; import React from 'react'; import { createRoot } from 'react-dom/client'; import App, { AppSansHoc } from './App'; -import { act, fireEvent, render, screen } from '@testing-library/react'; import { flushPromises } from './setupTests'; -import axios from 'axios'; import { RegisterRouteType } from './state/scigateway.types'; -import { useMediaQuery } from '@mui/material'; jest.mock('./state/actions/loadMicroFrontends', () => ({ init: jest.fn(() => Promise.resolve()), diff --git a/src/adminPage/adminPage.component.test.tsx b/src/adminPage/adminPage.component.test.tsx index e2aaba7d..a929b6a7 100644 --- a/src/adminPage/adminPage.component.test.tsx +++ b/src/adminPage/adminPage.component.test.tsx @@ -1,19 +1,18 @@ -import React from 'react'; +import { StyledEngineProvider, ThemeProvider } from '@mui/material'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { createLocation, createMemoryHistory, History } from 'history'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router'; +import configureStore from 'redux-mock-store'; +import { thunk } from 'redux-thunk'; +import TestAuthProvider from '../authentication/testAuthProvider'; import { authState, initialState } from '../state/reducers/scigateway.reducer'; -import { StateType } from '../state/state.types'; import { PluginConfig } from '../state/scigateway.types'; -import configureStore from 'redux-mock-store'; -import AdminPage from './adminPage.component'; -import { Provider } from 'react-redux'; +import { StateType } from '../state/state.types'; import { buildTheme } from '../theming'; -import TestAuthProvider from '../authentication/testAuthProvider'; -import { thunk } from 'redux-thunk'; -import { Router } from 'react-router'; -import { StyledEngineProvider, ThemeProvider } from '@mui/material'; -import { render, screen } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { getPluginRoutes } from './adminPage.component'; +import AdminPage, { getAdminPluginRoutes } from './adminPage.component'; describe('Admin page component', () => { let mockStore; @@ -50,9 +49,24 @@ describe('Admin page component', () => { ); } + afterEach(() => { + jest.clearAllMocks(); + }); + it('should render maintenance page correctly', () => { - history.replace('/admin/maintenance'); state.scigateway.adminPageDefaultTab = 'download'; + state.scigateway.plugins = [ + ...state.scigateway.plugins, + { + order: 1, + plugin: 'datagateway-download', + link: '/admin/download', + section: 'Admin', + displayName: 'Admin Download', + admin: true, + }, + ]; + history.replace('/admin/maintenance'); render(, { wrapper: Wrapper }); @@ -133,7 +147,7 @@ describe('Admin page component', () => { it('should return an empty object when given an empty plugins array', () => { const plugins = []; - const result = getPluginRoutes(plugins); + const result = getAdminPluginRoutes({ plugins }); expect(result).toEqual({}); }); @@ -164,9 +178,9 @@ describe('Admin page component', () => { order: 3, }, ]; - const result = getPluginRoutes(plugins, true); // Admin user + const result = getAdminPluginRoutes({ plugins }); // Admin user expect(result).toEqual({ - PluginA: ['/admin/pluginA', '/admin/pluginA2'], + PluginA: { pluginA: '/admin/pluginA', pluginA2: '/admin/pluginA2' }, }); }); @@ -175,7 +189,7 @@ describe('Admin page component', () => { { plugin: 'PluginA', admin: true, - link: '/admin/pluginA', + link: '/admin/pluginALink', section: 'A', displayName: 'A', order: 1, @@ -183,15 +197,17 @@ describe('Admin page component', () => { { plugin: 'PluginB', admin: false, - link: '/public/pluginB', + link: '/public/pluginBLink', section: 'B', displayName: 'B', order: 2, }, ]; - const result = getPluginRoutes(plugins, false); // Non-admin user + const result = getAdminPluginRoutes({ plugins }); // Non-admin user expect(result).toEqual({ - PluginB: ['/public/pluginB'], + PluginA: { + pluginALink: '/admin/pluginALink', + }, }); }); }); diff --git a/src/adminPage/adminPage.component.tsx b/src/adminPage/adminPage.component.tsx index d5adbf23..5739de2e 100644 --- a/src/adminPage/adminPage.component.tsx +++ b/src/adminPage/adminPage.component.tsx @@ -1,55 +1,67 @@ -import React, { ReactElement } from 'react'; -import Typography from '@mui/material/Typography'; import { Paper } from '@mui/material'; +import Typography from '@mui/material/Typography'; +import React, { ReactElement } from 'react'; import { connect } from 'react-redux'; +import { PluginConfig } from '../state/scigateway.types'; import { StateType } from '../state/state.types'; -import { adminRoutes, PluginConfig } from '../state/scigateway.types'; -import Tabs from '@mui/material/Tabs'; import Tab from '@mui/material/Tab'; +import Tabs from '@mui/material/Tabs'; +import { useTranslation } from 'react-i18next'; import { Link, Route, Switch, useLocation } from 'react-router-dom'; import PageNotFound from '../pageNotFound/pageNotFound.component'; -import { PluginPlaceHolder } from '../routing/routing.component'; +import { + getAdminRoutes, + PluginPlaceHolder, +} from '../routing/routing.component'; import MaintenancePage from './maintenancePage.component'; -import { useTranslation } from 'react-i18next'; export interface AdminPageProps { plugins: PluginConfig[]; - adminPageDefaultTab?: 'maintenance' | 'download'; + adminPageDefaultTab?: string; } -export const getPluginRoutes = ( - plugins: PluginConfig[], - admin?: boolean -): Record => { - const pluginRoutes: Record = {}; +export const getAdminPluginRoutes = (props: { + plugins: PluginConfig[]; +}): Record> => { + const { plugins } = props; + const pluginRoutes: Record> = {}; plugins.forEach((p) => { - const isAdmin = admin ? p.admin : !p.admin; const basePluginLink = p.link.split('?')[0]; - if (isAdmin) { - if (pluginRoutes[p.plugin]) { - pluginRoutes[p.plugin].push(basePluginLink); - } else { - pluginRoutes[p.plugin] = [basePluginLink]; + + if (p.admin) { + // Extract `plugin` and `tabName` values from the link + const tabName = basePluginLink.split('/')[2]; // Ignore `/admin` part, get the tabName as the third part + + // Initialize nested structure for each plugin and tabName + if (!pluginRoutes[p.plugin]) { + pluginRoutes[p.plugin] = {}; + } + + // Only store the first route (or the most relevant one) + if (!pluginRoutes[p.plugin][tabName]) { + pluginRoutes[p.plugin][tabName] = basePluginLink; } } }); + return pluginRoutes; }; const AdminPage = (props: AdminPageProps): ReactElement => { - const pluginRoutes = getPluginRoutes(props.plugins, true); + const pluginRoutes = getAdminPluginRoutes({ plugins: props.plugins }); + const adminRoutes = getAdminRoutes({ plugins: props.plugins }); const location = useLocation(); - - const [tabValue, setTabValue] = React.useState<'maintenance' | 'download'>( - // allows direct access to a tab when another tab is the default - (Object.keys(adminRoutes) as (keyof typeof adminRoutes)[]).find( - (key) => adminRoutes[key] === location.pathname + const [tabValue, setTabValue] = React.useState( + (Object.keys(adminRoutes) as (keyof typeof adminRoutes)[]).find((key) => + location.pathname.startsWith(adminRoutes[key]) ) ?? - props.adminPageDefaultTab ?? - 'maintenance' + (props.adminPageDefaultTab && + adminRoutes.hasOwnProperty(props.adminPageDefaultTab) + ? props.adminPageDefaultTab + : 'maintenance') ); const [t] = useTranslation(); @@ -76,22 +88,26 @@ const AdminPage = (props: AdminPageProps): ReactElement => { setTabValue(newValue); }} > - - + {Object.entries(adminRoutes).map(([key, value]) => { + const pluginDetails = props.plugins.find( + (plugin) => plugin.link === value + ); + + return ( + + ); + })} @@ -105,20 +121,21 @@ const AdminPage = (props: AdminPageProps): ReactElement => { - {Object.entries(pluginRoutes).map(([key, value]) => { - return ( - + {Object.entries(pluginRoutes).map(([pluginName, tabRoutes]) => + Object.entries(tabRoutes).map(([tabName, route]) => ( + - ); - })} + )) + )} + diff --git a/src/mainAppBar/mainAppBar.component.test.tsx b/src/mainAppBar/mainAppBar.component.test.tsx index a1aefd2e..44fd1bcf 100644 --- a/src/mainAppBar/mainAppBar.component.test.tsx +++ b/src/mainAppBar/mainAppBar.component.test.tsx @@ -1,25 +1,25 @@ -import React from 'react'; -import MainAppBarComponent from './mainAppBar.component'; +import { useMediaQuery } from '@mui/material'; +import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; +import { render, screen, waitFor, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { push } from 'connected-react-router'; import { createLocation, createMemoryHistory, History } from 'history'; -import { StateType } from '../state/state.types'; -import { PluginConfig } from '../state/scigateway.types'; +import React from 'react'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; import configureStore, { MockStore } from 'redux-mock-store'; -import { push } from 'connected-react-router'; -import { initialState } from '../state/reducers/scigateway.reducer'; +import TestAuthProvider from '../authentication/testAuthProvider'; import { loadDarkModePreference, loadHighContrastModePreference, toggleDrawer, toggleHelp, } from '../state/actions/scigateway.actions'; -import { Provider } from 'react-redux'; -import TestAuthProvider from '../authentication/testAuthProvider'; +import { initialState } from '../state/reducers/scigateway.reducer'; +import { PluginConfig } from '../state/scigateway.types'; +import { StateType } from '../state/state.types'; import { buildTheme } from '../theming'; -import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; -import { Router } from 'react-router-dom'; -import { render, screen, waitFor, within } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { useMediaQuery } from '@mui/material'; +import MainAppBarComponent from './mainAppBar.component'; jest.mock('@mui/material', () => ({ __esmodule: true, @@ -219,6 +219,17 @@ describe('Main app bar component', () => { it('redirects to Admin page when Admin button clicked (download is default)', async () => { state.scigateway.adminPageDefaultTab = 'download'; + state.scigateway.plugins = [ + ...state.scigateway.plugins, + { + section: 'Admin', + link: '/admin/download', + displayName: 'Admin Download', + admin: true, + order: 1, + plugin: 'plugin', + }, + ]; const user = userEvent.setup(); render(, { wrapper: Wrapper }); diff --git a/src/mainAppBar/mainAppBar.component.tsx b/src/mainAppBar/mainAppBar.component.tsx index eb727df6..56ace706 100644 --- a/src/mainAppBar/mainAppBar.component.tsx +++ b/src/mainAppBar/mainAppBar.component.tsx @@ -1,37 +1,36 @@ -import React, { useState } from 'react'; -import { Dispatch, Action } from 'redux'; -import { connect } from 'react-redux'; -import AppBar from '@mui/material/AppBar'; -import Toolbar from '@mui/material/Toolbar'; -import Button from '@mui/material/Button'; -import IconButton from '@mui/material/IconButton'; import HelpIcon from '@mui/icons-material/HelpOutline'; import MenuIcon from '@mui/icons-material/Menu'; -import SettingsIcon from '@mui/icons-material/Settings'; +import MenuOpenIcon from '@mui/icons-material/MenuOpen'; import MoreVertIcon from '@mui/icons-material/MoreVert'; +import SettingsIcon from '@mui/icons-material/Settings'; import { Box, styled, useMediaQuery } from '@mui/material'; -import MenuOpenIcon from '@mui/icons-material/MenuOpen'; +import AppBar from '@mui/material/AppBar'; +import Button from '@mui/material/Button'; +import IconButton from '@mui/material/IconButton'; import { Theme, useTheme } from '@mui/material/styles'; +import Toolbar from '@mui/material/Toolbar'; +import { push } from 'connected-react-router'; +import React, { useState } from 'react'; +import { connect } from 'react-redux'; +import { useLocation } from 'react-router-dom'; +import { Action, Dispatch } from 'redux'; +import NullAuthProvider from '../authentication/nullAuthProvider'; import ScigatewayLogo from '../images/scigateway-white-text-blue-mark-logo.svg'; +import NotificationBadgeComponent from '../notifications/notificationBadge.component'; import { - toggleDrawer, - toggleHelp, loadDarkModePreference, loadHighContrastModePreference, + toggleDrawer, + toggleHelp, } from '../state/actions/scigateway.actions'; -import { AppStrings } from '../state/scigateway.types'; +import { AppStrings, PluginConfig } from '../state/scigateway.types'; import { StateType } from '../state/state.types'; -import { push } from 'connected-react-router'; import { getAppStrings, getString } from '../state/strings'; -import UserProfileComponent from './userProfile.component'; -import NotificationBadgeComponent from '../notifications/notificationBadge.component'; -import { PluginConfig } from '../state/scigateway.types'; -import { useLocation } from 'react-router-dom'; -import SettingsMenu from './settingsMenu.component'; import MobileOverflowMenu from './mobileOverflowMenu.component'; -import { appBarIconButtonStyle, appBarMenuItemIconStyle } from './styles'; import PageLinks from './pageLinks.component'; -import NullAuthProvider from '../authentication/nullAuthProvider'; +import SettingsMenu from './settingsMenu.component'; +import { appBarIconButtonStyle, appBarMenuItemIconStyle } from './styles'; +import UserProfileComponent from './userProfile.component'; interface MainAppProps { drawerOpen: boolean; @@ -47,7 +46,7 @@ interface MainAppProps { loading: boolean; logo?: string; homepageUrl?: string; - adminPageDefaultTab?: 'maintenance' | 'download'; + adminPageDefaultTab?: string; pathname: string; } diff --git a/src/mainAppBar/mobileOverflowMenu.component.test.tsx b/src/mainAppBar/mobileOverflowMenu.component.test.tsx index be298589..b62d9da1 100644 --- a/src/mainAppBar/mobileOverflowMenu.component.test.tsx +++ b/src/mainAppBar/mobileOverflowMenu.component.test.tsx @@ -1,20 +1,20 @@ -import configureStore, { MockStore } from 'redux-mock-store'; -import { StateType } from '../state/state.types'; -import { initialState } from '../state/reducers/scigateway.reducer'; +import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { push } from 'connected-react-router'; import { createLocation, createMemoryHistory, History } from 'history'; import * as React from 'react'; -import { StyledEngineProvider, ThemeProvider } from '@mui/material/styles'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; -import { buildTheme } from '../theming'; -import { render, screen } from '@testing-library/react'; -import MobileOverflowMenu from './mobileOverflowMenu.component'; +import configureStore, { MockStore } from 'redux-mock-store'; import TestAuthProvider, { NonAdminTestAuthProvider, } from '../authentication/testAuthProvider'; -import userEvent from '@testing-library/user-event'; -import { push } from 'connected-react-router'; import { toggleHelp } from '../state/actions/scigateway.actions'; +import { initialState } from '../state/reducers/scigateway.reducer'; +import { StateType } from '../state/state.types'; +import { buildTheme } from '../theming'; +import MobileOverflowMenu from './mobileOverflowMenu.component'; describe('Mobile overflow menu', () => { let testStore: MockStore; @@ -99,6 +99,17 @@ describe('Mobile overflow menu', () => { it('redirects to Admin page when Admin button clicked (download is default)', async () => { state.scigateway.adminPageDefaultTab = 'download'; + state.scigateway.plugins = [ + ...state.scigateway.plugins, + { + section: 'Admin', + link: '/admin/download', + displayName: 'Admin Download', + admin: true, + order: 1, + plugin: 'plugin', + }, + ]; const user = userEvent.setup(); render(, { diff --git a/src/mainAppBar/mobileOverflowMenu.component.tsx b/src/mainAppBar/mobileOverflowMenu.component.tsx index e6754cbc..5ef77b26 100644 --- a/src/mainAppBar/mobileOverflowMenu.component.tsx +++ b/src/mainAppBar/mobileOverflowMenu.component.tsx @@ -1,18 +1,18 @@ import React from 'react'; import { + Divider, + ListItemText, Menu, MenuItem, - ListItemText, MenuProps, - Divider, } from '@mui/material'; -import { SettingsMenuContent } from './settingsMenu.component'; +import { push } from 'connected-react-router'; import { useDispatch, useSelector } from 'react-redux'; +import { getAdminRoutes } from '../routing/routing.component'; +import { toggleHelp } from '../state/actions/scigateway.actions'; import { StateType } from '../state/state.types'; import { getAppStrings, getString } from '../state/strings'; -import { push } from 'connected-react-router'; -import { adminRoutes } from '../state/scigateway.types'; -import { toggleHelp } from '../state/actions/scigateway.actions'; +import { SettingsMenuContent } from './settingsMenu.component'; interface MobileOverflowMenuProps extends MenuProps { onClose: () => void; @@ -39,6 +39,9 @@ function MobileOverflowMenu({ (state: StateType) => state.scigateway.adminPageDefaultTab ); + const plugins = useSelector((state: StateType) => state.scigateway.plugins); + const adminRoutes = getAdminRoutes({ plugins }); + const dispatch = useDispatch(); function navigateToHelpPage(): void { @@ -46,7 +49,12 @@ function MobileOverflowMenu({ } function navigateToAdminPage(): void { - dispatch(push(adminRoutes[adminPageDefaultTab ?? 'maintenance'])); + const targetRoute = + adminPageDefaultTab && adminRoutes.hasOwnProperty(adminPageDefaultTab) + ? adminRoutes[adminPageDefaultTab] + : adminRoutes['maintenance']; + + dispatch(push(targetRoute)); } function toggleTutorial(): void { diff --git a/src/mainAppBar/pageLinks.component.tsx b/src/mainAppBar/pageLinks.component.tsx index b126ae96..7de64c33 100644 --- a/src/mainAppBar/pageLinks.component.tsx +++ b/src/mainAppBar/pageLinks.component.tsx @@ -1,12 +1,12 @@ +import React from 'react'; +import Button from '@mui/material/Button'; +import Typography from '@mui/material/Typography'; +import { push } from 'connected-react-router'; import { useDispatch, useSelector } from 'react-redux'; +import { getAdminRoutes } from '../routing/routing.component'; import { StateType } from '../state/state.types'; -import Button from '@mui/material/Button'; import { getAppStrings, getString } from '../state/strings'; -import Typography from '@mui/material/Typography'; -import React from 'react'; import { appBarIconButtonStyle } from './styles'; -import { push } from 'connected-react-router'; -import { adminRoutes } from '../state/scigateway.types'; function PageLinks(): JSX.Element { const shouldShowHelpPageButton = useSelector( @@ -20,6 +20,10 @@ function PageLinks(): JSX.Element { const adminPageDefaultTab = useSelector( (state: StateType) => state.scigateway.adminPageDefaultTab ); + + const plugins = useSelector((state: StateType) => state.scigateway.plugins); + const adminRoutes = getAdminRoutes({ plugins }); + const res = useSelector((state: StateType) => getAppStrings(state, 'main-appbar') ); @@ -31,7 +35,12 @@ function PageLinks(): JSX.Element { } function navigateToAdminPage(): void { - dispatch(push(adminRoutes[adminPageDefaultTab ?? 'maintenance'])); + const targetRoute = + adminPageDefaultTab && adminRoutes.hasOwnProperty(adminPageDefaultTab) + ? adminRoutes[adminPageDefaultTab] + : adminRoutes['maintenance']; + + dispatch(push(targetRoute)); } return ( diff --git a/src/routing/routing.component.tsx b/src/routing/routing.component.tsx index 4b83f45d..c955b61e 100644 --- a/src/routing/routing.component.tsx +++ b/src/routing/routing.component.tsx @@ -1,29 +1,29 @@ -import React from 'react'; +import { useMediaQuery } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; -import { Redirect, Route, Switch } from 'react-router-dom'; -import { StateType } from '../state/state.types'; -import { - adminRoutes, - MaintenanceState, - PluginConfig, - scigatewayRoutes, -} from '../state/scigateway.types'; +import { RouterLocation } from 'connected-react-router'; +import React from 'react'; import { connect } from 'react-redux'; -import HomePage from '../homePage/homePage.component'; +import { Redirect, Route, Switch } from 'react-router-dom'; +import * as singleSpa from 'single-spa'; +import AccessibilityPage from '../accessibilityPage/accessibilityPage.component'; +import AdminPage from '../adminPage/adminPage.component'; +import NullAuthProvider from '../authentication/nullAuthProvider'; +import CookiesPage from '../cookieConsent/cookiesPage.component'; import HelpPage from '../helpPage/helpPage.component'; +import HomePage from '../homePage/homePage.component'; import LoginPage from '../loginPage/loginPage.component'; import LogoutPage from '../logoutPage/logoutPage.component'; -import CookiesPage from '../cookieConsent/cookiesPage.component'; import MaintenancePage from '../maintenancePage/maintenancePage.component'; -import AdminPage from '../adminPage/adminPage.component'; import PageNotFound from '../pageNotFound/pageNotFound.component'; -import AccessibilityPage from '../accessibilityPage/accessibilityPage.component'; -import withAuth, { usePrevious } from './authorisedRoute.component'; import { Preloader } from '../preloader/preloader.component'; -import * as singleSpa from 'single-spa'; -import { useMediaQuery } from '@mui/material'; -import NullAuthProvider from '../authentication/nullAuthProvider'; -import { RouterLocation } from 'connected-react-router'; +import { + baseAdminRoutes, + MaintenanceState, + PluginConfig, + scigatewayRoutes, +} from '../state/scigateway.types'; +import { StateType } from '../state/state.types'; +import withAuth, { usePrevious } from './authorisedRoute.component'; interface ContainerDivProps { drawerOpen: boolean; @@ -68,6 +68,29 @@ const ContainerDiv = styled('div', { }; }); +export const getAdminRoutes = (props: { + plugins: PluginConfig[]; +}): Record => { + const { plugins } = props; + const newAdminRoutes: Record = JSON.parse( + JSON.stringify(baseAdminRoutes) + ); + + // Note: Any nested paths under `/admin/path` are managed by the plugin itself and + // should not be included in the `newAdminRoutes` object. This ensures only top-level + // admin routes are added here, keeping the route structure consistent and preventing + // conflicts in routing. + + plugins.forEach((plugin) => { + if (plugin.admin) { + const routeKey = plugin.link.split('/')[2]; + newAdminRoutes[routeKey] = plugin.link; + } + }); + + return newAdminRoutes; +}; + interface RoutingProps { plugins: PluginConfig[]; location: RouterLocation; @@ -101,6 +124,7 @@ export const UnauthorisedPlugin = PluginPlaceHolder; export const AuthorisedAdminPage = withAuth(true)(AdminPage); const Routing: React.FC = (props: RoutingProps) => { + const adminRoutes = getAdminRoutes({ plugins: props.plugins }); // only set to false if we're on a plugin route i.e. not a scigateway route const manuallyLoadedPluginRef = React.useRef( Object.values(scigatewayRoutes).includes(props.location.pathname) || @@ -137,7 +161,6 @@ const Routing: React.FC = (props: RoutingProps) => { window.clearInterval(intervalId); }; }, [props.loading, props.plugins, props.location]); - React.useEffect(() => { // switching between an admin & non-admin route of the same app causes problems // as the Route and thus the plugin div changes but single-spa doesn't remount diff --git a/src/state/actions/scigateway.actions.tsx b/src/state/actions/scigateway.actions.tsx index 5746ccbe..18ef4055 100644 --- a/src/state/actions/scigateway.actions.tsx +++ b/src/state/actions/scigateway.actions.tsx @@ -5,6 +5,7 @@ import log from 'loglevel'; import { Step } from 'react-joyride'; import { Action, AnyAction } from 'redux'; import { ThunkAction } from 'redux-thunk'; +import * as singleSpa from 'single-spa'; import { AddHelpTourStepsPayload, AddHelpTourStepsType, @@ -18,15 +19,27 @@ import { ConfigureFeatureSwitchesType, ConfigureStringsPayload, ConfigureStringsType, + ContactUsAccessibilityFormUrlPayload, + CustomAdminPageDefaultTabPayload, + CustomAdminPageDefaultTabType, + CustomLogoPayload, + CustomLogoType, + CustomNavigationDrawerLogoPayload, + CustomNavigationDrawerLogoType, + CustomPrimaryColourPayload, + CustomPrimaryColourType, DismissNotificationPayload, DismissNotificationType, FeatureSwitches, FeatureSwitchesPayload, + HomepageUrlPayload, InitialiseAnalyticsType, InvalidateTokenType, LoadAuthProviderType, LoadDarkModePreferencePayload, LoadDarkModePreferenceType, + LoadHighContrastModePreferencePayload, + LoadHighContrastModePreferenceType, LoadMaintenanceStateType, LoadScheduledMaintenanceStateType, LoadedAuthType, @@ -34,8 +47,11 @@ import { MaintenanceState, MaintenanceStatePayLoad, NotificationType, + RegisterContactUsAccessibilityFormUrlType, RegisterHomepageUrlType, + RegisterRouteType, RequestPluginRerenderType, + ResetAuthStateType, ScheduledMaintenanceState, ScheduledMaintenanceStatePayLoad, SendThemeOptionsPayload, @@ -43,29 +59,13 @@ import { SignOutType, SiteLoadingPayload, SiteLoadingType, - HomepageUrlPayload, - CustomLogoPayload, ToggleDrawerType, ToggleHelpType, - RegisterRouteType, + baseAdminRoutes, scigatewayRoutes, - CustomLogoType, - LoadHighContrastModePreferenceType, - LoadHighContrastModePreferencePayload, - ResetAuthStateType, - CustomNavigationDrawerLogoPayload, - CustomNavigationDrawerLogoType, - CustomAdminPageDefaultTabPayload, - CustomAdminPageDefaultTabType, - RegisterContactUsAccessibilityFormUrlType, - ContactUsAccessibilityFormUrlPayload, - adminRoutes, - CustomPrimaryColourType, - CustomPrimaryColourPayload, } from '../scigateway.types'; import { ActionType, LogoState, StateType, ThunkResult } from '../state.types'; import loadMicroFrontends from './loadMicroFrontends'; -import * as singleSpa from 'single-spa'; export const configureStrings = ( appStrings: ApplicationStrings @@ -124,7 +124,7 @@ export const customNavigationDrawerLogo = ( }); export const customAdminPageDefaultTab = ( - adminPageDefaultTab: 'maintenance' | 'download' + adminPageDefaultTab: string ): ActionType => ({ type: CustomAdminPageDefaultTabType, payload: { @@ -342,11 +342,7 @@ export const configureSite = (): ThunkResult> => { dispatch(customPrimaryColour(settings['primaryColour'])); } - if ( - settings['adminPageDefaultTab'] && - (settings['adminPageDefaultTab'].includes('maintenance') || - settings['adminPageDefaultTab'].includes('download')) - ) { + if (settings['adminPageDefaultTab']) { dispatch(customAdminPageDefaultTab(settings['adminPageDefaultTab'])); } @@ -371,7 +367,7 @@ export const configureSite = (): ThunkResult> => { const currUrl = getState().router.location.pathname; if ( !Object.values(scigatewayRoutes).includes(currUrl) && - currUrl !== adminRoutes.maintenance && + currUrl !== baseAdminRoutes.maintenance && !getState().scigateway.plugins.find((p) => currUrl.startsWith(p.link.split('?')[0]) ) diff --git a/src/state/scigateway.types.tsx b/src/state/scigateway.types.tsx index 7a9f0d13..53004b08 100644 --- a/src/state/scigateway.types.tsx +++ b/src/state/scigateway.types.tsx @@ -55,9 +55,8 @@ export const scigatewayRoutes = { cookies: '/cookies', accessibility: '/accessibility', }; -export const adminRoutes = { +export const baseAdminRoutes = { maintenance: '/admin/maintenance', - download: '/admin/download', }; export interface NotificationPayload { @@ -99,7 +98,7 @@ export interface CustomNavigationDrawerLogoPayload { } export interface CustomAdminPageDefaultTabPayload { - adminPageDefaultTab: 'maintenance' | 'download'; + adminPageDefaultTab: string; } export interface CustomPrimaryColourPayload { diff --git a/src/state/state.types.tsx b/src/state/state.types.tsx index f6ecad05..018d0bca 100644 --- a/src/state/state.types.tsx +++ b/src/state/state.types.tsx @@ -1,14 +1,14 @@ -import { ThunkAction } from 'redux-thunk'; -import { AnyAction } from 'redux'; +import { RouterState } from 'connected-react-router'; import { Step } from 'react-joyride'; +import { AnyAction } from 'redux'; +import { ThunkAction } from 'redux-thunk'; import { ApplicationStrings, - PluginConfig, FeatureSwitches, - ScheduledMaintenanceState, MaintenanceState, + PluginConfig, + ScheduledMaintenanceState, } from './scigateway.types'; -import { RouterState } from 'connected-react-router'; export interface Plugin { name: string; @@ -41,7 +41,7 @@ export interface ScigatewayState { scheduledMaintenance: ScheduledMaintenanceState; maintenance: MaintenanceState; navigationDrawerLogo?: LogoState; - adminPageDefaultTab?: 'maintenance' | 'download'; + adminPageDefaultTab?: string; contactUsAccessibilityFormUrl?: string; primaryColour?: string; } From 0553a525b3718fb33060bc2e9839a02c1b163aa7 Mon Sep 17 00:00:00 2001 From: Joshua Kitenge Date: Mon, 11 Nov 2024 15:12:06 +0000 Subject: [PATCH 5/7] improve coverage #1418 --- src/adminPage/adminPage.component.test.tsx | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/adminPage/adminPage.component.test.tsx b/src/adminPage/adminPage.component.test.tsx index a929b6a7..76e32697 100644 --- a/src/adminPage/adminPage.component.test.tsx +++ b/src/adminPage/adminPage.component.test.tsx @@ -145,6 +145,55 @@ describe('Admin page component', () => { ).toBeInTheDocument(); }); + it("falls back to 'maintenance' when adminPageDefaultTab is not provided", () => { + state.scigateway.adminPageDefaultTab = undefined; + history.replace('/admin'); + + render(, { wrapper: Wrapper }); + + // Assert that the `maintenance` tab is selected by default + expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + }); + + it("falls back to 'maintenance' when on an invalid route", () => { + state.scigateway.plugins = [ + { + order: 1, + plugin: 'datagateway-download', + link: '/admin/download', + section: 'Admin', + displayName: 'Admin Download', + admin: true, + }, + ]; + state.scigateway.adminPageDefaultTab = 'maintenance'; + history.replace('/admin/test'); + + render(, { wrapper: Wrapper }); + + // Assert that the `maintenance` tab is selected by default + expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + }); + + it("falls back to 'maintenance' when adminPageDefaultTab doesn't match any key in adminRoutes", () => { + state.scigateway.adminPageDefaultTab = 'nonexistentTab'; + history.replace('/admin'); + + render(, { wrapper: Wrapper }); + + // Assert that the `maintenance` tab is selected by default + expect(screen.getByRole('tab', { name: 'Maintenance' })).toHaveAttribute( + 'aria-selected', + 'true' + ); + }); + it('should return an empty object when given an empty plugins array', () => { const plugins = []; const result = getAdminPluginRoutes({ plugins }); From 212de213b5e8b3d32ee009dfe0b171b74b6908ef Mon Sep 17 00:00:00 2001 From: Joel Davies Date: Mon, 18 Nov 2024 14:36:06 +0000 Subject: [PATCH 6/7] Add docker image push when tags pushed #1390 --- .github/workflows/ci-build.yml | 18 +++++----- .github/workflows/docker-release-build.yml | 40 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/docker-release-build.yml diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 7ce396ac..db19ee0f 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -38,32 +38,32 @@ jobs: run: yarn e2e docker: - # This job triggers only if all the other jobs succeed. It builds the Docker image and if successful, - # it pushes it to Harbor. + # This job triggers only if all the other jobs succeed. It builds the Docker image to ensure it builds correctly. needs: [test] name: Docker runs-on: ubuntu-20.04 steps: - name: Checkout repo - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Login to Harbor - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 with: registry: ${{ secrets.HARBOR_URL }} username: ${{ secrets.HARBOR_USERNAME }} password: ${{ secrets.HARBOR_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata (tags, labels, annotations) for Docker id: meta - uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175 # v4.6.0 + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 with: images: ${{ secrets.HARBOR_URL }}/scigateway - - name: Build and push Docker image to Harbor - uses: docker/build-push-action@0a97817b6ade9f46837855d676c4cca3a2471fc9 # v4.2.1 + - name: Build Docker image + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 with: context: . - push: true + push: false tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} diff --git a/.github/workflows/docker-release-build.yml b/.github/workflows/docker-release-build.yml new file mode 100644 index 00000000..307c66e2 --- /dev/null +++ b/.github/workflows/docker-release-build.yml @@ -0,0 +1,40 @@ +name: Docker Release Build +on: + push: + tags: '*' + +jobs: + docker: + # This job builds the Docker image and if successful, it pushes it to Harbor. + name: Docker + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Login to Harbor + uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 + with: + registry: ${{ secrets.HARBOR_URL }} + username: ${{ secrets.HARBOR_USERNAME }} + password: ${{ secrets.HARBOR_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 + with: + images: ${{ secrets.HARBOR_URL }}/scigateway + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=ref,event=tag,pattern={{ref}} + + - name: Build and push Docker image to Harbor + uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + annotations: ${{ steps.meta.outputs.annotations }} From c361ee18f59b61d68797b718fc51c250062095fe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:18:15 +0000 Subject: [PATCH 7/7] Bump cross-spawn from 7.0.3 to 7.0.6 Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4bdda75e..dd1f7258 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5813,13 +5813,13 @@ __metadata: linkType: hard "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": - version: 7.0.3 - resolution: "cross-spawn@npm:7.0.3" + version: 7.0.6 + resolution: "cross-spawn@npm:7.0.6" dependencies: path-key: "npm:^3.1.0" shebang-command: "npm:^2.0.0" which: "npm:^2.0.1" - checksum: 10/e1a13869d2f57d974de0d9ef7acbf69dc6937db20b918525a01dacb5032129bd552d290d886d981e99f1b624cb03657084cc87bd40f115c07ecf376821c729ce + checksum: 10/0d52657d7ae36eb130999dffff1168ec348687b48dd38e2ff59992ed916c88d328cf1d07ff4a4a10bc78de5e1c23f04b306d569e42f7a2293915c081e4dfee86 languageName: node linkType: hard