From b7e13382c4e834786f65e760f2b48499300f0a20 Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Mon, 14 Nov 2022 13:30:57 -0800 Subject: [PATCH 1/5] CEC-3119 Magna access (#235) * CEC-3119 Magna access * Clean up * Update test provider --- .env.cec-prd | 2 + .env.dev | 4 +- .env.local | 3 +- .env.prd | 2 + .env.stg | 4 +- .env.template | 4 +- package-lock.json | 77 ++- package.json | 10 +- src/components/App/App.test.js | 30 +- .../App/__snapshots__/App.test.js.snap | 476 +++++++++++++++--- src/components/CANFilter/Add/index.test.jsx | 5 +- .../Table/__snapshots__/index.test.jsx.snap | 51 -- src/components/CANFilter/Table/index.jsx | 69 +-- src/components/CANFilter/Table/index.test.jsx | 4 +- .../CANFilter/Update/index.test.jsx | 4 +- src/components/Cars/Add/index.test.jsx | 4 +- .../List/__snapshots__/index.test.jsx.snap | 17 +- src/components/Cars/List/index.jsx | 62 ++- src/components/Cars/List/index.test.jsx | 4 +- .../Cars/Status/CANFiltersTab.test.jsx | 4 +- .../Cars/Status/CANSignalsTab.test.jsx | 4 +- .../Cars/Status/CarUpdatesTab.test.jsx | 4 +- .../Details/__snapshots__/index.test.jsx.snap | 17 - src/components/Cars/Status/Details/index.jsx | 107 ++-- .../Cars/Status/Details/index.test.jsx | 4 +- .../Cars/Status/DetailsTab.test.jsx | 4 +- .../Cars/Status/DigitalTwinTab.test.jsx | 4 +- src/components/Cars/Status/FleetsTab.test.jsx | 4 +- .../Cars/Status/RemoteCommandsTab.test.jsx | 4 +- .../__snapshots__/CANFiltersTab.test.jsx.snap | 51 -- .../__snapshots__/DetailsTab.test.jsx.snap | 17 - .../Status/__snapshots__/index.test.jsx.snap | 22 - src/components/Cars/Status/index.jsx | 74 +-- src/components/Cars/Status/index.test.jsx | 4 +- src/components/Cars/Update/index.test.jsx | 4 +- src/components/Contexts/UserContext.jsx | 6 +- .../Contexts/__mocks__/UserContext.jsx | 6 +- .../CarUpdateStatusTable.test.jsx | 4 +- .../Controls/CarUpdatesTable/index.jsx | 30 +- .../RoleWrap/__snapshots__/index.test.js.snap | 23 + src/components/Controls/RoleWrap/index.js | 19 + .../Controls/RoleWrap/index.test.js | 70 +++ src/components/Fleets/Add/index.test.jsx | 4 +- .../Status/CANFilters/Add/index.test.jsx | 4 +- .../Table/__snapshots__/index.test.jsx.snap | 51 -- .../Fleets/Status/CANFilters/Table/index.jsx | 77 +-- .../Status/CANFilters/Table/index.test.jsx | 4 +- .../Status/CANFilters/Update/index.test.jsx | 4 +- .../Fleets/Status/CANFiltersTab.test.jsx | 4 +- .../Fleets/Status/Details/index.test.jsx | 4 +- .../Fleets/Status/DetailsTab.test.jsx | 4 +- .../Fleets/Status/Vehicles/Add/index.test.jsx | 4 +- .../Table/__snapshots__/index.test.jsx.snap | 63 +-- .../Fleets/Status/Vehicles/Table/index.jsx | 71 ++- .../Status/Vehicles/Table/index.test.jsx | 4 +- .../Fleets/Status/VehiclesTab.test.jsx | 4 +- .../__snapshots__/CANFiltersTab.test.jsx.snap | 51 -- .../__snapshots__/VehiclesTab.test.jsx.snap | 63 +-- src/components/Fleets/Status/index.test.jsx | 4 +- src/components/Fleets/Table/index.test.jsx | 4 +- src/components/Fleets/Update/index.test.jsx | 4 +- src/components/Layouts/SideMenu.jsx | 61 +-- src/components/Layouts/SideMenu.test.jsx | 21 +- .../__snapshots__/SideMenu.test.jsx.snap | 85 ++++ .../Manifest/Details/index.test.jsx | 4 +- src/components/Manifest/List/index.jsx | 69 +-- src/components/Manifest/Status/index.jsx | 45 +- src/components/Manifest/Update/index.jsx | 4 +- src/components/Manifest/Update/index.test.jsx | 6 +- src/components/Routes/AuthRoute.jsx | 7 +- src/components/Routes/SiteRoutes.jsx | 77 ++- .../Suppliers/Details/index.test.js | 4 +- src/components/Suppliers/List/index.test.jsx | 4 +- src/utils/roles.js | 63 ++- src/utils/roles.test.js | 30 +- src/utils/testing.js | 8 + 76 files changed, 1316 insertions(+), 918 deletions(-) create mode 100644 src/components/Controls/RoleWrap/__snapshots__/index.test.js.snap create mode 100644 src/components/Controls/RoleWrap/index.js create mode 100644 src/components/Controls/RoleWrap/index.test.js diff --git a/.env.cec-prd b/.env.cec-prd index b9a9c92..b290f2f 100644 --- a/.env.cec-prd +++ b/.env.cec-prd @@ -3,3 +3,5 @@ REACT_APP_CERT_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/certificate REACT_APP_UPLOAD_SERVICE_URL=https://gw.cec-prd.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cec-prd.fiskerinc.com REACT_APP_SUPERSET_URL=https://superset.cec-prd.fiskerinc.com +REACT_APP_MAGNA_PROVIDER=Magna +REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe diff --git a/.env.dev b/.env.dev index fb00db4..f3731e4 100644 --- a/.env.dev +++ b/.env.dev @@ -2,4 +2,6 @@ REACT_APP_CERT_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/certificate REACT_APP_AUTH_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/compute_auth REACT_APP_UPLOAD_SERVICE_URL=https://dev-gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com -REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com \ No newline at end of file +REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com +REACT_APP_MAGNA_PROVIDER=Fisker-QA +REACT_APP_MAGNA_GROUP_ID=efcc3025-e2d8-4212-8227-805c7be39d2c diff --git a/.env.local b/.env.local index 8647c32..3a54e33 100644 --- a/.env.local +++ b/.env.local @@ -3,4 +3,5 @@ REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com -REACT_APP_SUPERSET_KEYS_LIST=0b01ac72-9ef4-4a5f-be34-b1ac0bf972cc +REACT_APP_MAGNA_PROVIDER=Fisker-QA +REACT_APP_MAGNA_GROUP_ID=efcc3025-e2d8-4212-8227-805c7be39d2c \ No newline at end of file diff --git a/.env.prd b/.env.prd index d8ce7fe..a818e1d 100644 --- a/.env.prd +++ b/.env.prd @@ -3,3 +3,5 @@ REACT_APP_CERT_SERVICE_URL=https://gw.cloud.fiskerinc.com/certificate REACT_APP_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com REACT_APP_SUPERSET_URL=https://superset.cloud.fiskerinc.com +REACT_APP_MAGNA_PROVIDER=Magna +REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe diff --git a/.env.stg b/.env.stg index 8d545cf..016f158 100644 --- a/.env.stg +++ b/.env.stg @@ -2,4 +2,6 @@ REACT_APP_AUTH_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/compute_auth REACT_APP_CERT_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/certificate REACT_APP_UPLOAD_SERVICE_URL=https://stg-gw.cloud.fiskerinc.com/ota_update REACT_APP_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com -REACT_APP_SUPERSET_URL=https://stg-superset.cloud.fiskerinc.com \ No newline at end of file +REACT_APP_SUPERSET_URL=https://stg-superset.cloud.fiskerinc.com +REACT_APP_MAGNA_PROVIDER=Fisker-QA +REACT_APP_MAGNA_GROUP_ID=efcc3025-e2d8-4212-8227-805c7be39d2c diff --git a/.env.template b/.env.template index eb5a173..362d6d9 100644 --- a/.env.template +++ b/.env.template @@ -2,4 +2,6 @@ REACT_APP_AUTH_SERVICE_URL=http://localhost/compute_auth REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com -REACT_APP_CERT_SERVICE_URL=http://localhost/certificate \ No newline at end of file +REACT_APP_CERT_SERVICE_URL=http://localhost/certificate +REACT_APP_MAGNA_PROVIDER=Magna +REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe diff --git a/package-lock.json b/package-lock.json index 7211f78..f858b27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,7 +37,8 @@ "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.0", "usehooks-ts": "^2.7.1", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "webpack": "^5.74.0" }, "devDependencies": { "react-test-renderer": "^17.0.2" @@ -5607,9 +5608,9 @@ } }, "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", "bin": { "acorn": "bin/acorn" }, @@ -8028,9 +8029,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -11602,11 +11603,6 @@ "node": ">=4" } }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -16635,9 +16631,9 @@ } }, "node_modules/watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -16668,33 +16664,33 @@ } }, "node_modules/webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "bin": { @@ -21520,9 +21516,9 @@ } }, "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" }, "acorn-globals": { "version": "6.0.0", @@ -23304,9 +23300,9 @@ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "enhanced-resolve": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", - "integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", "requires": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -25886,11 +25882,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -29497,9 +29488,9 @@ } }, "watchpack": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", - "integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "requires": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -29524,33 +29515,33 @@ "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" }, "webpack": { - "version": "5.70.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", - "integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", + "version": "5.74.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", + "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", "requires": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^0.0.51", "@webassemblyjs/ast": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.4.1", + "acorn": "^8.7.1", "acorn-import-assertions": "^1.7.6", "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.9.2", + "enhanced-resolve": "^5.10.0", "es-module-lexer": "^0.9.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.9", - "json-parse-better-errors": "^1.0.2", + "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^3.1.0", "tapable": "^2.1.1", "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.3.1", + "watchpack": "^2.4.0", "webpack-sources": "^3.2.3" }, "dependencies": { diff --git a/package.json b/package.json index 18caa41..0ab9581 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.0", "usehooks-ts": "^2.7.1", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "webpack": "^5.74.0" }, "scripts": { "start": "env-cmd -f .env.local react-scripts start", @@ -51,7 +52,10 @@ "extends": [ "react-app", "react-app/jest" - ] + ], + "rules": { + "no-console": "warn" + } }, "browserslist": [ ">0.2%", @@ -82,4 +86,4 @@ "lcov" ] } -} \ No newline at end of file +} diff --git a/src/components/App/App.test.js b/src/components/App/App.test.js index 24f282c..3346d96 100644 --- a/src/components/App/App.test.js +++ b/src/components/App/App.test.js @@ -16,7 +16,7 @@ import { waitForElementToBeRemoved, } from "@testing-library/react"; import { setToken } from "../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../utils/testing"; +import {TEST_AUTH_OBJECT, TEST_AUTH_OBJECT_FISKER} from "../../utils/testing"; import App from "."; import addSnapshotSerializer from "../../utils/snapshot"; @@ -35,7 +35,7 @@ const check = async (path, selector, compare) => { let container await act(async () => { container = await renderRoute(path); - }) + }); expect(container.querySelector(selector).innerHTML).toEqual(compare); expect(container).toMatchSnapshot(); }; @@ -117,57 +117,57 @@ describe("App", () => { await check("/page-not-found", "h1", "Page Not Found"); }); it("Route / authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await sleepAndCheck("/", "h6", "Home"); }); it("Route /home authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await sleepAndCheck("/home", "h6", "Home"); }); it("Route /page-not-found authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/page-not-found", "h1", "Page Not Found"); }); it("Route /packages authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/packages", "h6", "Deployments"); }); it("Route /package-status authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/package-status/1", "h6", "Manifest Test Manifest 1.0"); }); it("Route /package-deploy authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/package-deploy/1", "h6", "Deploy Test Manifest 1.0"); }); it("Route /vehicle-add authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/vehicle-add", "h6", "Add Vehicle"); }); it("Route /vehicles authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/vehicles", "h6", "Vehicles"); }); it("Route /vehicle-status authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details"); }); it("Route /tools/certificates/add authenticated", async () => { - setToken(TEST_AUTH_OBJECT); - await check("/tools/certificates/add", "h6", "Create Certificate"); + setToken(TEST_AUTH_OBJECT_FISKER); + await sleepAndCheck("/tools/certificates/add", "h6", "Create Certificate"); }); it("Route /tools/sms/send authenticated", async () => { - setToken(TEST_AUTH_OBJECT); - await check("/tools/sms/send", "h6", "Send SMS"); + setToken(TEST_AUTH_OBJECT_FISKER); + await sleepAndCheck("/tools/sms/send", "h6", "Send SMS"); }); }); diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 4d7e87c..91462b2 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -35,7 +35,7 @@ exports[`App Route / authenticated 1`] = ` - John + Anvar +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - - - - - @@ -3729,7 +3890,7 @@ exports[`App Route /page-not-found authenticated 1`] = ` - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • - John + Anvar
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • + />
    diff --git a/src/components/CANFilter/Add/index.test.jsx b/src/components/CANFilter/Add/index.test.jsx index 6bd1813..7afca84 100644 --- a/src/components/CANFilter/Add/index.test.jsx +++ b/src/components/CANFilter/Add/index.test.jsx @@ -1,3 +1,5 @@ +import {TEST_AUTH_OBJECT_FISKER} from "../../../utils/testing"; + jest.mock("../../Contexts/CANFiltersContext"); jest.mock("../../Contexts/StatusContext"); jest.mock("../../Contexts/UserContext"); @@ -8,7 +10,6 @@ import { BrowserRouter } from "react-router-dom"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -35,7 +36,7 @@ describe("CANFiltersAdd", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANFiltersAdd(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap b/src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap index 00fb3e3..010dd2a 100644 --- a/src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap +++ b/src/components/CANFilter/Table/__snapshots__/index.test.jsx.snap @@ -250,23 +250,6 @@ exports[`CANFiltersTable Render 1`] = ` /> - - - - - - - - - diff --git a/src/components/CANFilter/Table/index.jsx b/src/components/CANFilter/Table/index.jsx index 9068090..5156f0b 100644 --- a/src/components/CANFilter/Table/index.jsx +++ b/src/components/CANFilter/Table/index.jsx @@ -1,5 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { Link } from 'react-router-dom'; import { Grid, Table, @@ -12,46 +10,46 @@ import { } from "@material-ui/core"; import AddCircleIcon from "@material-ui/icons/AddCircle"; import DeleteIcon from "@material-ui/icons/Delete"; -import EditIcon from '@material-ui/icons/Edit'; +import EditIcon from "@material-ui/icons/Edit"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { logger } from "../../../services/monitoring"; +import { hasRole, Permissions } from "../../../utils/roles"; +import { trimIfMoreThan } from "../../../utils/strings"; import { CANFiltersProvider, useCANFiltersContext, } from "../../Contexts/CANFiltersContext"; -import TableHeaderSortable from "../../Table/HeaderSortable"; -import { - useUserContext -} from "../../Contexts/UserContext" import { useStatusContext } from "../../Contexts/StatusContext"; -import useStyles from "../../useStyles"; +import { useUserContext } from "../../Contexts/UserContext"; import SearchField from "../../Controls/SearchField"; -import { logger } from "../../../services/monitoring"; -import { Roles, hasRole } from "../../../utils/roles"; -import {useLocalStorage} from "../../useLocalStorage"; -import {trimIfMoreThan} from "../../../utils/strings"; +import TableHeaderSortable from "../../Table/HeaderSortable"; +import { useLocalStorage } from "../../useLocalStorage"; +import useStyles from "../../useStyles"; const tableColumns = [ { id: "can_id", - label: "CAN ID" + label: "CAN ID", }, { id: "interval", - label: "Interval (ms)" + label: "Interval (ms)", }, { id: "fleet", - label: "Fleet" + label: "Fleet", }, { id: "edge_mask", - label: "Edge mask" + label: "Edge mask", }, { id: "", - label: "Actions" - } + label: "Actions", + }, ]; const PAGE_SIZE = "CAN_FILTER_TABLE_PAGE_SIZE"; @@ -63,9 +61,16 @@ const MainForm = ({ vin }) => { const [pageIndex, setPageIndex] = useState(0); const [orderBy, setOrderBy] = useState("id"); const [order, setOrder] = useState("desc"); - const { getFilters, deleteFilter, filters, totalFilters } = useCANFiltersContext(); + const { getFilters, deleteFilter, filters, totalFilters } = + useCANFiltersContext(); const { setMessage } = useStatusContext(); - const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + groups, + providers, + } = useUserContext(); useEffect(() => { (async () => { @@ -118,7 +123,7 @@ const MainForm = ({ vin }) => { const onDelete = async (can_id) => { try { await deleteFilter(vin, can_id, token); - setMessage(`Deleted ${can_id}`) + setMessage(`Deleted ${can_id}`); } catch (e) { setMessage(e.message); logger.warn(e.stack); @@ -126,24 +131,24 @@ const MainForm = ({ vin }) => { }; const Actions = (row) => { - if(row.fleet != null) { - return + if (row.fleet != null) { + return; } let actions = []; - if (hasRole([Roles.CREATE], groups)) { + if (hasRole(groups, Permissions.FiskerCreate, providers)) { actions.push({ tip: `Update "${row.can_id}"`, link: `/filter-update?vin=${vin}&can_id=${row.can_id}&interval=${row.interval}`, - icon: + icon: , }); } - if (hasRole([Roles.DELETE], groups)) { + if (hasRole(groups, Permissions.FiskerDelete, providers)) { actions.push({ tip: `Delete "${row.can_id}"`, id: row.can_id, - icon: - }) + icon: , + }); } if (actions.length === 0) return ["No actions"]; @@ -177,7 +182,7 @@ const MainForm = ({ vin }) => { - + @@ -196,7 +201,9 @@ const MainForm = ({ vin }) => { {row.fleet} - {trimIfMoreThan(row.edge_mask, 15, "...")} + + {trimIfMoreThan(row.edge_mask, 15, "...")} + {Actions(row)} ))} @@ -219,7 +226,7 @@ const MainForm = ({ vin }) => {
    -
    + ); }; diff --git a/src/components/CANFilter/Table/index.test.jsx b/src/components/CANFilter/Table/index.test.jsx index 40e3102..2642dc1 100644 --- a/src/components/CANFilter/Table/index.test.jsx +++ b/src/components/CANFilter/Table/index.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import {TEST_AUTH_OBJECT_FISKER} from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -37,7 +37,7 @@ describe("CANFiltersTable", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANFiltersTable(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/CANFilter/Update/index.test.jsx b/src/components/CANFilter/Update/index.test.jsx index e1e264d..a38cb35 100644 --- a/src/components/CANFilter/Update/index.test.jsx +++ b/src/components/CANFilter/Update/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -34,7 +34,7 @@ describe("CANFiltersUpdate", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANFiltersUpdate(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Add/index.test.jsx b/src/components/Cars/Add/index.test.jsx index bb8c506..1b7e753 100644 --- a/src/components/Cars/Add/index.test.jsx +++ b/src/components/Cars/Add/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { VehicleProvider } from "../../Contexts/VehicleContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -36,7 +36,7 @@ describe("VehicleAddForm", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderVehicleAdd(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/List/__snapshots__/index.test.jsx.snap b/src/components/Cars/List/__snapshots__/index.test.jsx.snap index cc2b5f0..50c7849 100644 --- a/src/components/Cars/List/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/List/__snapshots__/index.test.jsx.snap @@ -22,22 +22,7 @@ exports[`VehicleTable Render 1`] = ` > + />
    diff --git a/src/components/Cars/List/index.jsx b/src/components/Cars/List/index.jsx index 0dbc101..db72783 100644 --- a/src/components/Cars/List/index.jsx +++ b/src/components/Cars/List/index.jsx @@ -1,16 +1,18 @@ -import React, { useEffect, useState } from "react"; import { Checkbox, FormControlLabel, Grid, MenuItem } from "@material-ui/core"; import AddCircleIcon from "@material-ui/icons/AddCircle"; -import { Link } from "react-router-dom"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; -import { VehicleProvider } from "../../Contexts/VehicleContext"; -import { useUserContext } from "../../Contexts/UserContext"; +import { Permissions } from "../../../utils/roles"; import { useStatusContext } from "../../Contexts/StatusContext"; -import useStyles from "../../useStyles"; -import SearchField from "../../Controls/SearchField"; +import { useUserContext } from "../../Contexts/UserContext"; +import { VehicleProvider } from "../../Contexts/VehicleContext"; import CarSelectionTable from "../../Controls/CarSelectionTable"; import OptionsDropdown from "../../Controls/OptionsDropdown"; +import { RoleWrap } from "../../Controls/RoleWrap"; +import SearchField from "../../Controls/SearchField"; +import useStyles from "../../useStyles"; const MainForm = () => { const classes = useStyles(); @@ -22,6 +24,8 @@ const MainForm = () => { token: { idToken: { jwtToken: token }, }, + groups, + providers, } = useUserContext(); const handleSearch = (query) => { @@ -30,11 +34,11 @@ const MainForm = () => { const handleOnline = (event) => { setOnline(event.target.checked); - } + }; const handleOnlineHMI = (event) => { setOnlineHMI(event.target.checked); - } + }; useEffect(() => { setTitle("Vehicles"); @@ -46,30 +50,34 @@ const MainForm = () => {
    - - - + + + + + - + - - } label="Only online" /> + } + label="Only online" + /> - - } label="Only online HMI" /> + + } + label="Only online HMI" + /> @@ -79,7 +87,11 @@ const MainForm = () => { classes={classes} token={token} multiSelect={false} - search={{ search, online: online? true : null, online_hmi: onlineHMI? true : null }} + search={{ + search, + online: online ? true : null, + online_hmi: onlineHMI ? true : null, + }} />
    ); diff --git a/src/components/Cars/List/index.test.jsx b/src/components/Cars/List/index.test.jsx index 08b6335..e45d166 100644 --- a/src/components/Cars/List/index.test.jsx +++ b/src/components/Cars/List/index.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { VehicleProvider } from "../../Contexts/VehicleContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -39,7 +39,7 @@ describe("VehicleTable", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderVehicleTable(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/CANFiltersTab.test.jsx b/src/components/Cars/Status/CANFiltersTab.test.jsx index f7ac32d..518ff0f 100644 --- a/src/components/Cars/Status/CANFiltersTab.test.jsx +++ b/src/components/Cars/Status/CANFiltersTab.test.jsx @@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import { setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import CANFiltersTab from "./CANFiltersTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -31,7 +31,7 @@ describe("CANFiltersTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANFiltersTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/CANSignalsTab.test.jsx b/src/components/Cars/Status/CANSignalsTab.test.jsx index 2747b69..f50b265 100644 --- a/src/components/Cars/Status/CANSignalsTab.test.jsx +++ b/src/components/Cars/Status/CANSignalsTab.test.jsx @@ -11,7 +11,7 @@ import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import CANSignalsTab from "./CANSignalsTab"; import {setToken} from "../../Contexts/UserContext"; -import {TEST_AUTH_OBJECT} from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; const renderCANSignalsTab = async () => { const { container } = render( @@ -31,7 +31,7 @@ describe("CANSignalsTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANSignalsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/CarUpdatesTab.test.jsx b/src/components/Cars/Status/CarUpdatesTab.test.jsx index 291be4b..1251721 100644 --- a/src/components/Cars/Status/CarUpdatesTab.test.jsx +++ b/src/components/Cars/Status/CarUpdatesTab.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./CarUpdatesTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -39,7 +39,7 @@ describe("CarUpdatesTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCarUpdatesTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap index d38148b..bf46c26 100644 --- a/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/Details/__snapshots__/index.test.jsx.snap @@ -101,23 +101,6 @@ exports[`VehicleDetailsTab Render 1`] = ` /> - - -
    diff --git a/src/components/Cars/Status/Details/index.jsx b/src/components/Cars/Status/Details/index.jsx index 7692078..bfb1c77 100644 --- a/src/components/Cars/Status/Details/index.jsx +++ b/src/components/Cars/Status/Details/index.jsx @@ -1,28 +1,36 @@ +import { Grid, Tooltip } from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; +import EditIcon from "@material-ui/icons/Edit"; +import clsx from "clsx"; import React, { useEffect, useState } from "react"; import { Redirect } from "react-router"; -import { Link } from 'react-router-dom'; -import { - Grid, - Tooltip, -} from "@material-ui/core"; -import EditIcon from "@material-ui/icons/Edit" -import DeleteIcon from "@material-ui/icons/Delete"; -import clsx from "clsx"; +import { Link } from "react-router-dom"; -import { useUserContext } from "../../../Contexts/UserContext" -import { useStatusContext } from "../../../Contexts/StatusContext"; -import useStyles from "../../../useStyles"; import { logger } from "../../../../services/monitoring"; -import { useVehicleContext, VehicleProvider } from "../../../Contexts/VehicleContext"; +import { Permissions } from "../../../../utils/roles"; +import { useStatusContext } from "../../../Contexts/StatusContext"; +import { useUserContext } from "../../../Contexts/UserContext"; +import { + useVehicleContext, + VehicleProvider +} from "../../../Contexts/VehicleContext"; +import { RoleWrap } from "../../../Controls/RoleWrap"; import DeleteConfirmation from "../../../DeleteConfirmation"; +import useStyles from "../../../useStyles"; const MainForm = ({ vin }) => { const classes = useStyles(); const { setMessage } = useStatusContext(); const { vehicle, getVehicle, deleteVehicle } = useVehicleContext(); const [redirect, setRedirect] = useState(null); - const [showDeleteModal, setShowDeleteModal] = useState(false) - const { token: { idToken: { jwtToken: token } } } = useUserContext(); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const { + token: { + idToken: { jwtToken: token }, + }, + groups, + providers, + } = useUserContext(); useEffect(() => { (async () => { @@ -40,7 +48,7 @@ const MainForm = ({ vin }) => { const onDelete = async () => { try { await deleteVehicle(vin, token); - setMessage(`Deleted ${vin}`) + setMessage(`Deleted ${vin}`); setRedirect(`/vehicles`); } catch (e) { setMessage(e.message); @@ -56,36 +64,67 @@ const MainForm = ({ vin }) => {
    -

    VIN: {vin}

    +

    + VIN: {vin} +

    {vehicle.log_level != null && ( -

    Log Level: {vehicle.log_level}

    +

    + Log Level: {vehicle.log_level} +

    )}
    {vehicle.canbus && ( CANBus -

    Enabled: {vehicle.canbus.enabled.toString()}

    -

    Max Memory Buffer Size: {vehicle.canbus.max_mem_buffer_size ?? "Default"}

    -

    Enabled: {vehicle.canbus.data_logger_enabled.toString()}

    -

    Max Disk Buffer Size: {vehicle.canbus.max_disk_buffer_size ?? "Default"}

    -

    Filters: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}

    +

    + Enabled: {vehicle.canbus.enabled.toString()} +

    +

    + Max Memory Buffer Size: {vehicle.canbus.max_mem_buffer_size ?? "Default"} +

    +

    + Enabled: {vehicle.canbus.data_logger_enabled.toString()} +

    +

    + Max Disk Buffer Size: {vehicle.canbus.max_disk_buffer_size ?? "Default"} +

    +

    + Filters: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0} +

    )} - - - - - - - setShowDeleteModal(true)}> - - - + + + + + + + + + + setShowDeleteModal(true)}> + + + +
    - setShowDeleteModal(false)} deleteFunction={onDelete}/> -
    + setShowDeleteModal(false)} + deleteFunction={onDelete} + /> +
    ); }; diff --git a/src/components/Cars/Status/Details/index.test.jsx b/src/components/Cars/Status/Details/index.test.jsx index 708c415..b571949 100644 --- a/src/components/Cars/Status/Details/index.test.jsx +++ b/src/components/Cars/Status/Details/index.test.jsx @@ -9,7 +9,7 @@ import routeData from "react-router"; import { VehicleProvider } from "../../../Contexts/VehicleContext"; import { StatusProvider } from "../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../utils/snapshot"; @@ -42,7 +42,7 @@ describe("VehicleDetailsTab", () => { }) it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderVehicleDetailsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/DetailsTab.test.jsx b/src/components/Cars/Status/DetailsTab.test.jsx index 4057a10..9ed2660 100644 --- a/src/components/Cars/Status/DetailsTab.test.jsx +++ b/src/components/Cars/Status/DetailsTab.test.jsx @@ -11,7 +11,7 @@ import { MemoryRouter, Route } from "react-router-dom"; import { VehicleProvider } from "../../Contexts/VehicleContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./DetailsTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -41,7 +41,7 @@ describe("DetailsTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderDetailsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/DigitalTwinTab.test.jsx b/src/components/Cars/Status/DigitalTwinTab.test.jsx index aa9c27a..3e62c6d 100644 --- a/src/components/Cars/Status/DigitalTwinTab.test.jsx +++ b/src/components/Cars/Status/DigitalTwinTab.test.jsx @@ -10,7 +10,7 @@ import { render, waitFor } from "@testing-library/react"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import DigitalTwinTab from "./DigitalTwinTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -34,7 +34,7 @@ describe("DigitalTwinTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderDetailsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/FleetsTab.test.jsx b/src/components/Cars/Status/FleetsTab.test.jsx index 58fad88..283f859 100644 --- a/src/components/Cars/Status/FleetsTab.test.jsx +++ b/src/components/Cars/Status/FleetsTab.test.jsx @@ -12,7 +12,7 @@ import { BrowserRouter } from "react-router-dom"; import {StatusProvider} from "../../Contexts/StatusContext"; import {VehicleProvider} from "../../Contexts/VehicleContext"; import {setToken, UserProvider} from "../../Contexts/UserContext"; -import {TEST_AUTH_OBJECT} from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import addSnapshotSerializer from "../../../utils/snapshot"; import FleetsTab from "./FleetsTab"; @@ -40,7 +40,7 @@ describe("FleetsTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/RemoteCommandsTab.test.jsx b/src/components/Cars/Status/RemoteCommandsTab.test.jsx index c1d7882..1644aa7 100644 --- a/src/components/Cars/Status/RemoteCommandsTab.test.jsx +++ b/src/components/Cars/Status/RemoteCommandsTab.test.jsx @@ -10,7 +10,7 @@ import {StatusProvider} from "../../Contexts/StatusContext"; import {setToken, UserProvider} from "../../Contexts/UserContext"; import RemoteCommandsTab from "./RemoteCommandsTab"; import addSnapshotSerializer from "../../../utils/snapshot"; -import {TEST_AUTH_OBJECT} from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import React from "react"; const renderRemoteCommandsTab = async () => { @@ -33,7 +33,7 @@ describe("RemoteCommandsTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderRemoteCommandsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Status/__snapshots__/CANFiltersTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/CANFiltersTab.test.jsx.snap index da44961..77fde3d 100644 --- a/src/components/Cars/Status/__snapshots__/CANFiltersTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/CANFiltersTab.test.jsx.snap @@ -249,23 +249,6 @@ exports[`CANFiltersTab Render 1`] = ` /> - - - - - - - - - diff --git a/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap b/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap index c2a24b3..d23af12 100644 --- a/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/DetailsTab.test.jsx.snap @@ -109,23 +109,6 @@ exports[`DetailsTab Render 1`] = ` /> - - -
    diff --git a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap index 87bafee..1954a3f 100644 --- a/src/components/Cars/Status/__snapshots__/index.test.jsx.snap +++ b/src/components/Cars/Status/__snapshots__/index.test.jsx.snap @@ -67,7 +67,6 @@ exports[`CarStatus Render 1`] = `
    diff --git a/src/components/Cars/Status/index.jsx b/src/components/Cars/Status/index.jsx index bed8b79..48c37b9 100644 --- a/src/components/Cars/Status/index.jsx +++ b/src/components/Cars/Status/index.jsx @@ -1,19 +1,22 @@ +import { Box, Tab, Tabs } from "@material-ui/core"; +import clsx from "clsx"; import React, { useEffect } from "react"; import { useParams } from "react-router"; import { useLocation } from "react-router-dom"; -import clsx from "clsx"; -import { Box, Tab, Tabs } from "@material-ui/core"; -import CarDetailsTab from "./DetailsTab"; -import CarUpdatesTab from "./CarUpdatesTab"; -import CANFiltersTab from "./CANFiltersTab"; -import DigitalTwinTab from "./DigitalTwinTab"; -import TabPanel from "../../Controls/TabPanel"; +import { Permissions } from "../../../utils/roles"; import { useStatusContext } from "../../Contexts/StatusContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import { RoleWrap } from "../../Controls/RoleWrap"; +import TabPanel from "../../Controls/TabPanel"; import useStyles from "../../useStyles"; +import CANFiltersTab from "./CANFiltersTab"; import CANSignalsTab from "./CANSignalsTab"; -import RemoteCommandsTab from "./RemoteCommandsTab"; +import CarUpdatesTab from "./CarUpdatesTab"; +import CarDetailsTab from "./DetailsTab"; +import DigitalTwinTab from "./DigitalTwinTab"; import FleetsTab from "./FleetsTab"; +import RemoteCommandsTab from "./RemoteCommandsTab"; const tabHashes = ["details", "updates", "filters"]; @@ -24,6 +27,8 @@ const CarStatus = () => { const { hash } = useLocation(); const [tabIndex, setTabIndex] = React.useState(0); + const { groups, providers } = useUserContext(); + useEffect(() => { const key = hash.replace("#", ""); const index = tabHashes.findIndex((element) => element === key); @@ -63,12 +68,18 @@ const CarStatus = () => { > - - - - - + + + + + + + @@ -80,25 +91,30 @@ const CarStatus = () => { - - - + + + + + + + - - - + + + - - - + + + - - - - - - - + + + +
    ); }; diff --git a/src/components/Cars/Status/index.test.jsx b/src/components/Cars/Status/index.test.jsx index 8083078..21a32d6 100644 --- a/src/components/Cars/Status/index.test.jsx +++ b/src/components/Cars/Status/index.test.jsx @@ -12,7 +12,7 @@ import routeData from "react-router"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import CarStatus from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -45,7 +45,7 @@ describe("CarStatus", () => { }) it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCarStatus(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Cars/Update/index.test.jsx b/src/components/Cars/Update/index.test.jsx index 9f2bfec..c3b680b 100644 --- a/src/components/Cars/Update/index.test.jsx +++ b/src/components/Cars/Update/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { VehicleProvider } from "../../Contexts/VehicleContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -36,7 +36,7 @@ describe("VehicleUpdate", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderVehicleUpdate(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Contexts/UserContext.jsx b/src/components/Contexts/UserContext.jsx index 3f05b69..f6ab06c 100644 --- a/src/components/Contexts/UserContext.jsx +++ b/src/components/Contexts/UserContext.jsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from "react"; import auth from "../../services/auth"; import getTimerWorker from "../../services/getTimerWorker"; import { parsePayload } from "../../utils/jwt"; -import { getGroups } from "../../utils/roles"; +import {getGroups, getProviders} from "../../utils/roles"; const UserContext = React.createContext(); @@ -10,6 +10,7 @@ export const UserProvider = ({ children }) => { const [fetching, setFetching] = useState(false); const [token, setToken] = useState(null); const [groups, setGroups] = useState(null); + const [providers, setProviders] = useState(null); const [error, setError] = useState(null); let timer; @@ -73,6 +74,7 @@ export const UserProvider = ({ children }) => { } setGroups(getGroups(idToken)); + setProviders(getProviders(idToken)); startSessionTimer(); } catch (e) { setError(`Verify error. ${e.message}`); @@ -106,6 +108,7 @@ export const UserProvider = ({ children }) => { const signOut = () => { setGroups(null); + setProviders(null); setToken(null); if (localStorage) { localStorage.removeItem("token"); @@ -153,6 +156,7 @@ export const UserProvider = ({ children }) => { error, fetching, groups, + providers, token, getAuthorizeURL, getLogoutURL, diff --git a/src/components/Contexts/__mocks__/UserContext.jsx b/src/components/Contexts/__mocks__/UserContext.jsx index 958d515..746fd32 100644 --- a/src/components/Contexts/__mocks__/UserContext.jsx +++ b/src/components/Contexts/__mocks__/UserContext.jsx @@ -1,9 +1,10 @@ import React from "react"; -import { getGroups } from "../../../utils/roles"; +import {getGroups, getProviders} from "../../../utils/roles"; let token = null; let groups = null; +let providers = null; let fetching = false; let error = null; let signInResp = {}; @@ -20,6 +21,7 @@ export const useUserContext = () => ({ error, fetching, groups, + providers, token, getAuthorizeURL: jest.fn(() => authorizeURL), getLogoutURL: jest.fn(() => logoutURL), @@ -35,8 +37,10 @@ export const setToken = (val) => { token = val; if (!val || !val.idToken || !val.idToken.jwtToken) { groups = null; + providers = null } else { groups = getGroups(val.idToken.jwtToken); + providers = getProviders(val.idToken.jwtToken); } }; diff --git a/src/components/Controls/CarUpdateStatusTable/CarUpdateStatusTable.test.jsx b/src/components/Controls/CarUpdateStatusTable/CarUpdateStatusTable.test.jsx index d728be1..c49d526 100644 --- a/src/components/Controls/CarUpdateStatusTable/CarUpdateStatusTable.test.jsx +++ b/src/components/Controls/CarUpdateStatusTable/CarUpdateStatusTable.test.jsx @@ -6,14 +6,14 @@ import { render, waitFor } from "@testing-library/react"; import { CarUpdatesProvider } from "../../Contexts/CarUpdatesContext"; import CarUpdateStatusTable from "../CarUpdateStatusTable"; import { StatusProvider } from "../../Contexts/StatusContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import addSnapshotSerializer from "../../../utils/snapshot"; const renderCarUpdateStatusTable = async () => { const { container } = render( - + ); diff --git a/src/components/Controls/CarUpdatesTable/index.jsx b/src/components/Controls/CarUpdatesTable/index.jsx index 3750152..ef60a4b 100644 --- a/src/components/Controls/CarUpdatesTable/index.jsx +++ b/src/components/Controls/CarUpdatesTable/index.jsx @@ -1,4 +1,3 @@ -import React, { useEffect, useState } from "react"; import { LinearProgress, Table, @@ -9,19 +8,22 @@ import { TableRow, Tooltip, } from "@material-ui/core"; -import { Link } from "react-router-dom"; import CancelIcon from "@material-ui/icons/Cancel"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; +import { logger } from "../../../services/monitoring"; import { LocalDateTimeString } from "../../../utils/dates"; -import TableHeaderSortable from "../../Table/HeaderSortable"; import { CarUpdatesProvider, useCarUpdatesContext, } from "../../Contexts/CarUpdatesContext"; import { useStatusContext } from "../../Contexts/StatusContext"; -import useStyles from "../../useStyles"; -import { logger } from "../../../services/monitoring"; +import { useUserContext } from "../../Contexts/UserContext"; +import TableHeaderSortable from "../../Table/HeaderSortable"; import { useLocalStorage } from "../../useLocalStorage"; +import useStyles from "../../useStyles"; +import { RoleWrap } from "../RoleWrap"; const tableColumns = [ { @@ -68,6 +70,8 @@ const MainForm = ({ vin, token }) => { } = useCarUpdatesContext(); const { setMessage } = useStatusContext(); + const { groups, providers } = useUserContext(); + useEffect(() => { (async () => { try { @@ -176,11 +180,17 @@ const MainForm = ({ vin, token }) => { {LocalDateTimeString(row.updated)} - - sendCancel(row)}> - - - + + + sendCancel(row)}> + + + + ))} diff --git a/src/components/Controls/RoleWrap/__snapshots__/index.test.js.snap b/src/components/Controls/RoleWrap/__snapshots__/index.test.js.snap new file mode 100644 index 0000000..94da114 --- /dev/null +++ b/src/components/Controls/RoleWrap/__snapshots__/index.test.js.snap @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RoleWrap RoleWrap with access, child renders 1`] = ` +
    +
    + Test +
    +
    +`; + +exports[`RoleWrap RoleWrap without access, child does not render 1`] = `
    `; + +exports[`RoleWrap RoleWrap without access, eitherComponent renders 1`] = ` +
    +
    + Either +
    +
    +`; diff --git a/src/components/Controls/RoleWrap/index.js b/src/components/Controls/RoleWrap/index.js new file mode 100644 index 0000000..b91b408 --- /dev/null +++ b/src/components/Controls/RoleWrap/index.js @@ -0,0 +1,19 @@ +import {hasRole} from "../../../utils/roles"; +import React from "react"; + +export const RoleWrap = (props) => { + const {groups, rolesPerProvider, providers} = props; + + const eitherComponent = props["eitherComponent"] || null; + + if (!hasRole(groups, rolesPerProvider, providers)) { + return eitherComponent != null ? eitherComponent : <>; + } + + return ( + + {props.children} + + ) +} + diff --git a/src/components/Controls/RoleWrap/index.test.js b/src/components/Controls/RoleWrap/index.test.js new file mode 100644 index 0000000..421caac --- /dev/null +++ b/src/components/Controls/RoleWrap/index.test.js @@ -0,0 +1,70 @@ +import { render, screen } from "@testing-library/react"; +import { Permissions, Providers, Roles } from "../../../utils/roles"; +import addSnapshotSerializer from "../../../utils/snapshot"; +import { RoleWrap } from "./index"; + +const selector = "test-role-wrap-child"; + +const renderRoleWrap = async ( + groups, + providers, + rolesPerProvider, + eitherComponent +) => { + const { container } = render( + +
    Test
    +
    + ); + return container; +}; + +describe("RoleWrap", () => { + beforeAll(() => { + addSnapshotSerializer(expect); + }); + + it("RoleWrap with access, child renders", async () => { + const view = await renderRoleWrap( + [Roles.READ], + [Providers.FISKER], + Permissions.FiskerRead, + null + ); + const childComponent = screen.getByTestId(selector); + expect(childComponent).toHaveTextContent("Test"); + + expect(view).toMatchSnapshot(); + }); + + it("RoleWrap without access, child does not render", async () => { + const view = await renderRoleWrap( + [Roles.READ], + [Providers.FISKER], + Permissions.FiskerCreate, + null + ); + const childComponent = screen.queryByTestId(selector); + expect(childComponent).toBeNull(); + + expect(view).toMatchSnapshot(); + }); + + it("RoleWrap without access, eitherComponent renders", async () => { + const view = await renderRoleWrap( + [Roles.READ], + [Providers.FISKER], + Permissions.FiskerCreate, +
    Either
    + ); + const childComponent = screen.getByTestId(selector); + expect(childComponent).toHaveTextContent("Either"); + + expect(view).toMatchSnapshot(); + }); +}); diff --git a/src/components/Fleets/Add/index.test.jsx b/src/components/Fleets/Add/index.test.jsx index 3806a23..b2ba2e4 100644 --- a/src/components/Fleets/Add/index.test.jsx +++ b/src/components/Fleets/Add/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../Contexts/FleetContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -36,7 +36,7 @@ describe("FleetAddForm", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetAdd(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/CANFilters/Add/index.test.jsx b/src/components/Fleets/Status/CANFilters/Add/index.test.jsx index 362ffbf..aaad37b 100644 --- a/src/components/Fleets/Status/CANFilters/Add/index.test.jsx +++ b/src/components/Fleets/Status/CANFilters/Add/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../../utils/snapshot"; @@ -34,7 +34,7 @@ describe("FleetCANFilterAdd", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetCANFilterAdd(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap index 6baadf0..c07188c 100644 --- a/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Status/CANFilters/Table/__snapshots__/index.test.jsx.snap @@ -221,23 +221,6 @@ exports[`FleetCANFiltersTable Render 1`] = ` /> - - - - - - - - - diff --git a/src/components/Fleets/Status/CANFilters/Table/index.jsx b/src/components/Fleets/Status/CANFilters/Table/index.jsx index e9eb631..69740b4 100644 --- a/src/components/Fleets/Status/CANFilters/Table/index.jsx +++ b/src/components/Fleets/Status/CANFilters/Table/index.jsx @@ -1,5 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { Link } from 'react-router-dom'; import { Grid, Table, @@ -12,37 +10,42 @@ import { } from "@material-ui/core"; import AddCircleIcon from "@material-ui/icons/AddCircle"; import DeleteIcon from "@material-ui/icons/Delete"; -import EditIcon from '@material-ui/icons/Edit'; +import EditIcon from "@material-ui/icons/Edit"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; -import TableHeaderSortable from "../../../../Table/HeaderSortable"; -import { useUserContext } from "../../../../Contexts/UserContext" -import { useStatusContext } from "../../../../Contexts/StatusContext"; -import { FleetProvider, useFleetContext } from "../../../../Contexts/FleetContext" -import useStyles from "../../../../useStyles"; -import SearchField from "../../../../Controls/SearchField"; import { logger } from "../../../../../services/monitoring"; -import { Roles, hasRole } from "../../../../../utils/roles"; -import {useLocalStorage} from "../../../../useLocalStorage"; -import {trimIfMoreThan} from "../../../../../utils/strings" +import { hasRole, Permissions } from "../../../../../utils/roles"; +import { trimIfMoreThan } from "../../../../../utils/strings"; +import { + FleetProvider, + useFleetContext, +} from "../../../../Contexts/FleetContext"; +import { useStatusContext } from "../../../../Contexts/StatusContext"; +import { useUserContext } from "../../../../Contexts/UserContext"; +import SearchField from "../../../../Controls/SearchField"; +import TableHeaderSortable from "../../../../Table/HeaderSortable"; +import { useLocalStorage } from "../../../../useLocalStorage"; +import useStyles from "../../../../useStyles"; const tableColumns = [ { id: "can_id", - label: "CAN ID" + label: "CAN ID", }, { id: "interval", - label: "Interval (ms)" + label: "Interval (ms)", }, { id: "edge_mask", - label: "Edge mask" + label: "Edge mask", }, { id: "", - label: "Actions" - } + label: "Actions", + }, ]; const PAGE_SIZE = "FLEET_STATUS_CAN_FILTER_TABLE_PAGE_SIZE"; @@ -54,8 +57,19 @@ const MainForm = ({ name }) => { const [order, setOrder] = useState("desc"); const classes = useStyles(); const { setMessage } = useStatusContext(); - const { fleetCANFilters, totalFleetCANFilters, getFleetCANFilters, deleteFleetCANFilter } = useFleetContext(); - const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); + const { + fleetCANFilters, + totalFleetCANFilters, + getFleetCANFilters, + deleteFleetCANFilter, + } = useFleetContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + groups, + providers, + } = useUserContext(); useEffect(() => { (async () => { @@ -107,7 +121,7 @@ const MainForm = ({ name }) => { const onDelete = async (can_id) => { try { await deleteFleetCANFilter(name, can_id, token); - setMessage(`Deleted ${can_id}`) + setMessage(`Deleted ${can_id}`); } catch (e) { setMessage(e.message); logger.warn(e.stack); @@ -116,19 +130,19 @@ const MainForm = ({ name }) => { const Actions = (row) => { let actions = []; - if (hasRole([Roles.CREATE], groups)) { + if (hasRole(groups, Permissions.FiskerCreate, providers)) { actions.push({ tip: `Update "${row.can_id}"`, link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`, - icon: + icon: , }); } - if (hasRole([Roles.DELETE], groups)) { + if (hasRole(groups, Permissions.FiskerDelete, providers)) { actions.push({ tip: `Delete "${row.can_id}"`, id: row.can_id, - icon: - }) + icon: , + }); } if (actions.length === 0) return ["No actions"]; @@ -157,7 +171,10 @@ const MainForm = ({ name }) => {
    - + @@ -174,11 +191,13 @@ const MainForm = ({ name }) => { onSortRequest={handleSort} /> - {fleetCANFilters.map(row => ( + {fleetCANFilters.map((row) => ( {row.can_id} {row.interval} - {trimIfMoreThan(row.edge_mask, 15, "...")} + + {trimIfMoreThan(row.edge_mask, 15, "...")} + {Actions(row)} ))} @@ -201,7 +220,7 @@ const MainForm = ({ name }) => { -
    +
    ); }; diff --git a/src/components/Fleets/Status/CANFilters/Table/index.test.jsx b/src/components/Fleets/Status/CANFilters/Table/index.test.jsx index 4c69962..ce7c40c 100644 --- a/src/components/Fleets/Status/CANFilters/Table/index.test.jsx +++ b/src/components/Fleets/Status/CANFilters/Table/index.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../../utils/snapshot"; @@ -37,7 +37,7 @@ describe("FleetCANFiltersTable", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetCANFiltersTable(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/CANFilters/Update/index.test.jsx b/src/components/Fleets/Status/CANFilters/Update/index.test.jsx index 36fdb68..029e6e2 100644 --- a/src/components/Fleets/Status/CANFilters/Update/index.test.jsx +++ b/src/components/Fleets/Status/CANFilters/Update/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../../utils/snapshot"; @@ -34,7 +34,7 @@ describe("FleetCANFilterUpdate", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetCANFilterUpdate(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/CANFiltersTab.test.jsx b/src/components/Fleets/Status/CANFiltersTab.test.jsx index bd30ecc..bd490d2 100644 --- a/src/components/Fleets/Status/CANFiltersTab.test.jsx +++ b/src/components/Fleets/Status/CANFiltersTab.test.jsx @@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import { setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import CANFiltersTab from "./CANFiltersTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -31,7 +31,7 @@ describe("CANFiltersTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCANFitlersTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/Details/index.test.jsx b/src/components/Fleets/Status/Details/index.test.jsx index 6cd7f4b..7a3384f 100644 --- a/src/components/Fleets/Status/Details/index.test.jsx +++ b/src/components/Fleets/Status/Details/index.test.jsx @@ -9,7 +9,7 @@ import routeData from "react-router"; import { FleetProvider } from "../../../Contexts/FleetContext"; import { StatusProvider } from "../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../utils/snapshot"; @@ -42,7 +42,7 @@ describe("FleetDetailsTab", () => { }) it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetDetailsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/DetailsTab.test.jsx b/src/components/Fleets/Status/DetailsTab.test.jsx index 21f3eca..c6da46b 100644 --- a/src/components/Fleets/Status/DetailsTab.test.jsx +++ b/src/components/Fleets/Status/DetailsTab.test.jsx @@ -11,7 +11,7 @@ import { MemoryRouter, Route } from "react-router-dom"; import { FleetProvider } from "../../Contexts/FleetContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./DetailsTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -41,7 +41,7 @@ describe("DetailsTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderDetailsTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/Vehicles/Add/index.test.jsx b/src/components/Fleets/Status/Vehicles/Add/index.test.jsx index 1eccb7e..fea2983 100644 --- a/src/components/Fleets/Status/Vehicles/Add/index.test.jsx +++ b/src/components/Fleets/Status/Vehicles/Add/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../../utils/snapshot"; @@ -36,7 +36,7 @@ describe("FleetVehicleAdd", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetVehicleAdd(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/Vehicles/Table/__snapshots__/index.test.jsx.snap b/src/components/Fleets/Status/Vehicles/Table/__snapshots__/index.test.jsx.snap index 7cb84c5..9cad130 100644 --- a/src/components/Fleets/Status/Vehicles/Table/__snapshots__/index.test.jsx.snap +++ b/src/components/Fleets/Status/Vehicles/Table/__snapshots__/index.test.jsx.snap @@ -153,26 +153,7 @@ exports[`FleetVehiclesTable Render 1`] = ` - - - - -
    - + No actions - - - - -
    - + No actions - - - - -
    - + No actions diff --git a/src/components/Fleets/Status/Vehicles/Table/index.jsx b/src/components/Fleets/Status/Vehicles/Table/index.jsx index 07127f2..07dc489 100644 --- a/src/components/Fleets/Status/Vehicles/Table/index.jsx +++ b/src/components/Fleets/Status/Vehicles/Table/index.jsx @@ -1,5 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { Link } from 'react-router-dom'; import { Grid, Table, @@ -13,27 +11,32 @@ import { import AddCircleIcon from "@material-ui/icons/AddCircle"; import DeleteIcon from "@material-ui/icons/Delete"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; -import TableHeaderSortable from "../../../../Table/HeaderSortable"; -import { useUserContext } from "../../../../Contexts/UserContext" -import { useStatusContext } from "../../../../Contexts/StatusContext"; -import { FleetProvider, useFleetContext } from "../../../../Contexts/FleetContext" -import useStyles from "../../../../useStyles"; -import SearchField from "../../../../Controls/SearchField"; import { logger } from "../../../../../services/monitoring"; -import { Roles, hasRole } from "../../../../../utils/roles"; -import { useLocalStorage } from "../../../../useLocalStorage"; +import { hasRole, Permissions } from "../../../../../utils/roles"; +import { + FleetProvider, + useFleetContext, +} from "../../../../Contexts/FleetContext"; +import { useStatusContext } from "../../../../Contexts/StatusContext"; +import { useUserContext } from "../../../../Contexts/UserContext"; +import SearchField from "../../../../Controls/SearchField"; import DeleteConfirmation from "../../../../DeleteConfirmation"; +import TableHeaderSortable from "../../../../Table/HeaderSortable"; +import { useLocalStorage } from "../../../../useLocalStorage"; +import useStyles from "../../../../useStyles"; const tableColumns = [ { id: "vin", - label: "VIN" + label: "VIN", }, { id: "", - label: "Actions" - } + label: "Actions", + }, ]; const PAGE_SIZE = "FLEET_STATUS_VEHICLES_TABLE_PAGE_SIZE"; @@ -44,11 +47,22 @@ const MainForm = ({ name }) => { const [orderBy, setOrderBy] = useState("id"); const [order, setOrder] = useState("desc"); const [search, setSearch] = useState(""); - const [showDeleteModal, setShowDeleteModal] = useState(false) + const [showDeleteModal, setShowDeleteModal] = useState(false); const classes = useStyles(); const { setMessage } = useStatusContext(); - const { fleetVehicles, totalFleetVehicles, getFleetVehicles, deleteFleetVehicle } = useFleetContext(); - const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); + const { + fleetVehicles, + totalFleetVehicles, + getFleetVehicles, + deleteFleetVehicle, + } = useFleetContext(); + const { + token: { + idToken: { jwtToken: token }, + }, + groups, + providers, + } = useUserContext(); const handleSearch = (query) => { setSearch(query); @@ -105,7 +119,7 @@ const MainForm = ({ name }) => { const onDelete = async (vin) => { try { await deleteFleetVehicle(name, { vin: vin }, token); - setMessage(`Deleted ${vin}`) + setMessage(`Deleted ${vin}`); } catch (e) { setMessage(e.message); logger.warn(e.stack); @@ -114,12 +128,12 @@ const MainForm = ({ name }) => { const Actions = (vin) => { let actions = []; - if (hasRole([Roles.DELETE], groups)) { + if (hasRole(groups, Permissions.FiskerDelete, providers)) { actions.push({ tip: `Delete "${vin}"`, id: vin, - icon: - }) + icon: , + }); } if (actions.length === 0) return ["No actions"]; @@ -140,9 +154,13 @@ const MainForm = ({ name }) => { {action.icon} - setShowDeleteModal(false)} deleteFunction={() => onDelete(action.id)} /> + setShowDeleteModal(false)} + deleteFunction={() => onDelete(action.id)} + /> - ); } }); @@ -152,7 +170,10 @@ const MainForm = ({ name }) => {
    - + @@ -169,7 +190,7 @@ const MainForm = ({ name }) => { onSortRequest={handleSort} /> - {fleetVehicles.map(vin => ( + {fleetVehicles.map((vin) => ( {vin} @@ -196,7 +217,7 @@ const MainForm = ({ name }) => { -
    +
    ); }; diff --git a/src/components/Fleets/Status/Vehicles/Table/index.test.jsx b/src/components/Fleets/Status/Vehicles/Table/index.test.jsx index 703be2a..a6b2c11 100644 --- a/src/components/Fleets/Status/Vehicles/Table/index.test.jsx +++ b/src/components/Fleets/Status/Vehicles/Table/index.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../../../Contexts/FleetContext"; import { StatusProvider } from "../../../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER }from "../../../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../../../utils/snapshot"; @@ -39,7 +39,7 @@ describe("FleetVehiclesTable", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetVehiclesTable(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/VehiclesTab.test.jsx b/src/components/Fleets/Status/VehiclesTab.test.jsx index ef72fd7..9d8081e 100644 --- a/src/components/Fleets/Status/VehiclesTab.test.jsx +++ b/src/components/Fleets/Status/VehiclesTab.test.jsx @@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; import { setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import VehiclesTab from "./VehiclesTab"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -31,7 +31,7 @@ describe("VehiclesTab", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderVehiclesTab(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap b/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap index 27c2794..4ab0de0 100644 --- a/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap +++ b/src/components/Fleets/Status/__snapshots__/CANFiltersTab.test.jsx.snap @@ -220,23 +220,6 @@ exports[`CANFiltersTab Render 1`] = ` /> - - - - - - - - - diff --git a/src/components/Fleets/Status/__snapshots__/VehiclesTab.test.jsx.snap b/src/components/Fleets/Status/__snapshots__/VehiclesTab.test.jsx.snap index 7db6e6e..aab2895 100644 --- a/src/components/Fleets/Status/__snapshots__/VehiclesTab.test.jsx.snap +++ b/src/components/Fleets/Status/__snapshots__/VehiclesTab.test.jsx.snap @@ -152,26 +152,7 @@ exports[`VehiclesTab Render 1`] = ` - - - - -
    - + No actions - - - - -
    - + No actions - - - - -
    - + No actions diff --git a/src/components/Fleets/Status/index.test.jsx b/src/components/Fleets/Status/index.test.jsx index bc5cb8c..9e18e1d 100644 --- a/src/components/Fleets/Status/index.test.jsx +++ b/src/components/Fleets/Status/index.test.jsx @@ -12,7 +12,7 @@ import routeData from "react-router"; import { FleetProvider } from "../../Contexts/FleetContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import FleetStatus from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -45,7 +45,7 @@ describe("FleetStatus", () => { }) it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderCarStatus(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Table/index.test.jsx b/src/components/Fleets/Table/index.test.jsx index 31d2b6b..f70cbe0 100644 --- a/src/components/Fleets/Table/index.test.jsx +++ b/src/components/Fleets/Table/index.test.jsx @@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../Contexts/FleetContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -39,7 +39,7 @@ describe("FleetTable", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetTable(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Fleets/Update/index.test.jsx b/src/components/Fleets/Update/index.test.jsx index 8e91ee1..3e4479e 100644 --- a/src/components/Fleets/Update/index.test.jsx +++ b/src/components/Fleets/Update/index.test.jsx @@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom"; import { FleetProvider } from "../../Contexts/FleetContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -36,7 +36,7 @@ describe("FleetUpdate", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderFleetUpdate(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Layouts/SideMenu.jsx b/src/components/Layouts/SideMenu.jsx index 214bdf3..ea750a9 100644 --- a/src/components/Layouts/SideMenu.jsx +++ b/src/components/Layouts/SideMenu.jsx @@ -1,17 +1,17 @@ -import React from "react"; import { List } from "@material-ui/core"; -import HomeIcon from "@material-ui/icons/Home"; -import DirectionsCarIcon from "@material-ui/icons/DirectionsCar"; -import CommuteIcon from "@material-ui/icons/Commute"; -import CloudDownloadIcon from "@material-ui/icons/CloudDownload"; import AssessmentIcon from "@material-ui/icons/Assessment"; import BuildIcon from "@material-ui/icons/Build"; +import CloudDownloadIcon from "@material-ui/icons/CloudDownload"; +import CommuteIcon from "@material-ui/icons/Commute"; +import DirectionsCarIcon from "@material-ui/icons/DirectionsCar"; +import HomeIcon from "@material-ui/icons/Home"; import SettingsInputCompositeIcon from "@material-ui/icons/SettingsInputComposite"; +import { default as React, useEffect, useState } from "react"; +import { hasRole, Permissions } from "../../utils/roles"; import { useUserContext } from "../Contexts/UserContext"; -import { Roles, hasRole } from "../../utils/roles"; -import { ExpandableSideMenuItem, MenuItem } from "./MenuItem"; import SupersetDashboardList from "../SupersetDashboardList/SupersetDashboardList"; +import { ExpandableSideMenuItem, MenuItem } from "./MenuItem"; const menuData = [ { @@ -24,65 +24,60 @@ const menuData = [ label: "Deployments", to: "/packages", icon: , - roles: [Roles.READ, Roles.CREATE], + rolesPerProvider: Permissions.FiskerMagnaRead, }, { label: "Vehicles", to: "/vehicles", icon: , - roles: [Roles.READ, Roles.CREATE], + rolesPerProvider: Permissions.FiskerMagnaRead, }, { label: "Fleets", to: "/fleets", icon: , - roles: [Roles.READ, Roles.CREATE], + rolesPerProvider: Permissions.FiskerRead, }, { label: "Datascope", to: null, icon: , - roles: [Roles.READ, Roles.CREATE], - component: SupersetDashboardList + rolesPerProvider: Permissions.FiskerRead, + component: SupersetDashboardList, }, { label: "Suppliers", to: "/suppliers", icon: , - roles: [Roles.APPROVESUPPLIERS], + rolesPerProvider: Permissions.FiskerSupplierAdmin, }, { label: "Tools", to: "/tools/certificates/add", icon: , - roles: [Roles.CERTIFICATES, Roles.APPROVESUPPLIERS], + rolesPerProvider: Permissions.FiskerTools, submenus: [ { label: "Certificate", to: "/tools/certificates/add", - roles: [Roles.CERTIFICATES], + rolesPerProvider: Permissions.FiskerCertificate, }, { label: "SMS", to: "/tools/sms/send", - roles: [Roles.CREATE], - } + rolesPerProvider: Permissions.FiskerCreate, + }, ], }, ]; - const SideMenu = () => { - const { groups } = useUserContext(); - - const menu = menuData.reduce((result, item) => { - if (hasRole(item.roles, groups)) { - result.push(item); - } - - return result; - }, []); + const { groups, providers } = useUserContext(); + const [menu, setMenu] = useState(menuData); + useEffect(() => { + filterAccessible(groups, providers, setMenu); + }, [groups, providers]); return ( @@ -96,4 +91,14 @@ const SideMenu = () => { ); }; -export default SideMenu; \ No newline at end of file +const filterAccessible = (groups, providers, setMenu) => { + const filteredMenu = menuData.reduce((result, item) => { + if (hasRole(groups, item.rolesPerProvider, providers)) { + result.push(item); + } + return result; + }, []); + setMenu(filteredMenu); +}; + +export default SideMenu; diff --git a/src/components/Layouts/SideMenu.test.jsx b/src/components/Layouts/SideMenu.test.jsx index 476a22a..c481462 100644 --- a/src/components/Layouts/SideMenu.test.jsx +++ b/src/components/Layouts/SideMenu.test.jsx @@ -1,12 +1,12 @@ jest.mock("../Contexts/UserContext"); -jest.mock("../../services/superset") +jest.mock("../../services/superset"); -import { render, waitFor, screen } from "@testing-library/react"; +import { render, screen, waitFor } from "@testing-library/react"; import { BrowserRouter } from "react-router-dom"; -import { UserProvider, setToken } from "../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../utils/testing"; -import SideMenu from "./SideMenu"; import addSnapshotSerializer from "../../utils/snapshot"; +import { TEST_AUTH_OBJECT_FISKER } from "../../utils/testing"; +import { setToken, UserProvider } from "../Contexts/UserContext"; +import SideMenu from "./SideMenu"; const renderMenu = async () => { const { container } = render( @@ -23,15 +23,20 @@ const renderMenu = async () => { describe("SideMenu", () => { beforeAll(() => { addSnapshotSerializer(expect); + }); + it("Unauthenticated", async () => { + setToken({ idToken: { jwtToken: null } }); + const container = await renderMenu(); + expect(container).toMatchSnapshot(); }); it("Authenticated", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderMenu(); await waitFor(() => { - expect(screen.getByText('Datascope')).toBeInTheDocument() - }) + expect(screen.getByText("Datascope")).toBeInTheDocument(); + }); expect(container).toMatchSnapshot(); }); }); diff --git a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap index c804955..12a0106 100644 --- a/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap +++ b/src/components/Layouts/__snapshots__/SideMenu.test.jsx.snap @@ -213,6 +213,42 @@ exports[`SideMenu Authenticated 1`] = `
  • +
  • + +
    + +
    +
    + + Suppliers + +
    + +
    +
  • `; + +exports[`SideMenu Unauthenticated 1`] = ` + +`; diff --git a/src/components/Manifest/Details/index.test.jsx b/src/components/Manifest/Details/index.test.jsx index 0c0ecdd..e0e2be2 100644 --- a/src/components/Manifest/Details/index.test.jsx +++ b/src/components/Manifest/Details/index.test.jsx @@ -9,7 +9,7 @@ import { ManifestsProvider } from "../../Contexts/ManifestsContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import ManifestDetails from "."; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import addSnapshotSerializer from "../../../utils/snapshot"; const TestComponent = (opened) => ( @@ -26,7 +26,7 @@ const TestComponent = (opened) => ( describe("Manifest Details Component", () => { beforeAll(() => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); addSnapshotSerializer(expect); }); diff --git a/src/components/Manifest/List/index.jsx b/src/components/Manifest/List/index.jsx index 742ddc1..c81819d 100644 --- a/src/components/Manifest/List/index.jsx +++ b/src/components/Manifest/List/index.jsx @@ -1,5 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; import { Grid, Table, @@ -10,28 +8,30 @@ import { TableRow, Tooltip, } from "@material-ui/core"; +import DeleteIcon from "@material-ui/icons/Delete"; import SendIcon from "@material-ui/icons/Send"; import VisibilityIcon from "@material-ui/icons/Visibility"; -import DeleteIcon from "@material-ui/icons/Delete"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { Link } from "react-router-dom"; -import { - useManifestsContext, - ManifestsProvider, -} from "../../Contexts/ManifestsContext"; -import { useUserContext } from "../../Contexts/UserContext"; -import { useStatusContext } from "../../Contexts/StatusContext"; -import useStyles from "../../useStyles"; -import { LocalDateTimeString } from "../../../utils/dates"; -import TableHeaderSortable from "../../Table/HeaderSortable"; -import SearchField from "../../Controls/SearchField"; -import { logger } from "../../../services/monitoring"; -import ECUList from "../../Controls/ECUList"; -import { Roles, hasRole } from "../../../utils/roles"; -import { useLocalStorage } from "../../useLocalStorage"; -import { TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types"; -import DeleteConfirmation from "../../DeleteConfirmation"; import EditIcon from "@material-ui/icons/Edit"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; +import { TYPE_MANIFEST_SOFTWARE } from "../../../utils/manifest_types"; +import { hasRole, Permissions } from "../../../utils/roles"; +import { + ManifestsProvider, + useManifestsContext, +} from "../../Contexts/ManifestsContext"; +import { useStatusContext } from "../../Contexts/StatusContext"; +import { useUserContext } from "../../Contexts/UserContext"; +import ECUList from "../../Controls/ECUList"; +import SearchField from "../../Controls/SearchField"; +import DeleteConfirmation from "../../DeleteConfirmation"; +import TableHeaderSortable from "../../Table/HeaderSortable"; +import { useLocalStorage } from "../../useLocalStorage"; +import useStyles from "../../useStyles"; const tableColumns = [ { @@ -71,7 +71,7 @@ const formatManifestType = (type) => { default: return "Standard"; } -} +}; const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE"; @@ -95,6 +95,7 @@ const MainForm = () => { idToken: { jwtToken: token }, }, groups, + providers, } = useUserContext(); const sortHandler = (event, property) => { @@ -155,7 +156,7 @@ const MainForm = () => { setDeleteId(id); setDeleteRowName(`${row.name} ${row.version}`); setShowDeleteModal(true); - } + }; const onDelete = async (manifest_id) => { try { @@ -168,29 +169,30 @@ const MainForm = () => { const Actions = (row) => { let actions = []; - if (hasRole([Roles.CREATE, Roles.READ], groups)) { + if (hasRole(groups, Permissions.FiskerMagnaRead, providers)) { actions.push({ tip: `Status "${row.name} ${row.version}"`, link: `/package-status/${row.id}`, icon: ( ), - }, { - tip: `Update "${row.name} ${row.version}"`, - link: `/package-update/${row.id}`, - icon: ( - - ), }); } - if (hasRole([Roles.CREATE], groups)) { + if (hasRole(groups, Permissions.FiskerRead, providers)) { + actions.push({ + tip: `Update "${row.name} ${row.version}"`, + link: `/package-update/${row.id}`, + icon: , + }); + } + if (hasRole(groups, Permissions.FiskerMagnaCreate, providers)) { actions.push({ tip: `Deploy "${row.name} ${row.version}"`, link: `/package-deploy/${row.id}`, icon: , }); } - if (hasRole([Roles.DELETE], groups)) { + if (hasRole(groups, Permissions.FiskerDelete, providers)) { actions.push({ tip: `Delete "${row.name} ${row.version}"`, id: row.id, @@ -257,7 +259,9 @@ const MainForm = () => { )} {row.version} - {formatManifestType(row.type)} + + {formatManifestType(row.type)} + {LocalDateTimeString(row.created)} @@ -290,7 +294,8 @@ const MainForm = () => { message={deleteRowName} open={showDeleteModal} close={() => setShowDeleteModal(false)} - deleteFunction={() => onDelete(deleteId)} /> + deleteFunction={() => onDelete(deleteId)} + /> ); }; diff --git a/src/components/Manifest/Status/index.jsx b/src/components/Manifest/Status/index.jsx index b57686f..28eb6de 100644 --- a/src/components/Manifest/Status/index.jsx +++ b/src/components/Manifest/Status/index.jsx @@ -1,6 +1,3 @@ -import React, { useEffect, useState } from "react"; -import { useParams } from "react-router"; -import { Link } from "react-router-dom"; import { LinearProgress, Table, @@ -14,22 +11,27 @@ import { } from "@material-ui/core"; import CancelIcon from "@material-ui/icons/Cancel"; import clsx from "clsx"; +import React, { useEffect, useState } from "react"; +import { useParams } from "react-router"; +import { Link } from "react-router-dom"; -import { - ManifestsProvider, - useManifestsContext, -} from "../../Contexts/ManifestsContext"; +import { logger } from "../../../services/monitoring"; +import { LocalDateTimeString } from "../../../utils/dates"; +import { Permissions } from "../../../utils/roles"; import { CarUpdatesProvider, useCarUpdatesContext, } from "../../Contexts/CarUpdatesContext"; -import { useUserContext } from "../../Contexts/UserContext"; +import { + ManifestsProvider, + useManifestsContext, +} from "../../Contexts/ManifestsContext"; import { useStatusContext } from "../../Contexts/StatusContext"; -import useStyles from "../../useStyles"; -import { LocalDateTimeString } from "../../../utils/dates"; -import { logger } from "../../../services/monitoring"; -import ManifestDetails from "../Details"; +import { useUserContext } from "../../Contexts/UserContext"; +import { RoleWrap } from "../../Controls/RoleWrap"; import { useLocalStorage } from "../../useLocalStorage"; +import useStyles from "../../useStyles"; +import ManifestDetails from "../Details"; const PAGE_SIZE = "MANIFEST_STATUS_PAGE_SIZE"; @@ -52,6 +54,8 @@ const MainForm = () => { token: { idToken: { jwtToken: token }, }, + groups, + providers, } = useUserContext(); useEffect(() => { @@ -172,11 +176,18 @@ const MainForm = () => { {LocalDateTimeString(row.updated)} - - sendCancel(row)}> - - - + No action} + > + + sendCancel(row)}> + + + + ))} diff --git a/src/components/Manifest/Update/index.jsx b/src/components/Manifest/Update/index.jsx index e4897f5..ae25364 100644 --- a/src/components/Manifest/Update/index.jsx +++ b/src/components/Manifest/Update/index.jsx @@ -49,9 +49,7 @@ const MainForm = () => { const onSubmit = async (e) => { e.preventDefault(); try { - const data = {name, type}; - console.log(data); - const result = await updateManifest(manifest_id, data, token); + const result = await updateManifest(manifest_id, {name, type}, token); if (!result || result.error) return; setMessage(`Updated manifest ${manifest_id}`); diff --git a/src/components/Manifest/Update/index.test.jsx b/src/components/Manifest/Update/index.test.jsx index d851193..ff49e93 100644 --- a/src/components/Manifest/Update/index.test.jsx +++ b/src/components/Manifest/Update/index.test.jsx @@ -9,7 +9,7 @@ import { ManifestsProvider } from "../../Contexts/ManifestsContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import ManifestUpdate from "."; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import addSnapshotSerializer from "../../../utils/snapshot"; const renderManifestUpdate = async () => { @@ -33,12 +33,12 @@ const renderManifestUpdate = async () => { describe("Manifest Details Component", () => { beforeAll(() => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); addSnapshotSerializer(expect); }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const view = await renderManifestUpdate(); expect(view).toMatchSnapshot(); }); diff --git a/src/components/Routes/AuthRoute.jsx b/src/components/Routes/AuthRoute.jsx index f3e02d3..0fe8110 100644 --- a/src/components/Routes/AuthRoute.jsx +++ b/src/components/Routes/AuthRoute.jsx @@ -8,7 +8,7 @@ export const TYPES = { PROTECTED: 2, }; -export const AuthRoute = ({ token, type, roles, groups, ...others }) => { +export const AuthRoute = ({ token, type, groups, rolesPerGroup, providers, ...others }) => { if (type === TYPES.PROTECTED && !token) { return ; } else if (type === TYPES.GUEST && token) { @@ -16,8 +16,9 @@ export const AuthRoute = ({ token, type, roles, groups, ...others }) => { } else if ( type === TYPES.PROTECTED && token && - roles && - !hasRole(roles, groups) + rolesPerGroup && + providers && + !hasRole(groups, rolesPerGroup, providers) ) { return ; } diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index 1bb32fb..46c8fa4 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -1,10 +1,10 @@ import React, { Suspense } from "react"; import { Switch } from "react-router-dom"; -import { AuthRoute, TYPES } from "../Routes/AuthRoute"; -import { MessageBar } from "../MessageBar"; +import { Permissions } from "../../utils/roles"; import { useUserContext } from "../Contexts/UserContext"; -import { Roles } from "../../utils/roles"; +import { MessageBar } from "../MessageBar"; +import { AuthRoute, TYPES } from "../Routes/AuthRoute"; const CANFilterCreate = React.lazy(() => import("../CANFilter/Add")); const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update")); @@ -39,7 +39,7 @@ const SuppliersList = React.lazy(() => import("../Suppliers/List")); const SupplierDetails = React.lazy(() => import("../Suppliers/Details")); const Datascope = React.lazy(() => import("../Dashboard")); const SiteRoutes = () => { - const { token, groups } = useUserContext(); + const { token, groups, providers } = useUserContext(); return ( @@ -57,7 +57,8 @@ const SiteRoutes = () => { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerMagnaRead} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CERTIFICATES]} + rolesPerGroup={Permissions.FiskerCertificate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.CREATE]} + rolesPerGroup={Permissions.FiskerCreate} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.APPROVESUPPLIERS]} + rolesPerGroup={Permissions.FiskerSupplierAdmin} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.APPROVESUPPLIERS]} + rolesPerGroup={Permissions.FiskerSupplierAdmin} + providers={providers} /> { type={TYPES.PROTECTED} token={token} groups={groups} - roles={[Roles.READ, Roles.CREATE]} + rolesPerGroup={Permissions.FiskerRead} + providers={providers} /> diff --git a/src/components/Suppliers/Details/index.test.js b/src/components/Suppliers/Details/index.test.js index d093d7b..760c81c 100644 --- a/src/components/Suppliers/Details/index.test.js +++ b/src/components/Suppliers/Details/index.test.js @@ -15,7 +15,7 @@ import { BrowserRouter } from "react-router-dom"; import { SupplierProvider } from "../../Contexts/SupplierContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -43,7 +43,7 @@ describe("Supplier page", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderSupplierDetailsPage(); expect(container).toMatchSnapshot(); }); diff --git a/src/components/Suppliers/List/index.test.jsx b/src/components/Suppliers/List/index.test.jsx index 520e896..8f395b6 100644 --- a/src/components/Suppliers/List/index.test.jsx +++ b/src/components/Suppliers/List/index.test.jsx @@ -10,7 +10,7 @@ import { BrowserRouter } from "react-router-dom"; import { SupplierProvider } from "../../Contexts/SupplierContext"; import { StatusProvider } from "../../Contexts/StatusContext"; import { UserProvider, setToken } from "../../Contexts/UserContext"; -import { TEST_AUTH_OBJECT } from "../../../utils/testing"; +import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing"; import MainForm from "./index"; import addSnapshotSerializer from "../../../utils/snapshot"; @@ -38,7 +38,7 @@ describe("Suppliers page", () => { }); it("Render", async () => { - setToken(TEST_AUTH_OBJECT); + setToken(TEST_AUTH_OBJECT_FISKER); const container = await renderSuppliersPage(); expect(container).toMatchSnapshot(); }); diff --git a/src/utils/roles.js b/src/utils/roles.js index 78546bf..484297f 100644 --- a/src/utils/roles.js +++ b/src/utils/roles.js @@ -6,16 +6,12 @@ export const Roles = { DELETE: "8f78dce7-f5f9-4033-a10c-c9c7408bfcfe", CERTIFICATES: "746f34b0-9ba0-4b5d-8d84-0256a9c8e390", APPROVESUPPLIERS: "a6c9805e-80b2-42b2-bfbb-9df52e5504d8", + MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID, }; -export const hasRoleToken = (roles, token) => { - if (!roles || roles.length === 0) return true; - - const groups = getGroups(token); - - if (!groups) return false; - - return hasRole(roles, groups); +export const Providers = { + FISKER: "Fisker", + MAGNA: process.env.REACT_APP_MAGNA_PROVIDER, }; export const getGroups = (token) => { @@ -26,13 +22,54 @@ export const getGroups = (token) => { return payload["custom:groups"]; }; -export const hasRole = (roles, groups) => { - if (!roles || roles.length === 0) return true; - if (!groups) return false; +export const getProviders = (token) => { + const payload = parsePayload(token); - for (let role of roles) { - if (groups.indexOf(role) > -1) return true; + if (!payload || !payload["identities"] || payload["identities"].length === 0) + return null; + + return payload["identities"].map((identity) => identity["providerName"]); +}; + +export const hasRole = (userGroups, rolesPerProvider, userProviders) => { + if (!rolesPerProvider || rolesPerProvider.length === 0) return true; + + if (!userGroups || userGroups.length === 0) return false; + + if (rolesPerProvider && userProviders) { + for (const provider of userProviders) { + if (rolesPerProvider[provider]) { + const rolesForProvider = rolesPerProvider[provider]; + if (!rolesForProvider || rolesForProvider.length === 0) return true; + if ( + rolesForProvider.filter((role) => userGroups.includes(role)).length > + 0 + ) { + return true; + } + } + } } return false; }; + +export const Permissions = { + FiskerRead: { [Providers.FISKER]: [Roles.CREATE, Roles.READ] }, + FiskerCreate: { [Providers.FISKER]: [Roles.CREATE] }, + FiskerDelete: { [Providers.FISKER]: [Roles.DELETE] }, + FiskerSupplierAdmin: { [Providers.FISKER]: [Roles.APPROVESUPPLIERS] }, + FiskerCertificate: { [Providers.FISKER]: [Roles.CERTIFICATES] }, + FiskerTools: { + [Providers.FISKER]: [Roles.APPROVESUPPLIERS, Roles.CERTIFICATES], + }, + Magna: { [Providers.MAGNA]: [Roles.MAGNAGROUP] }, + FiskerMagnaRead: { + [Providers.FISKER]: [Roles.CREATE, Roles.READ], + [Providers.MAGNA]: [Roles.MAGNAGROUP], + }, + FiskerMagnaCreate: { + [Providers.FISKER]: [Roles.CREATE], + [Providers.MAGNA]: [Roles.MAGNAGROUP], + }, +}; diff --git a/src/utils/roles.test.js b/src/utils/roles.test.js index 8d73178..06294b8 100644 --- a/src/utils/roles.test.js +++ b/src/utils/roles.test.js @@ -1,10 +1,28 @@ - -import { hasRoleToken, Roles } from "./roles"; -import { TEST_TOKEN } from "./testing"; +import { + getGroups, + getProviders, + hasRole, + Permissions, + Providers, + Roles, +} from "./roles"; +import { TEST_EXPECTED_GROUPS, TEST_TOKEN } from "./testing"; describe("Roles Helper", () => { it("Check roles", () => { - expect(hasRoleToken([Roles.CREATE], TEST_TOKEN)).toEqual(true); - expect(hasRoleToken([Roles.READ], TEST_TOKEN)).toEqual(false); - }) + expect( + hasRole([Roles.CREATE], Permissions.FiskerCreate, [Providers.FISKER]) + ).toEqual(true); + expect( + hasRole([Roles.READ], Permissions.FiskerCreate, [Providers.FISKER]) + ).toEqual(false); + }); + + it("Get groups", () => { + expect(getGroups(TEST_TOKEN)).toEqual(TEST_EXPECTED_GROUPS); + }); + + it("Get providers", () => { + expect(getProviders(TEST_TOKEN)).toEqual(["AzureAD"]); + }); }); diff --git a/src/utils/testing.js b/src/utils/testing.js index 6bdc7d2..1ccb278 100644 --- a/src/utils/testing.js +++ b/src/utils/testing.js @@ -5,5 +5,13 @@ export const TEST_AUTH_OBJECT = { jwtToken: TEST_TOKEN, }, }; + +export const TEST_TOKEN_FISKER = + "eyJraWQiOiJlUTNuZFJLaUVcL084VUZ5RHFsYjN0S1RzWG00SzVPMlc4NXd3VWkzT2tNZz0iLCJhbGciOiJSUzI1NiJ9.eyJhdF9oYXNoIjoiODIwNTViMm9CUl9HQURiMXVyU3V3dyIsInN1YiI6ImEwZDAzMzA5LWQxYzMtNGNhZi05Zjc3LWJlMDE5N2ZlOGE1OSIsImNvZ25pdG86Z3JvdXBzIjpbInVzLXdlc3QtMl9BV3dqTFh5bTJfRmlza2VyIl0sImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLXdlc3QtMi5hbWF6b25hd3MuY29tXC91cy13ZXN0LTJfQVd3akxYeW0yIiwiY29nbml0bzp1c2VybmFtZSI6ImZpc2tlcl9hdW1hcm92QGZpc2tlcmluYy5jb20iLCJnaXZlbl9uYW1lIjoiQW52YXIiLCJjdXN0b206Z3JvdXBzIjoiWzhkODI3OGE1LTljMGUtNGM3Zi05MThhLTgxMWZkMWQyMzZlNCwgNzQ2ZjM0YjAtOWJhMC00YjVkLThkODQtMDI1NmE5YzhlMzkwLCA2MDcxZWFiYS1kNjY2LTRjMDMtODE3Yi1jMjgyYTk1MWI5NjYsIGFjYmQ3MmM5LTlmZjMtNDZhOC04M2JhLWM2ZmE1ZGYzZjI2NCwgNzM3ZTQ0OWMtMjMwOS00NjllLWFhNDgtNDZlNjg5MTk4M2MwLCBmNjNiNjQzMC0xMjg4LTQwYTgtYWE0OS04NDM4NTE1MGQ2YTgsIDg3NzcwYWVhLTkxNjItNDA5ZS1hYTcwLTEwZDBkMWRlNTkwMiwgYTZjOTgwNWUtODBiMi00MmIyLWJmYmItOWRmNTJlNTUwNGQ4LCBiYWZjMTdhNy1lYzY0LTQ5ZDktYTJhNy1kYWZjMjhkY2IwMzcsIDI0NDljMDY2LTkxNDYtNDRhNy1iNGU2LTQ4MjgwMzFkOTU4ZCwgZWZjYzMwMjUtZTJkOC00MjEyLTgyMjctODA1YzdiZTM5ZDJjLCAyNzNjN2MwZC01MWQzLTRhMzEtOTQ0My1jNDM2NzU3MmVlNGYsIDcwZmU1OWM2LTFkNjktNGRmZS05N2I5LWZhZTQzMjE1MTAxOSwgODY5NTZhMmYtOGQ0Ni00N2ZmLTliMjktZjk5MDc5YWUzYzFkLCBiMGU4MmU4My0yYzk0LTRiMjQtYjljNy1lNzc4MTZmMjU5N2RdIiwiYXVkIjoiN2NrMnRmb3FhdmM3MmM0NWhoN3RnZTQya2QiLCJjdXN0b206c2Vzc2lvbi1kdXJhdGlvbiI6IjkwMCIsImlkZW50aXRpZXMiOlt7InVzZXJJZCI6ImF1bWFyb3ZAZmlza2VyaW5jLmNvbSIsInByb3ZpZGVyTmFtZSI6IkZpc2tlciIsInByb3ZpZGVyVHlwZSI6IlNBTUwiLCJpc3N1ZXIiOiJodHRwczpcL1wvc3RzLndpbmRvd3MubmV0XC81YWE0YjY0MC1jOWZjLTRhOWItYjNhMy1kNGE3ZDAwOGZiNWVcLyIsInByaW1hcnkiOiJ0cnVlIiwiZGF0ZUNyZWF0ZWQiOiIxNjY0ODA3MDE1NzQ3In1dLCJ0b2tlbl91c2UiOiJpZCIsImF1dGhfdGltZSI6MTY2Nzc1NjIzOCwiZXhwIjoxNjY3NzU5ODM4LCJpYXQiOjE2Njc3NTYyMzgsImZhbWlseV9uYW1lIjoiVW1hcm92IiwiZW1haWwiOiJhdW1hcm92QGZpc2tlcmluYy5jb20ifQ.ZZC7tgMMjRpRNJcGdp7ftXJQJ6pjzP45SJAcfJRZC4oUHG-h5BI1bGKEKmslWdK09GEH1cN7YDGQg8ufkKOyncm-J5DRNvj27fN--Lapkg1Q0bHT8pwMDXRjPUbazLEwEvfo1yGyLBOBopxdisQvv9km8-UH3Xy7a7IsCOWx51xKsgMWho4w4BK1PQUBpUrG0h-tno7-7nHO4gwr4urCtDPIoMjsq_lF77LDvM3SBdCH28ogaoDE3ogd1tbBpwHXtOhnp-FMp85I2Vk-pI_KsiW3RXr50OdeAVEfFLFKhjCCoB5RR6pKxWJXs-7BN91Tnn7SYgfAWt5Wa2cxBxMUDg" +export const TEST_AUTH_OBJECT_FISKER = { + idToken: { + jwtToken: TEST_TOKEN_FISKER + }, +} export const TEST_EXPECTED_GROUPS = "[2914e67f-fb85-4b78-b79d-656f4f37faa1, 8d8278a5-9c0e-4c7f-918a-811fd1d236e4, 746f34b0-9ba0-4b5d-8d84-0256a9c8e390, 6c3cf98d-0ada-48c6-ae94-b171cfa275fc, acbd72c9-9ff3-46a8-83ba-c6fa5df3f264, 5e59a619-c890-4472-91c2-d5eae5d8fa18, 56ef4bec-d739-4ddf-a003-ecc813085b8d, 929b0470-f7eb-4e18-9f97-22ac2c591a10, 131e6257-fdb7-426a-9825-1cd918802fba, 1ac794cc-76ac-47f6-bee6-d6666998f0fd, 87770aea-9162-409e-aa70-10d0d1de5902, 5dea6c32-6575-4527-9254-e50d27ae5e92, bafc17a7-ec64-49d9-a2a7-dafc28dcb037, 2449c066-9146-44a7-b4e6-4828031d958d, efcc3025-e2d8-4212-8227-805c7be39d2c, 8f78dce7-f5f9-4033-a10c-c9c7408bfcfe, cf653183-c829-4eed-a6ce-453fa1017cd9, 783c5979-f5e7-4cb6-b14e-c3553dde956a, 5515a98f-4668-4121-8e8d-fee2825699cf, 86956a2f-8d46-47ff-9b29-f99079ae3c1d, c4d4361c-8882-47b4-8641-fd3ab68ae722, 972ad095-16b7-40ad-a464-f5dbf417a8cd, 7bcdcdb2-3279-44bf-a998-771bab4b33e1]"; From aa585bebc31b38a6c2936981e371724cdf18c86b Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Tue, 15 Nov 2022 12:19:43 -0800 Subject: [PATCH 2/5] CEC-3119 Magna Create Certs (#236) * CEC-3119 FiskerQA Give Magna access to create certs * Comment --- src/components/Certificates/Add/CreateForm.jsx | 17 +++++++++++++++-- src/components/Layouts/SideMenu.jsx | 14 +++++++++++--- src/components/Routes/SiteRoutes.jsx | 2 +- src/utils/roles.js | 6 +++++- 4 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/components/Certificates/Add/CreateForm.jsx b/src/components/Certificates/Add/CreateForm.jsx index f15c020..c7855e6 100644 --- a/src/components/Certificates/Add/CreateForm.jsx +++ b/src/components/Certificates/Add/CreateForm.jsx @@ -4,11 +4,13 @@ import { FormLabel, Radio, RadioGroup, - TextField, + TextField } from "@material-ui/core"; import React, { useRef, useState } from "react"; import { CertTypeData, CertTypes } from "../../../utils/certificates"; +import { Providers } from "../../../utils/roles"; +import { useUserContext } from "../../Contexts/UserContext"; import useStyles from "../../useStyles"; const getCertTypeLabel = (certtype) => { @@ -17,9 +19,20 @@ const getCertTypeLabel = (certtype) => { return "ID"; }; +const getCertsTypes = (providers) => { + if (providers.length === 0) return []; + if (providers[0] === Providers.MAGNA) { + return CertTypeData.filter((item) => { + return (item.label === CertTypes.TBOX || item.label === CertTypes.ICC); + }) + } + return CertTypeData; +} + const CreateForm = ({ onCreate, busy }) => { const classes = useStyles(); const commonnameEl = useRef(null); + const {providers} = useUserContext(); const [certType, setCertType] = useState(CertTypes.TBOX); const onSubmit = async (event) => { @@ -61,7 +74,7 @@ const CreateForm = ({ onCreate, busy }) => { onChange={onCertTypeChange} margin="normal" > - {CertTypeData.map((item, i) => { + {getCertsTypes(providers).map((item, i) => { return ( { const [menu, setMenu] = useState(menuData); useEffect(() => { - filterAccessible(groups, providers, setMenu); + filterMenu(groups, providers, setMenu); }, [groups, providers]); return ( @@ -91,14 +91,22 @@ const SideMenu = () => { ); }; -const filterAccessible = (groups, providers, setMenu) => { - const filteredMenu = menuData.reduce((result, item) => { +const reduceMenu = (data, groups, providers) => { + return data.reduce((result, item) => { if (hasRole(groups, item.rolesPerProvider, providers)) { + if (item.submenus) { + item.submenus = reduceMenu(item.submenus, groups, providers); + } result.push(item); } return result; }, []); +} + +const filterMenu = (groups, providers, setMenu) => { + const filteredMenu = reduceMenu(menuData, groups, providers); setMenu(filteredMenu); }; + export default SideMenu; diff --git a/src/components/Routes/SiteRoutes.jsx b/src/components/Routes/SiteRoutes.jsx index 46c8fa4..288655f 100644 --- a/src/components/Routes/SiteRoutes.jsx +++ b/src/components/Routes/SiteRoutes.jsx @@ -225,7 +225,7 @@ const SiteRoutes = () => { type={TYPES.PROTECTED} token={token} groups={groups} - rolesPerGroup={Permissions.FiskerCertificate} + rolesPerGroup={Permissions.FiskerMagnaCertificate} providers={providers} /> Date: Tue, 15 Nov 2022 18:30:10 -0800 Subject: [PATCH 3/5] fix docker --- .github/workflows/deploy.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bceb3fd..e9cc99f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,3 +1,5 @@ +name: OTA Portal Deploy + on: push: branches: @@ -33,12 +35,12 @@ jobs: with: creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Docker login - uses: azure/docker-login@v1 + - name: Login to ACR + uses: docker/login-action@v2 with: - login-server: ${{ env.REGISTRY }} - username: ${{ secrets.REGISTRY_USERNAME }} - password: ${{ secrets.REGISTRY_PASSWORD }} + registry: ${{ env.REGISTRY }} + username: ${{ secrets.AZURE_CLIENT_ID }} + password: ${{ secrets.AZURE_CLIENT_SECRET }} - name: Set Env id: set-env From ae2d5e69f3450e7e18f7307648618663528b92a2 Mon Sep 17 00:00:00 2001 From: John Wu <76966357+jwu-fisker@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:32:48 -0800 Subject: [PATCH 4/5] CEC-3119 Fix tabs (#238) --- package-lock.json | 109 +++++++---------- .../App/__snapshots__/App.test.js.snap | 8 +- .../Status/__snapshots__/index.test.jsx.snap | 8 +- src/components/Cars/Status/index.jsx | 111 +++++++++--------- src/components/Controls/RoleWrap/index.js | 2 +- 5 files changed, 112 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index f858b27..f763e27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6106,9 +6106,9 @@ } }, "node_modules/babel-loader/node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -7317,14 +7317,6 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, - "node_modules/css-select/node_modules/nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "dependencies": { - "boolbase": "~1.0.0" - } - }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -11844,9 +11836,9 @@ } }, "node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -12321,6 +12313,14 @@ "node": ">=8" } }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, "node_modules/nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -14434,9 +14434,9 @@ } }, "node_modules/react-dev-utils/node_modules/loader-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", - "integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", "engines": { "node": ">= 12.13.0" } @@ -14733,25 +14733,14 @@ } }, "node_modules/recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dependencies": { - "minimatch": "3.0.4" + "minimatch": "^3.0.5" }, "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/recursive-readdir/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "node": ">=6.0.0" } }, "node_modules/redent": { @@ -21863,9 +21852,9 @@ } }, "loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", + "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -22741,16 +22730,6 @@ "css-what": "^3.2.1", "domutils": "^1.7.0", "nth-check": "^1.0.2" - }, - "dependencies": { - "nth-check": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", - "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", - "requires": { - "boolbase": "~1.0.0" - } - } } }, "css-select-base-adapter": { @@ -26086,9 +26065,9 @@ "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" }, "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -26433,6 +26412,14 @@ "path-key": "^3.0.0" } }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -27842,9 +27829,9 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "loader-utils": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", - "integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==" }, "supports-color": { "version": "5.5.0", @@ -28072,21 +28059,11 @@ } }, "recursive-readdir": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", - "integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", + "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "requires": { - "minimatch": "3.0.4" - }, - "dependencies": { - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - } + "minimatch": "^3.0.5" } }, "redent": { diff --git a/src/components/App/__snapshots__/App.test.js.snap b/src/components/App/__snapshots__/App.test.js.snap index 91462b2..af9ea5e 100644 --- a/src/components/App/__snapshots__/App.test.js.snap +++ b/src/components/App/__snapshots__/App.test.js.snap @@ -7536,6 +7536,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `