Merge branch 'release/0.0.3'

This commit is contained in:
jwu-fisker
2022-11-22 17:16:22 -08:00
79 changed files with 1409 additions and 1011 deletions

View File

@@ -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_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_AUTH_CALLBACK_URL=https://ota-admin.cec-prd.fiskerinc.com
REACT_APP_SUPERSET_URL=https://superset.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

View File

@@ -3,3 +3,5 @@ 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_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_AUTH_CALLBACK_URL=https://dev-ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com 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

View File

@@ -3,4 +3,5 @@ REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_SUPERSET_URL=https://dev-superset-new.cloud.fiskerinc.com 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

View File

@@ -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_UPLOAD_SERVICE_URL=https://gw.cloud.fiskerinc.com/ota_update
REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com REACT_APP_AUTH_CALLBACK_URL=https://ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=https://superset.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

View File

@@ -3,3 +3,5 @@ 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_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_AUTH_CALLBACK_URL=https://stg-ota-admin.cloud.fiskerinc.com
REACT_APP_SUPERSET_URL=https://stg-superset.cloud.fiskerinc.com 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

View File

@@ -3,3 +3,5 @@ REACT_APP_UPLOAD_SERVICE_URL=http://localhost/ota_update
REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000 REACT_APP_AUTH_CALLBACK_URL=http://localhost:3000
REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com REACT_APP_SUPERSET_URL=https://dev-superset.cloud.fiskerinc.com
REACT_APP_CERT_SERVICE_URL=http://localhost/certificate REACT_APP_CERT_SERVICE_URL=http://localhost/certificate
REACT_APP_MAGNA_PROVIDER=Magna
REACT_APP_MAGNA_GROUP_ID=68273225-9da4-4fa7-aea5-38e16ec471fe

View File

@@ -1,3 +1,5 @@
name: OTA Portal Deploy
on: on:
push: push:
branches: branches:
@@ -33,12 +35,12 @@ jobs:
with: with:
creds: ${{ secrets.AZURE_CREDENTIALS }} creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Docker login - name: Login to ACR
uses: azure/docker-login@v1 uses: docker/login-action@v2
with: with:
login-server: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.AZURE_CLIENT_ID }}
password: ${{ secrets.REGISTRY_PASSWORD }} password: ${{ secrets.AZURE_CLIENT_SECRET }}
- name: Set Env - name: Set Env
id: set-env id: set-env

186
package-lock.json generated
View File

@@ -37,7 +37,8 @@
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"usehooks-ts": "^2.7.1", "usehooks-ts": "^2.7.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4",
"webpack": "^5.74.0"
}, },
"devDependencies": { "devDependencies": {
"react-test-renderer": "^17.0.2" "react-test-renderer": "^17.0.2"
@@ -5607,9 +5608,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.7.0", "version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -6105,9 +6106,9 @@
} }
}, },
"node_modules/babel-loader/node_modules/loader-utils": { "node_modules/babel-loader/node_modules/loader-utils": {
"version": "1.4.0", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dependencies": { "dependencies": {
"big.js": "^5.2.2", "big.js": "^5.2.2",
"emojis-list": "^3.0.0", "emojis-list": "^3.0.0",
@@ -7316,14 +7317,6 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" "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": { "node_modules/css-tree": {
"version": "1.0.0-alpha.37", "version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -8028,9 +8021,9 @@
} }
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.9.2", "version": "5.10.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
"integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
"dependencies": { "dependencies": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"tapable": "^2.2.0" "tapable": "^2.2.0"
@@ -11602,11 +11595,6 @@
"node": ">=4" "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": { "node_modules/json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -11848,9 +11836,9 @@
} }
}, },
"node_modules/loader-utils": { "node_modules/loader-utils": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dependencies": { "dependencies": {
"big.js": "^5.2.2", "big.js": "^5.2.2",
"emojis-list": "^3.0.0", "emojis-list": "^3.0.0",
@@ -12325,6 +12313,14 @@
"node": ">=8" "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": { "node_modules/nwsapi": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
@@ -14438,9 +14434,9 @@
} }
}, },
"node_modules/react-dev-utils/node_modules/loader-utils": { "node_modules/react-dev-utils/node_modules/loader-utils": {
"version": "3.2.0", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz",
"integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==", "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==",
"engines": { "engines": {
"node": ">= 12.13.0" "node": ">= 12.13.0"
} }
@@ -14737,25 +14733,14 @@
} }
}, },
"node_modules/recursive-readdir": { "node_modules/recursive-readdir": {
"version": "2.2.2", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
"integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
"dependencies": { "dependencies": {
"minimatch": "3.0.4" "minimatch": "^3.0.5"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=6.0.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_modules/redent": { "node_modules/redent": {
@@ -16635,9 +16620,9 @@
} }
}, },
"node_modules/watchpack": { "node_modules/watchpack": {
"version": "2.3.1", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"dependencies": { "dependencies": {
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2" "graceful-fs": "^4.1.2"
@@ -16668,33 +16653,33 @@
} }
}, },
"node_modules/webpack": { "node_modules/webpack": {
"version": "5.70.0", "version": "5.74.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
"integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
"dependencies": { "dependencies": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^0.0.51", "@types/estree": "^0.0.51",
"@webassemblyjs/ast": "1.11.1", "@webassemblyjs/ast": "1.11.1",
"@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1",
"@webassemblyjs/wasm-parser": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1",
"acorn": "^8.4.1", "acorn": "^8.7.1",
"acorn-import-assertions": "^1.7.6", "acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5", "browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.9.2", "enhanced-resolve": "^5.10.0",
"es-module-lexer": "^0.9.0", "es-module-lexer": "^0.9.0",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9", "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", "loader-runner": "^4.2.0",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"neo-async": "^2.6.2", "neo-async": "^2.6.2",
"schema-utils": "^3.1.0", "schema-utils": "^3.1.0",
"tapable": "^2.1.1", "tapable": "^2.1.1",
"terser-webpack-plugin": "^5.1.3", "terser-webpack-plugin": "^5.1.3",
"watchpack": "^2.3.1", "watchpack": "^2.4.0",
"webpack-sources": "^3.2.3" "webpack-sources": "^3.2.3"
}, },
"bin": { "bin": {
@@ -21520,9 +21505,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "8.7.0", "version": "8.8.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==" "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA=="
}, },
"acorn-globals": { "acorn-globals": {
"version": "6.0.0", "version": "6.0.0",
@@ -21867,9 +21852,9 @@
} }
}, },
"loader-utils": { "loader-utils": {
"version": "1.4.0", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
"integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"requires": { "requires": {
"big.js": "^5.2.2", "big.js": "^5.2.2",
"emojis-list": "^3.0.0", "emojis-list": "^3.0.0",
@@ -22745,16 +22730,6 @@
"css-what": "^3.2.1", "css-what": "^3.2.1",
"domutils": "^1.7.0", "domutils": "^1.7.0",
"nth-check": "^1.0.2" "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": { "css-select-base-adapter": {
@@ -23304,9 +23279,9 @@
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
}, },
"enhanced-resolve": { "enhanced-resolve": {
"version": "5.9.2", "version": "5.10.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz",
"integrity": "sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA==", "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==",
"requires": { "requires": {
"graceful-fs": "^4.2.4", "graceful-fs": "^4.2.4",
"tapable": "^2.2.0" "tapable": "^2.2.0"
@@ -25886,11 +25861,6 @@
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" "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": { "json-parse-even-better-errors": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -26095,9 +26065,9 @@
"integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==" "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw=="
}, },
"loader-utils": { "loader-utils": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"requires": { "requires": {
"big.js": "^5.2.2", "big.js": "^5.2.2",
"emojis-list": "^3.0.0", "emojis-list": "^3.0.0",
@@ -26442,6 +26412,14 @@
"path-key": "^3.0.0" "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": { "nwsapi": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
@@ -27851,9 +27829,9 @@
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
}, },
"loader-utils": { "loader-utils": {
"version": "3.2.0", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.0.tgz", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz",
"integrity": "sha512-HVl9ZqccQihZ7JM85dco1MvO9G+ONvxoGa9rkhzFsneGLKSUg1gJf9bWzhRhcvm2qChhWpebQhP44qxjKIUCaQ==" "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw=="
}, },
"supports-color": { "supports-color": {
"version": "5.5.0", "version": "5.5.0",
@@ -28081,21 +28059,11 @@
} }
}, },
"recursive-readdir": { "recursive-readdir": {
"version": "2.2.2", "version": "2.2.3",
"resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.2.tgz", "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz",
"integrity": "sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==",
"requires": { "requires": {
"minimatch": "3.0.4" "minimatch": "^3.0.5"
},
"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"
}
}
} }
}, },
"redent": { "redent": {
@@ -29497,9 +29465,9 @@
} }
}, },
"watchpack": { "watchpack": {
"version": "2.3.1", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
"integrity": "sha512-x0t0JuydIo8qCNctdDrn1OzH/qDzk2+rdCOC3YzumZ42fiMqmQ7T3xQurykYMhYfHaPHTp4ZxAx2NfUo1K6QaA==", "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==",
"requires": { "requires": {
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2" "graceful-fs": "^4.1.2"
@@ -29524,33 +29492,33 @@
"integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==" "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w=="
}, },
"webpack": { "webpack": {
"version": "5.70.0", "version": "5.74.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.70.0.tgz", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz",
"integrity": "sha512-ZMWWy8CeuTTjCxbeaQI21xSswseF2oNOwc70QSKNePvmxE7XW36i7vpBMYZFAUHPwQiEbNGCEYIOOlyRbdGmxw==", "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==",
"requires": { "requires": {
"@types/eslint-scope": "^3.7.3", "@types/eslint-scope": "^3.7.3",
"@types/estree": "^0.0.51", "@types/estree": "^0.0.51",
"@webassemblyjs/ast": "1.11.1", "@webassemblyjs/ast": "1.11.1",
"@webassemblyjs/wasm-edit": "1.11.1", "@webassemblyjs/wasm-edit": "1.11.1",
"@webassemblyjs/wasm-parser": "1.11.1", "@webassemblyjs/wasm-parser": "1.11.1",
"acorn": "^8.4.1", "acorn": "^8.7.1",
"acorn-import-assertions": "^1.7.6", "acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5", "browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2", "chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.9.2", "enhanced-resolve": "^5.10.0",
"es-module-lexer": "^0.9.0", "es-module-lexer": "^0.9.0",
"eslint-scope": "5.1.1", "eslint-scope": "5.1.1",
"events": "^3.2.0", "events": "^3.2.0",
"glob-to-regexp": "^0.4.1", "glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.9", "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", "loader-runner": "^4.2.0",
"mime-types": "^2.1.27", "mime-types": "^2.1.27",
"neo-async": "^2.6.2", "neo-async": "^2.6.2",
"schema-utils": "^3.1.0", "schema-utils": "^3.1.0",
"tapable": "^2.1.1", "tapable": "^2.1.1",
"terser-webpack-plugin": "^5.1.3", "terser-webpack-plugin": "^5.1.3",
"watchpack": "^2.3.1", "watchpack": "^2.4.0",
"webpack-sources": "^3.2.3" "webpack-sources": "^3.2.3"
}, },
"dependencies": { "dependencies": {

View File

@@ -32,7 +32,8 @@
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"usehooks-ts": "^2.7.1", "usehooks-ts": "^2.7.1",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4",
"webpack": "^5.74.0"
}, },
"scripts": { "scripts": {
"start": "env-cmd -f .env.local react-scripts start", "start": "env-cmd -f .env.local react-scripts start",
@@ -51,7 +52,10 @@
"extends": [ "extends": [
"react-app", "react-app",
"react-app/jest" "react-app/jest"
] ],
"rules": {
"no-console": "warn"
}
}, },
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",

View File

@@ -16,7 +16,7 @@ import {
waitForElementToBeRemoved, waitForElementToBeRemoved,
} from "@testing-library/react"; } from "@testing-library/react";
import { setToken } from "../Contexts/UserContext"; 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 App from ".";
import addSnapshotSerializer from "../../utils/snapshot"; import addSnapshotSerializer from "../../utils/snapshot";
@@ -35,7 +35,7 @@ const check = async (path, selector, compare) => {
let container let container
await act(async () => { await act(async () => {
container = await renderRoute(path); container = await renderRoute(path);
}) });
expect(container.querySelector(selector).innerHTML).toEqual(compare); expect(container.querySelector(selector).innerHTML).toEqual(compare);
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}; };
@@ -117,57 +117,57 @@ describe("App", () => {
await check("/page-not-found", "h1", "Page Not Found"); await check("/page-not-found", "h1", "Page Not Found");
}); });
it("Route / authenticated", async () => { it("Route / authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await sleepAndCheck("/", "h6", "Home"); await sleepAndCheck("/", "h6", "Home");
}); });
it("Route /home authenticated", async () => { it("Route /home authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await sleepAndCheck("/home", "h6", "Home"); await sleepAndCheck("/home", "h6", "Home");
}); });
it("Route /page-not-found authenticated", async () => { 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"); await check("/page-not-found", "h1", "Page Not Found");
}); });
it("Route /packages authenticated", async () => { it("Route /packages authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/packages", "h6", "Deployments"); await check("/packages", "h6", "Deployments");
}); });
it("Route /package-status authenticated", async () => { 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"); await check("/package-status/1", "h6", "Manifest Test Manifest 1.0");
}); });
it("Route /package-deploy authenticated", async () => { 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"); await check("/package-deploy/1", "h6", "Deploy Test Manifest 1.0");
}); });
it("Route /vehicle-add authenticated", async () => { it("Route /vehicle-add authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/vehicle-add", "h6", "Add Vehicle"); await check("/vehicle-add", "h6", "Add Vehicle");
}); });
it("Route /vehicles authenticated", async () => { it("Route /vehicles authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/vehicles", "h6", "Vehicles"); await check("/vehicles", "h6", "Vehicles");
}); });
it("Route /vehicle-status authenticated", async () => { it("Route /vehicle-status authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details"); await check("/vehicle-status/FISKER123", "h6", "Vehicle FISKER123 Details");
}); });
it("Route /tools/certificates/add authenticated", async () => { it("Route /tools/certificates/add authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/tools/certificates/add", "h6", "Create Certificate"); await sleepAndCheck("/tools/certificates/add", "h6", "Create Certificate");
}); });
it("Route /tools/sms/send authenticated", async () => { it("Route /tools/sms/send authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
await check("/tools/sms/send", "h6", "Send SMS"); await sleepAndCheck("/tools/sms/send", "h6", "Send SMS");
}); });
}); });

View File

@@ -35,7 +35,7 @@ exports[`App Route / authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -270,6 +270,42 @@ exports[`App Route / authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -594,7 +630,7 @@ exports[`App Route /home authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -829,6 +865,42 @@ exports[`App Route /home authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -1189,7 +1261,7 @@ exports[`App Route /package-deploy authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -1424,6 +1496,42 @@ exports[`App Route /package-deploy authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -2148,7 +2256,7 @@ exports[`App Route /package-status authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -2383,6 +2491,42 @@ exports[`App Route /package-status authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -2825,7 +2969,7 @@ exports[`App Route /packages authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -3060,6 +3204,42 @@ exports[`App Route /packages authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -3482,25 +3662,6 @@ exports[`App Route /packages authenticated 1`] = `
/> />
</svg> </svg>
</a> </a>
<span>
<a
class=""
href="/packages"
title="Delete \\"Test Manifest 1.0\\""
>
<svg
aria-hidden="true"
aria-label="Delete Test Manifest 1.0"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -3729,7 +3890,7 @@ exports[`App Route /page-not-found authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -3964,6 +4125,42 @@ exports[`App Route /page-not-found authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -4251,7 +4448,7 @@ exports[`App Route /tools/certificates/add authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -4486,6 +4683,42 @@ exports[`App Route /tools/certificates/add authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -5006,7 +5239,7 @@ exports[`App Route /tools/sms/send authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -5241,6 +5474,42 @@ exports[`App Route /tools/sms/send authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -5627,7 +5896,7 @@ exports[`App Route /vehicle-add authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -5862,6 +6131,42 @@ exports[`App Route /vehicle-add authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -6806,7 +7111,7 @@ exports[`App Route /vehicle-status authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -7041,6 +7346,42 @@ exports[`App Route /vehicle-status authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -7397,23 +7738,6 @@ exports[`App Route /vehicle-status authenticated 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/vehicle-status/FISKER123"
title="Delete \\"FISKER123\\""
>
<svg
aria-hidden="true"
aria-label="Delete \\"FISKER123\\""
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</div> </div>
</div> </div>
<div /> <div />
@@ -7442,21 +7766,18 @@ exports[`App Route /vehicle-status authenticated 1`] = `
/> />
<div <div
aria-labelledby="tab-4" aria-labelledby="tab-4"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-4" id="tabpanel-4"
role="tabpanel" role="tabpanel"
/> />
<div <div
aria-labelledby="tab-5" aria-labelledby="tab-5"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-5" id="tabpanel-5"
role="tabpanel" role="tabpanel"
/> />
<div <div
aria-labelledby="tab-6" aria-labelledby="tab-6"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-6" id="tabpanel-6"
role="tabpanel" role="tabpanel"
@@ -7616,7 +7937,7 @@ exports[`App Route /vehicles authenticated 1`] = `
<span <span
class="MuiButton-label" class="MuiButton-label"
> >
John Anvar
</span> </span>
<span <span
class="MuiTouchRipple-root" class="MuiTouchRipple-root"
@@ -7851,6 +8172,42 @@ exports[`App Route /vehicles authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -7960,22 +8317,7 @@ exports[`App Route /vehicles authenticated 1`] = `
> >
<div <div
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4"
>
<a
href="/vehicle-add"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
/> />
</svg>
</a>
</div>
<div <div
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"
> >

View File

@@ -1,3 +1,5 @@
import {TEST_AUTH_OBJECT_FISKER} from "../../../utils/testing";
jest.mock("../../Contexts/CANFiltersContext"); jest.mock("../../Contexts/CANFiltersContext");
jest.mock("../../Contexts/StatusContext"); jest.mock("../../Contexts/StatusContext");
jest.mock("../../Contexts/UserContext"); jest.mock("../../Contexts/UserContext");
@@ -8,7 +10,6 @@ import { BrowserRouter } from "react-router-dom";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; import { UserProvider, setToken } from "../../Contexts/UserContext";
import { TEST_AUTH_OBJECT } from "../../../utils/testing";
import MainForm from "./index"; import MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -35,7 +36,7 @@ describe("CANFiltersAdd", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANFiltersAdd(); const container = await renderCANFiltersAdd();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -250,23 +250,6 @@ exports[`CANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"123\\""
>
<svg
aria-hidden="true"
aria-label="Delete 123"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -313,23 +296,6 @@ exports[`CANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"456-789\\""
>
<svg
aria-hidden="true"
aria-label="Delete 456-789"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -376,23 +342,6 @@ exports[`CANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -1,5 +1,3 @@
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
import { import {
Grid, Grid,
Table, Table,
@@ -12,46 +10,46 @@ import {
} from "@material-ui/core"; } from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle"; import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete"; 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 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 { import {
CANFiltersProvider, CANFiltersProvider,
useCANFiltersContext, useCANFiltersContext,
} from "../../Contexts/CANFiltersContext"; } from "../../Contexts/CANFiltersContext";
import TableHeaderSortable from "../../Table/HeaderSortable";
import {
useUserContext
} from "../../Contexts/UserContext"
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import { useUserContext } from "../../Contexts/UserContext";
import SearchField from "../../Controls/SearchField"; import SearchField from "../../Controls/SearchField";
import { logger } from "../../../services/monitoring"; import TableHeaderSortable from "../../Table/HeaderSortable";
import { Roles, hasRole } from "../../../utils/roles";
import { useLocalStorage } from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
import {trimIfMoreThan} from "../../../utils/strings"; import useStyles from "../../useStyles";
const tableColumns = [ const tableColumns = [
{ {
id: "can_id", id: "can_id",
label: "CAN ID" label: "CAN ID",
}, },
{ {
id: "interval", id: "interval",
label: "Interval (ms)" label: "Interval (ms)",
}, },
{ {
id: "fleet", id: "fleet",
label: "Fleet" label: "Fleet",
}, },
{ {
id: "edge_mask", id: "edge_mask",
label: "Edge mask" label: "Edge mask",
}, },
{ {
id: "", id: "",
label: "Actions" label: "Actions",
} },
]; ];
const PAGE_SIZE = "CAN_FILTER_TABLE_PAGE_SIZE"; const PAGE_SIZE = "CAN_FILTER_TABLE_PAGE_SIZE";
@@ -63,9 +61,16 @@ const MainForm = ({ vin }) => {
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
const [orderBy, setOrderBy] = useState("id"); const [orderBy, setOrderBy] = useState("id");
const [order, setOrder] = useState("desc"); const [order, setOrder] = useState("desc");
const { getFilters, deleteFilter, filters, totalFilters } = useCANFiltersContext(); const { getFilters, deleteFilter, filters, totalFilters } =
useCANFiltersContext();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -118,7 +123,7 @@ const MainForm = ({ vin }) => {
const onDelete = async (can_id) => { const onDelete = async (can_id) => {
try { try {
await deleteFilter(vin, can_id, token); await deleteFilter(vin, can_id, token);
setMessage(`Deleted ${can_id}`) setMessage(`Deleted ${can_id}`);
} catch (e) { } catch (e) {
setMessage(e.message); setMessage(e.message);
logger.warn(e.stack); logger.warn(e.stack);
@@ -127,23 +132,23 @@ const MainForm = ({ vin }) => {
const Actions = (row) => { const Actions = (row) => {
if (row.fleet != null) { if (row.fleet != null) {
return return;
} }
let actions = []; let actions = [];
if (hasRole([Roles.CREATE], groups)) { if (hasRole(groups, Permissions.FiskerCreate, providers)) {
actions.push({ actions.push({
tip: `Update "${row.can_id}"`, tip: `Update "${row.can_id}"`,
link: `/filter-update?vin=${vin}&can_id=${row.can_id}&interval=${row.interval}`, link: `/filter-update?vin=${vin}&can_id=${row.can_id}&interval=${row.interval}`,
icon: <EditIcon aria-label={`Update ${row.can_id}`} /> icon: <EditIcon aria-label={`Update ${row.can_id}`} />,
}); });
} }
if (hasRole([Roles.DELETE], groups)) { if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({ actions.push({
tip: `Delete "${row.can_id}"`, tip: `Delete "${row.can_id}"`,
id: row.can_id, id: row.can_id,
icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} /> icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} />,
}) });
} }
if (actions.length === 0) return ["No actions"]; if (actions.length === 0) return ["No actions"];
@@ -196,7 +201,9 @@ const MainForm = ({ vin }) => {
<TableCell align="center"> <TableCell align="center">
<Link to={`/fleet/${row.fleet}`}>{row.fleet}</Link> <Link to={`/fleet/${row.fleet}`}>{row.fleet}</Link>
</TableCell> </TableCell>
<TableCell align="center">{trimIfMoreThan(row.edge_mask, 15, "...")}</TableCell> <TableCell align="center">
{trimIfMoreThan(row.edge_mask, 15, "...")}
</TableCell>
<TableCell align="center">{Actions(row)}</TableCell> <TableCell align="center">{Actions(row)}</TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -37,7 +37,7 @@ describe("CANFiltersTable", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANFiltersTable(); const container = await renderCANFiltersTable();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -34,7 +34,7 @@ describe("CANFiltersUpdate", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANFiltersUpdate(); const container = await renderCANFiltersUpdate();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -36,7 +36,7 @@ describe("VehicleAddForm", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderVehicleAdd(); const container = await renderVehicleAdd();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -22,22 +22,7 @@ exports[`VehicleTable Render 1`] = `
> >
<div <div
class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textJustifyAlign-0 MuiGrid-item MuiGrid-grid-md-4"
>
<a
href="/vehicle-add"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root MuiSvgIcon-fontSizeLarge"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm5 11h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"
/> />
</svg>
</a>
</div>
<div <div
class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4" class="MuiGrid-root makeStyles-textCenterAlign-0 MuiGrid-item MuiGrid-grid-md-4"
> >

View File

@@ -1,16 +1,18 @@
import React, { useEffect, useState } from "react";
import { Checkbox, FormControlLabel, Grid, MenuItem } from "@material-ui/core"; import { Checkbox, FormControlLabel, Grid, MenuItem } from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle"; import AddCircleIcon from "@material-ui/icons/AddCircle";
import { Link } from "react-router-dom";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext"; import { Permissions } from "../../../utils/roles";
import { useUserContext } from "../../Contexts/UserContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import { useUserContext } from "../../Contexts/UserContext";
import SearchField from "../../Controls/SearchField"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import CarSelectionTable from "../../Controls/CarSelectionTable"; import CarSelectionTable from "../../Controls/CarSelectionTable";
import OptionsDropdown from "../../Controls/OptionsDropdown"; import OptionsDropdown from "../../Controls/OptionsDropdown";
import { RoleWrap } from "../../Controls/RoleWrap";
import SearchField from "../../Controls/SearchField";
import useStyles from "../../useStyles";
const MainForm = () => { const MainForm = () => {
const classes = useStyles(); const classes = useStyles();
@@ -22,6 +24,8 @@ const MainForm = () => {
token: { token: {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups,
providers,
} = useUserContext(); } = useUserContext();
const handleSearch = (query) => { const handleSearch = (query) => {
@@ -30,11 +34,11 @@ const MainForm = () => {
const handleOnline = (event) => { const handleOnline = (event) => {
setOnline(event.target.checked); setOnline(event.target.checked);
} };
const handleOnlineHMI = (event) => { const handleOnlineHMI = (event) => {
setOnlineHMI(event.target.checked); setOnlineHMI(event.target.checked);
} };
useEffect(() => { useEffect(() => {
setTitle("Vehicles"); setTitle("Vehicles");
@@ -46,30 +50,34 @@ const MainForm = () => {
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}> <Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}> <Grid item md={4} className={classes.textJustifyAlign}>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerDelete}
>
<Link to="/vehicle-add"> <Link to="/vehicle-add">
<AddCircleIcon fontSize="large" /> <AddCircleIcon fontSize="large" />
</Link> </Link>
</RoleWrap>
</Grid> </Grid>
<Grid item md={4} className={classes.textCenterAlign}> <Grid item md={4} className={classes.textCenterAlign}>
<SearchField classes={classes} onSearch={handleSearch} /> <SearchField classes={classes} onSearch={handleSearch} />
</Grid> </Grid>
<Grid item md={2} className={classes.textJustifyAlign}> <Grid item md={2} className={classes.textJustifyAlign}>
<OptionsDropdown listId='filter-menu'> <OptionsDropdown listId="filter-menu">
<MenuItem> <MenuItem>
<FormControlLabel control={ <FormControlLabel
<Checkbox control={<Checkbox checked={online} onChange={handleOnline} />}
checked={online} label="Only online"
onChange={handleOnline}
/> />
} label="Only online" />
</MenuItem> </MenuItem>
<MenuItem> <MenuItem>
<FormControlLabel control={ <FormControlLabel
<Checkbox control={
checked={onlineHMI} <Checkbox checked={onlineHMI} onChange={handleOnlineHMI} />
onChange={handleOnlineHMI} }
label="Only online HMI"
/> />
} label="Only online HMI" />
</MenuItem> </MenuItem>
</OptionsDropdown> </OptionsDropdown>
</Grid> </Grid>
@@ -79,7 +87,11 @@ const MainForm = () => {
classes={classes} classes={classes}
token={token} token={token}
multiSelect={false} multiSelect={false}
search={{ search, online: online? true : null, online_hmi: onlineHMI? true : null }} search={{
search,
online: online ? true : null,
online_hmi: onlineHMI ? true : null,
}}
/> />
</div> </div>
); );

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -39,7 +39,7 @@ describe("VehicleTable", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderVehicleTable(); const container = await renderVehicleTable();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { setToken } from "../../Contexts/UserContext"; 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 CANFiltersTab from "./CANFiltersTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -31,7 +31,7 @@ describe("CANFiltersTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANFiltersTab(); const container = await renderCANFiltersTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -11,7 +11,7 @@ import { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import CANSignalsTab from "./CANSignalsTab"; import CANSignalsTab from "./CANSignalsTab";
import {setToken} from "../../Contexts/UserContext"; import {setToken} from "../../Contexts/UserContext";
import {TEST_AUTH_OBJECT} from "../../../utils/testing"; import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
const renderCANSignalsTab = async () => { const renderCANSignalsTab = async () => {
const { container } = render( const { container } = render(
@@ -31,7 +31,7 @@ describe("CANSignalsTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANSignalsTab(); const container = await renderCANSignalsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./CarUpdatesTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -39,7 +39,7 @@ describe("CarUpdatesTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCarUpdatesTab(); const container = await renderCarUpdatesTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -101,23 +101,6 @@ exports[`VehicleDetailsTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"TESTVIN1234567890\\""
>
<svg
aria-hidden="true"
aria-label="Delete \\"TESTVIN1234567890\\""
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</div> </div>
</div> </div>
<div /> <div />

View File

@@ -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 React, { useEffect, useState } from "react";
import { Redirect } from "react-router"; import { Redirect } from "react-router";
import { Link } from 'react-router-dom'; 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 { useUserContext } from "../../../Contexts/UserContext"
import { useStatusContext } from "../../../Contexts/StatusContext";
import useStyles from "../../../useStyles";
import { logger } from "../../../../services/monitoring"; 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 DeleteConfirmation from "../../../DeleteConfirmation";
import useStyles from "../../../useStyles";
const MainForm = ({ vin }) => { const MainForm = ({ vin }) => {
const classes = useStyles(); const classes = useStyles();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
const { vehicle, getVehicle, deleteVehicle } = useVehicleContext(); const { vehicle, getVehicle, deleteVehicle } = useVehicleContext();
const [redirect, setRedirect] = useState(null); const [redirect, setRedirect] = useState(null);
const [showDeleteModal, setShowDeleteModal] = useState(false) const [showDeleteModal, setShowDeleteModal] = useState(false);
const { token: { idToken: { jwtToken: token } } } = useUserContext(); const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -40,7 +48,7 @@ const MainForm = ({ vin }) => {
const onDelete = async () => { const onDelete = async () => {
try { try {
await deleteVehicle(vin, token); await deleteVehicle(vin, token);
setMessage(`Deleted ${vin}`) setMessage(`Deleted ${vin}`);
setRedirect(`/vehicles`); setRedirect(`/vehicles`);
} catch (e) { } catch (e) {
setMessage(e.message); setMessage(e.message);
@@ -56,35 +64,66 @@ const MainForm = ({ vin }) => {
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}> <Grid container className={classes.root} spacing={2}>
<Grid item md={12} className={classes.textCenterAlign}> <Grid item md={12} className={classes.textCenterAlign}>
<p><b>VIN</b>: {vin}</p> <p>
<b>VIN</b>: {vin}
</p>
{vehicle.log_level != null && ( {vehicle.log_level != null && (
<p><b>Log Level</b>: {vehicle.log_level}</p> <p>
<b>Log Level</b>: {vehicle.log_level}
</p>
)} )}
</Grid> </Grid>
{vehicle.canbus && ( {vehicle.canbus && (
<Grid item md={12} className={classes.textCenterAlign}> <Grid item md={12} className={classes.textCenterAlign}>
<b>CANBus</b> <b>CANBus</b>
<p><b>Enabled</b>: {vehicle.canbus.enabled.toString()}</p> <p>
<p><b>Max Memory Buffer Size</b>: {vehicle.canbus.max_mem_buffer_size ?? "Default"}</p> <b>Enabled</b>: {vehicle.canbus.enabled.toString()}
<p><b>Enabled</b>: {vehicle.canbus.data_logger_enabled.toString()}</p> </p>
<p><b>Max Disk Buffer Size</b>: {vehicle.canbus.max_disk_buffer_size ?? "Default"}</p> <p>
<p><b>Filters</b>: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}</p> <b>Max Memory Buffer Size</b>: {vehicle.canbus.max_mem_buffer_size ?? "Default"}
</p>
<p>
<b>Enabled</b>: {vehicle.canbus.data_logger_enabled.toString()}
</p>
<p>
<b>Max Disk Buffer Size</b>: {vehicle.canbus.max_disk_buffer_size ?? "Default"}
</p>
<p>
<b>Filters</b>: {vehicle.canbus.filters ? vehicle.canbus.filters.length : 0}
</p>
</Grid> </Grid>
)} )}
<Grid item md={12} className={classes.textCenterAlign}> <Grid item md={12} className={classes.textCenterAlign}>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerCreate}
>
<Tooltip key={`update-${vin}`} title={`Update "${vin}"`}> <Tooltip key={`update-${vin}`} title={`Update "${vin}"`}>
<Link to={`/vehicle-update?vin=${vin}`} style={{ margin: 5 }}> <Link to={`/vehicle-update?vin=${vin}`} style={{ margin: 5 }}>
<EditIcon aria-label={`Update "${vin}"`} /> <EditIcon aria-label={`Update "${vin}"`} />
</Link> </Link>
</Tooltip> </Tooltip>
</RoleWrap>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerDelete}
>
<Tooltip key={`delete-${vin}`} title={`Delete "${vin}"`}> <Tooltip key={`delete-${vin}`} title={`Delete "${vin}"`}>
<Link to="#" onClick={() => setShowDeleteModal(true)}> <Link to="#" onClick={() => setShowDeleteModal(true)}>
<DeleteIcon aria-label={`Delete "${vin}"`} /> <DeleteIcon aria-label={`Delete "${vin}"`} />
</Link> </Link>
</Tooltip> </Tooltip>
</RoleWrap>
</Grid> </Grid>
</Grid> </Grid>
<DeleteConfirmation message={vin} open={showDeleteModal} close={()=> setShowDeleteModal(false)} deleteFunction={onDelete}/> <DeleteConfirmation
message={vin}
open={showDeleteModal}
close={() => setShowDeleteModal(false)}
deleteFunction={onDelete}
/>
</div> </div>
); );
}; };

View File

@@ -9,7 +9,7 @@ import routeData from "react-router";
import { VehicleProvider } from "../../../Contexts/VehicleContext"; import { VehicleProvider } from "../../../Contexts/VehicleContext";
import { StatusProvider } from "../../../Contexts/StatusContext"; import { StatusProvider } from "../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../utils/snapshot";
@@ -42,7 +42,7 @@ describe("VehicleDetailsTab", () => {
}) })
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderVehicleDetailsTab(); const container = await renderVehicleDetailsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -11,7 +11,7 @@ import { MemoryRouter, Route } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./DetailsTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -41,7 +41,7 @@ describe("DetailsTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderDetailsTab(); const container = await renderDetailsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -10,7 +10,7 @@ import { render, waitFor } from "@testing-library/react";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 DigitalTwinTab from "./DigitalTwinTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -34,7 +34,7 @@ describe("DigitalTwinTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderDetailsTab(); const container = await renderDetailsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -12,7 +12,7 @@ import { BrowserRouter } from "react-router-dom";
import {StatusProvider} from "../../Contexts/StatusContext"; import {StatusProvider} from "../../Contexts/StatusContext";
import {VehicleProvider} from "../../Contexts/VehicleContext"; import {VehicleProvider} from "../../Contexts/VehicleContext";
import {setToken, UserProvider} from "../../Contexts/UserContext"; 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 addSnapshotSerializer from "../../../utils/snapshot";
import FleetsTab from "./FleetsTab"; import FleetsTab from "./FleetsTab";
@@ -40,7 +40,7 @@ describe("FleetsTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetsTab(); const container = await renderFleetsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -10,7 +10,7 @@ import {StatusProvider} from "../../Contexts/StatusContext";
import {setToken, UserProvider} from "../../Contexts/UserContext"; import {setToken, UserProvider} from "../../Contexts/UserContext";
import RemoteCommandsTab from "./RemoteCommandsTab"; import RemoteCommandsTab from "./RemoteCommandsTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
import {TEST_AUTH_OBJECT} from "../../../utils/testing"; import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import React from "react"; import React from "react";
const renderRemoteCommandsTab = async () => { const renderRemoteCommandsTab = async () => {
@@ -33,7 +33,7 @@ describe("RemoteCommandsTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderRemoteCommandsTab(); const container = await renderRemoteCommandsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -249,23 +249,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"123\\""
>
<svg
aria-hidden="true"
aria-label="Delete 123"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -312,23 +295,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"456-789\\""
>
<svg
aria-hidden="true"
aria-label="Delete 456-789"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -375,23 +341,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -109,23 +109,6 @@ exports[`DetailsTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/testroute/TESTVIN1234567890"
title="Delete \\"TESTVIN1234567890\\""
>
<svg
aria-hidden="true"
aria-label="Delete \\"TESTVIN1234567890\\""
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</div> </div>
</div> </div>
<div /> <div />

View File

@@ -217,23 +217,6 @@ exports[`CarStatus Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"TESTVIN1234567890\\""
>
<svg
aria-hidden="true"
aria-label="Delete \\"TESTVIN1234567890\\""
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</div> </div>
</div> </div>
<div /> <div />
@@ -261,21 +244,18 @@ exports[`CarStatus Render 1`] = `
/> />
<div <div
aria-labelledby="tab-4" aria-labelledby="tab-4"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-4" id="tabpanel-4"
role="tabpanel" role="tabpanel"
/> />
<div <div
aria-labelledby="tab-5" aria-labelledby="tab-5"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-5" id="tabpanel-5"
role="tabpanel" role="tabpanel"
/> />
<div <div
aria-labelledby="tab-6" aria-labelledby="tab-6"
class="makeStyles-fullWidth-0"
hidden="" hidden=""
id="tabpanel-6" id="tabpanel-6"
role="tabpanel" role="tabpanel"

View File

@@ -1,28 +1,80 @@
import React, { useEffect } from "react"; import { Box, Tab, Tabs } from "@material-ui/core";
import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { useParams } from "react-router"; import { useParams } from "react-router";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import clsx from "clsx";
import { Box, Tab, Tabs } from "@material-ui/core";
import CarDetailsTab from "./DetailsTab"; import { hasRole, Permissions } from "../../../utils/roles";
import CarUpdatesTab from "./CarUpdatesTab";
import CANFiltersTab from "./CANFiltersTab";
import DigitalTwinTab from "./DigitalTwinTab";
import TabPanel from "../../Controls/TabPanel";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import TabPanel from "../../Controls/TabPanel";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
import CANFiltersTab from "./CANFiltersTab";
import CANSignalsTab from "./CANSignalsTab"; 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 FleetsTab from "./FleetsTab";
import RemoteCommandsTab from "./RemoteCommandsTab";
const tabHashes = ["details", "updates", "filters"]; const tabHashes = ["details", "updates", "filters"];
const TabViews = [
{
label: "Details",
component: CarDetailsTab,
},
{
label: "Car Updates",
component: CarUpdatesTab,
},
{
label: "CAN Filters",
component: CANFiltersTab,
rolesPerProvider: Permissions.FiskerRead,
},
{
label: "Digital Twin",
component: DigitalTwinTab,
},
{
label: "CAN Signals",
component: CANSignalsTab,
},
{
label: "Remote Commands",
component: RemoteCommandsTab,
rolesPerProvider: Permissions.FiskerRead,
},
{
label: "Fleets",
component: FleetsTab,
rolesPerProvider: Permissions.FiskerRead,
},
];
const filterTabs = (data, groups, providers) => {
return data.reduce((result, item) => {
if (hasRole(groups, item.rolesPerProvider, providers)) {
result.push(item);
}
return result;
}, []);
};
const CarStatus = () => { const CarStatus = () => {
const { vin } = useParams(); const { vin } = useParams();
const classes = useStyles(); const classes = useStyles();
const { setTitle, setSitePath } = useStatusContext(); const { setTitle, setSitePath } = useStatusContext();
const { hash } = useLocation(); const { hash } = useLocation();
const [tabIndex, setTabIndex] = React.useState(0); const [tabIndex, setTabIndex] = useState(0);
const [tabs, setTabs] = useState([]);
const { groups, providers } = useUserContext();
useEffect(() => {
const data = filterTabs(TabViews, groups, providers);
setTabs(data);
}, [groups, providers]);
useEffect(() => { useEffect(() => {
const key = hash.replace("#", ""); const key = hash.replace("#", "");
@@ -59,45 +111,15 @@ const CarStatus = () => {
value={tabIndex} value={tabIndex}
onChange={handleTabChange} onChange={handleTabChange}
aria-label="car tabs" aria-label="car tabs"
indicatorColor="secondary" indicatorColor="secondary">
> {tabs.map((item, index) => <Tab key={index} label={item.label} {...tabProps(index)} />)}
<Tab label="Details" {...tabProps(0)} />
<Tab label="Car Updates" {...tabProps(1)} />
<Tab label="CAN Filters" {...tabProps(2)} />
<Tab label="Digital Twin" {...tabProps(3)} />
<Tab label="CAN Signals" {...tabProps(4)} />
<Tab label="Remote Commands" {...tabProps(5)} />
<Tab label="Fleets" {...tabProps(6)} />
</Tabs> </Tabs>
</Box> </Box>
{tabs.map((item, index) => (
<TabPanel value={tabIndex} index={0}> <TabPanel key={index} value={tabIndex} index={index}>
<CarDetailsTab /> <item.component vin={vin}/>
</TabPanel>
<TabPanel value={tabIndex} index={1}>
<CarUpdatesTab />
</TabPanel>
<TabPanel value={tabIndex} index={2}>
<CANFiltersTab />
</TabPanel>
<TabPanel value={tabIndex} index={3}>
<DigitalTwinTab vin={vin} />
</TabPanel>
<TabPanel value={tabIndex} index={4} className={classes.fullWidth}>
<CANSignalsTab vin={vin} />
</TabPanel>
<TabPanel value={tabIndex} index={5} className={classes.fullWidth}>
<RemoteCommandsTab vin={vin} />
</TabPanel>
<TabPanel value={tabIndex} index={6} className={classes.fullWidth}>
<FleetsTab vin={vin} />
</TabPanel> </TabPanel>
))}
</div> </div>
); );
}; };

View File

@@ -12,7 +12,7 @@ import routeData from "react-router";
import { CANFiltersProvider } from "../../Contexts/CANFiltersContext"; import { CANFiltersProvider } from "../../Contexts/CANFiltersContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 CarStatus from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -45,7 +45,7 @@ describe("CarStatus", () => {
}) })
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCarStatus(); const container = await renderCarStatus();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { VehicleProvider } from "../../Contexts/VehicleContext"; import { VehicleProvider } from "../../Contexts/VehicleContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -36,7 +36,7 @@ describe("VehicleUpdate", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderVehicleUpdate(); const container = await renderVehicleUpdate();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -4,11 +4,13 @@ import {
FormLabel, FormLabel,
Radio, Radio,
RadioGroup, RadioGroup,
TextField, TextField
} from "@material-ui/core"; } from "@material-ui/core";
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { CertTypeData, CertTypes } from "../../../utils/certificates"; import { CertTypeData, CertTypes } from "../../../utils/certificates";
import { Providers } from "../../../utils/roles";
import { useUserContext } from "../../Contexts/UserContext";
import useStyles from "../../useStyles"; import useStyles from "../../useStyles";
const getCertTypeLabel = (certtype) => { const getCertTypeLabel = (certtype) => {
@@ -17,9 +19,20 @@ const getCertTypeLabel = (certtype) => {
return "ID"; 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 CreateForm = ({ onCreate, busy }) => {
const classes = useStyles(); const classes = useStyles();
const commonnameEl = useRef(null); const commonnameEl = useRef(null);
const {providers} = useUserContext();
const [certType, setCertType] = useState(CertTypes.TBOX); const [certType, setCertType] = useState(CertTypes.TBOX);
const onSubmit = async (event) => { const onSubmit = async (event) => {
@@ -61,7 +74,7 @@ const CreateForm = ({ onCreate, busy }) => {
onChange={onCertTypeChange} onChange={onCertTypeChange}
margin="normal" margin="normal"
> >
{CertTypeData.map((item, i) => { {getCertsTypes(providers).map((item, i) => {
return ( return (
<FormControlLabel <FormControlLabel
key={i} key={i}

View File

@@ -2,7 +2,7 @@ import React, { useContext, useEffect, useState } from "react";
import auth from "../../services/auth"; import auth from "../../services/auth";
import getTimerWorker from "../../services/getTimerWorker"; import getTimerWorker from "../../services/getTimerWorker";
import { parsePayload } from "../../utils/jwt"; import { parsePayload } from "../../utils/jwt";
import { getGroups } from "../../utils/roles"; import {getGroups, getProviders} from "../../utils/roles";
const UserContext = React.createContext(); const UserContext = React.createContext();
@@ -10,6 +10,7 @@ export const UserProvider = ({ children }) => {
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
const [token, setToken] = useState(null); const [token, setToken] = useState(null);
const [groups, setGroups] = useState(null); const [groups, setGroups] = useState(null);
const [providers, setProviders] = useState(null);
const [error, setError] = useState(null); const [error, setError] = useState(null);
let timer; let timer;
@@ -73,6 +74,7 @@ export const UserProvider = ({ children }) => {
} }
setGroups(getGroups(idToken)); setGroups(getGroups(idToken));
setProviders(getProviders(idToken));
startSessionTimer(); startSessionTimer();
} catch (e) { } catch (e) {
setError(`Verify error. ${e.message}`); setError(`Verify error. ${e.message}`);
@@ -106,6 +108,7 @@ export const UserProvider = ({ children }) => {
const signOut = () => { const signOut = () => {
setGroups(null); setGroups(null);
setProviders(null);
setToken(null); setToken(null);
if (localStorage) { if (localStorage) {
localStorage.removeItem("token"); localStorage.removeItem("token");
@@ -153,6 +156,7 @@ export const UserProvider = ({ children }) => {
error, error,
fetching, fetching,
groups, groups,
providers,
token, token,
getAuthorizeURL, getAuthorizeURL,
getLogoutURL, getLogoutURL,

View File

@@ -1,9 +1,10 @@
import React from "react"; import React from "react";
import { getGroups } from "../../../utils/roles"; import {getGroups, getProviders} from "../../../utils/roles";
let token = null; let token = null;
let groups = null; let groups = null;
let providers = null;
let fetching = false; let fetching = false;
let error = null; let error = null;
let signInResp = {}; let signInResp = {};
@@ -20,6 +21,7 @@ export const useUserContext = () => ({
error, error,
fetching, fetching,
groups, groups,
providers,
token, token,
getAuthorizeURL: jest.fn(() => authorizeURL), getAuthorizeURL: jest.fn(() => authorizeURL),
getLogoutURL: jest.fn(() => logoutURL), getLogoutURL: jest.fn(() => logoutURL),
@@ -35,8 +37,10 @@ export const setToken = (val) => {
token = val; token = val;
if (!val || !val.idToken || !val.idToken.jwtToken) { if (!val || !val.idToken || !val.idToken.jwtToken) {
groups = null; groups = null;
providers = null
} else { } else {
groups = getGroups(val.idToken.jwtToken); groups = getGroups(val.idToken.jwtToken);
providers = getProviders(val.idToken.jwtToken);
} }
}; };

View File

@@ -6,14 +6,14 @@ import { render, waitFor } from "@testing-library/react";
import { CarUpdatesProvider } from "../../Contexts/CarUpdatesContext"; import { CarUpdatesProvider } from "../../Contexts/CarUpdatesContext";
import CarUpdateStatusTable from "../CarUpdateStatusTable"; import CarUpdateStatusTable from "../CarUpdateStatusTable";
import { StatusProvider } from "../../Contexts/StatusContext"; 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"; import addSnapshotSerializer from "../../../utils/snapshot";
const renderCarUpdateStatusTable = async () => { const renderCarUpdateStatusTable = async () => {
const { container } = render( const { container } = render(
<StatusProvider> <StatusProvider>
<CarUpdatesProvider> <CarUpdatesProvider>
<CarUpdateStatusTable carupdateid={283} token={TEST_AUTH_OBJECT} /> <CarUpdateStatusTable carupdateid={283} token={TEST_AUTH_OBJECT_FISKER} />
</CarUpdatesProvider> </CarUpdatesProvider>
</StatusProvider> </StatusProvider>
); );

View File

@@ -1,4 +1,3 @@
import React, { useEffect, useState } from "react";
import { import {
LinearProgress, LinearProgress,
Table, Table,
@@ -9,19 +8,22 @@ import {
TableRow, TableRow,
Tooltip, Tooltip,
} from "@material-ui/core"; } from "@material-ui/core";
import { Link } from "react-router-dom";
import CancelIcon from "@material-ui/icons/Cancel"; 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 { LocalDateTimeString } from "../../../utils/dates";
import TableHeaderSortable from "../../Table/HeaderSortable";
import { import {
CarUpdatesProvider, CarUpdatesProvider,
useCarUpdatesContext, useCarUpdatesContext,
} from "../../Contexts/CarUpdatesContext"; } from "../../Contexts/CarUpdatesContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import { useUserContext } from "../../Contexts/UserContext";
import { logger } from "../../../services/monitoring"; import TableHeaderSortable from "../../Table/HeaderSortable";
import { useLocalStorage } from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
import useStyles from "../../useStyles";
import { RoleWrap } from "../RoleWrap";
const tableColumns = [ const tableColumns = [
{ {
@@ -68,6 +70,8 @@ const MainForm = ({ vin, token }) => {
} = useCarUpdatesContext(); } = useCarUpdatesContext();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
const { groups, providers } = useUserContext();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
@@ -176,11 +180,17 @@ const MainForm = ({ vin, token }) => {
{LocalDateTimeString(row.updated)} {LocalDateTimeString(row.updated)}
</TableCell> </TableCell>
<TableCell> <TableCell>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerCreate}
>
<Tooltip key={row.vin} title={`Send cancel for ${row.vin}`}> <Tooltip key={row.vin} title={`Send cancel for ${row.vin}`}>
<Link to="#" onClick={() => sendCancel(row)}> <Link to="#" onClick={() => sendCancel(row)}>
<CancelIcon /> <CancelIcon />
</Link> </Link>
</Tooltip> </Tooltip>
</RoleWrap>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -0,0 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RoleWrap RoleWrap with access, child renders 1`] = `
<div>
<div
data-testid="test-role-wrap-child"
>
Test
</div>
</div>
`;
exports[`RoleWrap RoleWrap without access, child does not render 1`] = `<div />`;
exports[`RoleWrap RoleWrap without access, eitherComponent renders 1`] = `
<div>
<div
data-testid="test-role-wrap-child"
>
Either
</div>
</div>
`;

View File

@@ -0,0 +1,19 @@
import React from "react";
import { hasRole } from "../../../utils/roles";
export const RoleWrap = (props) => {
const {groups, rolesPerProvider, providers} = props;
const eitherComponent = props["eitherComponent"] || null;
if (!hasRole(groups, rolesPerProvider, providers)) {
return eitherComponent != null ? eitherComponent : <></>;
}
return (
<React.Fragment>
{props.children}
</React.Fragment>
)
}

View File

@@ -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(
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={rolesPerProvider}
eitherComponent={eitherComponent}
>
<div data-testid={selector}>Test</div>
</RoleWrap>
);
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,
<div data-testid={selector}>Either</div>
);
const childComponent = screen.getByTestId(selector);
expect(childComponent).toHaveTextContent("Either");
expect(view).toMatchSnapshot();
});
});

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -36,7 +36,7 @@ describe("FleetAddForm", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetAdd(); const container = await renderFleetAdd();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../../../Contexts/FleetContext"; import { FleetProvider } from "../../../../Contexts/FleetContext";
import { StatusProvider } from "../../../../Contexts/StatusContext"; import { StatusProvider } from "../../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../../utils/snapshot";
@@ -34,7 +34,7 @@ describe("FleetCANFilterAdd", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetCANFilterAdd(); const container = await renderFleetCANFilterAdd();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -221,23 +221,6 @@ exports[`FleetCANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"123-456\\""
>
<svg
aria-hidden="true"
aria-label="Delete 123-456"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -277,23 +260,6 @@ exports[`FleetCANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -333,23 +299,6 @@ exports[`FleetCANFiltersTable Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1000\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1000"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -1,5 +1,3 @@
import React, { useEffect, useState } from "react";
import { Link } from 'react-router-dom';
import { import {
Grid, Grid,
Table, Table,
@@ -12,37 +10,42 @@ import {
} from "@material-ui/core"; } from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle"; import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete"; 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 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 { logger } from "../../../../../services/monitoring";
import { Roles, hasRole } from "../../../../../utils/roles"; 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 { useLocalStorage } from "../../../../useLocalStorage";
import {trimIfMoreThan} from "../../../../../utils/strings" import useStyles from "../../../../useStyles";
const tableColumns = [ const tableColumns = [
{ {
id: "can_id", id: "can_id",
label: "CAN ID" label: "CAN ID",
}, },
{ {
id: "interval", id: "interval",
label: "Interval (ms)" label: "Interval (ms)",
}, },
{ {
id: "edge_mask", id: "edge_mask",
label: "Edge mask" label: "Edge mask",
}, },
{ {
id: "", id: "",
label: "Actions" label: "Actions",
} },
]; ];
const PAGE_SIZE = "FLEET_STATUS_CAN_FILTER_TABLE_PAGE_SIZE"; const PAGE_SIZE = "FLEET_STATUS_CAN_FILTER_TABLE_PAGE_SIZE";
@@ -54,8 +57,19 @@ const MainForm = ({ name }) => {
const [order, setOrder] = useState("desc"); const [order, setOrder] = useState("desc");
const classes = useStyles(); const classes = useStyles();
const { setMessage } = useStatusContext(); const { setMessage } = useStatusContext();
const { fleetCANFilters, totalFleetCANFilters, getFleetCANFilters, deleteFleetCANFilter } = useFleetContext(); const {
const { token: { idToken: { jwtToken: token } }, groups } = useUserContext(); fleetCANFilters,
totalFleetCANFilters,
getFleetCANFilters,
deleteFleetCANFilter,
} = useFleetContext();
const {
token: {
idToken: { jwtToken: token },
},
groups,
providers,
} = useUserContext();
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@@ -107,7 +121,7 @@ const MainForm = ({ name }) => {
const onDelete = async (can_id) => { const onDelete = async (can_id) => {
try { try {
await deleteFleetCANFilter(name, can_id, token); await deleteFleetCANFilter(name, can_id, token);
setMessage(`Deleted ${can_id}`) setMessage(`Deleted ${can_id}`);
} catch (e) { } catch (e) {
setMessage(e.message); setMessage(e.message);
logger.warn(e.stack); logger.warn(e.stack);
@@ -116,19 +130,19 @@ const MainForm = ({ name }) => {
const Actions = (row) => { const Actions = (row) => {
let actions = []; let actions = [];
if (hasRole([Roles.CREATE], groups)) { if (hasRole(groups, Permissions.FiskerCreate, providers)) {
actions.push({ actions.push({
tip: `Update "${row.can_id}"`, tip: `Update "${row.can_id}"`,
link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`, link: `/fleet/${name}/filter-update?name=${name}&can_id=${row.can_id}&interval=${row.interval}`,
icon: <EditIcon aria-label={`Update ${row.can_id}`} /> icon: <EditIcon aria-label={`Update ${row.can_id}`} />,
}); });
} }
if (hasRole([Roles.DELETE], groups)) { if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({ actions.push({
tip: `Delete "${row.can_id}"`, tip: `Delete "${row.can_id}"`,
id: row.can_id, id: row.can_id,
icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} /> icon: <DeleteIcon aria-label={`Delete ${row.can_id}`} />,
}) });
} }
if (actions.length === 0) return ["No actions"]; if (actions.length === 0) return ["No actions"];
@@ -157,7 +171,10 @@ const MainForm = ({ name }) => {
<div className={clsx(classes.paper, classes.tableSize)}> <div className={clsx(classes.paper, classes.tableSize)}>
<Grid container className={classes.root} spacing={2}> <Grid container className={classes.root} spacing={2}>
<Grid item md={4} className={classes.textJustifyAlign}> <Grid item md={4} className={classes.textJustifyAlign}>
<Link to={`/fleet/${name}/filter-add`} className={classes.labelInline}> <Link
to={`/fleet/${name}/filter-add`}
className={classes.labelInline}
>
<AddCircleIcon fontSize="large" /> <AddCircleIcon fontSize="large" />
</Link> </Link>
</Grid> </Grid>
@@ -174,11 +191,13 @@ const MainForm = ({ name }) => {
onSortRequest={handleSort} onSortRequest={handleSort}
/> />
<TableBody> <TableBody>
{fleetCANFilters.map(row => ( {fleetCANFilters.map((row) => (
<TableRow key={row.can_id}> <TableRow key={row.can_id}>
<TableCell align="center">{row.can_id}</TableCell> <TableCell align="center">{row.can_id}</TableCell>
<TableCell align="center">{row.interval}</TableCell> <TableCell align="center">{row.interval}</TableCell>
<TableCell align="center">{trimIfMoreThan(row.edge_mask, 15, "...")}</TableCell> <TableCell align="center">
{trimIfMoreThan(row.edge_mask, 15, "...")}
</TableCell>
<TableCell align="center">{Actions(row)}</TableCell> <TableCell align="center">{Actions(row)}</TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../../../Contexts/FleetContext"; import { FleetProvider } from "../../../../Contexts/FleetContext";
import { StatusProvider } from "../../../../Contexts/StatusContext"; import { StatusProvider } from "../../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../../utils/snapshot";
@@ -37,7 +37,7 @@ describe("FleetCANFiltersTable", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetCANFiltersTable(); const container = await renderFleetCANFiltersTable();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../../../Contexts/FleetContext"; import { FleetProvider } from "../../../../Contexts/FleetContext";
import { StatusProvider } from "../../../../Contexts/StatusContext"; import { StatusProvider } from "../../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../../utils/snapshot";
@@ -34,7 +34,7 @@ describe("FleetCANFilterUpdate", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetCANFilterUpdate(); const container = await renderFleetCANFilterUpdate();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { setToken } from "../../Contexts/UserContext"; 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 CANFiltersTab from "./CANFiltersTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -31,7 +31,7 @@ describe("CANFiltersTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCANFitlersTab(); const container = await renderCANFitlersTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -9,7 +9,7 @@ import routeData from "react-router";
import { FleetProvider } from "../../../Contexts/FleetContext"; import { FleetProvider } from "../../../Contexts/FleetContext";
import { StatusProvider } from "../../../Contexts/StatusContext"; import { StatusProvider } from "../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../utils/snapshot";
@@ -42,7 +42,7 @@ describe("FleetDetailsTab", () => {
}) })
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetDetailsTab(); const container = await renderFleetDetailsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -11,7 +11,7 @@ import { MemoryRouter, Route } from "react-router-dom";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./DetailsTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -41,7 +41,7 @@ describe("DetailsTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderDetailsTab(); const container = await renderDetailsTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../../../Contexts/FleetContext"; import { FleetProvider } from "../../../../Contexts/FleetContext";
import { StatusProvider } from "../../../../Contexts/StatusContext"; import { StatusProvider } from "../../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../../utils/snapshot";
@@ -36,7 +36,7 @@ describe("FleetVehicleAdd", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetVehicleAdd(); const container = await renderFleetVehicleAdd();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -153,26 +153,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345678\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345678"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -190,26 +171,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345679\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345679"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -227,26 +189,7 @@ exports[`FleetVehiclesTable Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345670\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345670"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -1,5 +1,3 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { import {
Grid, Grid,
Table, Table,
@@ -8,25 +6,27 @@ import {
TableFooter, TableFooter,
TablePagination, TablePagination,
TableRow, TableRow,
Tooltip, Tooltip
} from "@material-ui/core"; } from "@material-ui/core";
import AddCircleIcon from "@material-ui/icons/AddCircle"; import AddCircleIcon from "@material-ui/icons/AddCircle";
import DeleteIcon from "@material-ui/icons/Delete"; import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import TableHeaderSortable from "../../../../Table/HeaderSortable"; import { logger } from "../../../../../services/monitoring";
import { useUserContext } from "../../../../Contexts/UserContext"; import { hasRole, Permissions } from "../../../../../utils/roles";
import { useStatusContext } from "../../../../Contexts/StatusContext";
import { import {
FleetProvider, FleetProvider,
useFleetContext, useFleetContext
} from "../../../../Contexts/FleetContext"; } from "../../../../Contexts/FleetContext";
import useStyles from "../../../../useStyles"; import { useStatusContext } from "../../../../Contexts/StatusContext";
import { useUserContext } from "../../../../Contexts/UserContext";
import SearchField from "../../../../Controls/SearchField"; import SearchField from "../../../../Controls/SearchField";
import { logger } from "../../../../../services/monitoring";
import { Roles, hasRole } from "../../../../../utils/roles";
import { useLocalStorage } from "../../../../useLocalStorage";
import DeleteConfirmation from "../../../../DeleteConfirmation"; import DeleteConfirmation from "../../../../DeleteConfirmation";
import TableHeaderSortable from "../../../../Table/HeaderSortable";
import { useLocalStorage } from "../../../../useLocalStorage";
import useStyles from "../../../../useStyles";
const tableColumns = [ const tableColumns = [
{ {
@@ -61,6 +61,7 @@ const MainForm = ({ name }) => {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups, groups,
providers,
} = useUserContext(); } = useUserContext();
const handleSearch = (query) => { const handleSearch = (query) => {
@@ -127,7 +128,7 @@ const MainForm = ({ name }) => {
const Actions = (vin) => { const Actions = (vin) => {
let actions = []; let actions = [];
if (hasRole([Roles.DELETE], groups)) { if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({ actions.push({
tip: `Delete "${vin}"`, tip: `Delete "${vin}"`,
id: vin, id: vin,

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../../../Contexts/FleetContext"; import { FleetProvider } from "../../../../Contexts/FleetContext";
import { StatusProvider } from "../../../../Contexts/StatusContext"; import { StatusProvider } from "../../../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../../../utils/snapshot"; import addSnapshotSerializer from "../../../../../utils/snapshot";
@@ -39,7 +39,7 @@ describe("FleetVehiclesTable", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetVehiclesTable(); const container = await renderFleetVehiclesTable();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -9,7 +9,7 @@ import { render, waitFor } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
import { setToken } from "../../Contexts/UserContext"; 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 VehiclesTab from "./VehiclesTab";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -31,7 +31,7 @@ describe("VehiclesTab", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderVehiclesTab(); const container = await renderVehiclesTab();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -220,23 +220,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"123-456\\""
>
<svg
aria-hidden="true"
aria-label="Delete 123-456"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -276,23 +259,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
<tr <tr
@@ -332,23 +298,6 @@ exports[`CANFiltersTab Render 1`] = `
/> />
</svg> </svg>
</a> </a>
<a
class=""
href="/"
title="Delete \\"1000\\""
>
<svg
aria-hidden="true"
aria-label="Delete 1000"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -152,26 +152,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345678\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345678"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -189,26 +170,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345679\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345679"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
<tr <tr
@@ -226,26 +188,7 @@ exports[`VehiclesTab Render 1`] = `
<td <td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter" class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignCenter"
> >
<span> No actions
<a
class=""
href="/"
title="Delete \\"USWESTVIN12345670\\""
>
<svg
aria-hidden="true"
aria-label="Delete USWESTVIN12345670"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
/>
</svg>
</a>
<div />
</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -12,7 +12,7 @@ import routeData from "react-router";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 FleetStatus from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -45,7 +45,7 @@ describe("FleetStatus", () => {
}) })
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderCarStatus(); const container = await renderCarStatus();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -11,7 +11,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -39,7 +39,7 @@ describe("FleetTable", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetTable(); const container = await renderFleetTable();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ import { BrowserRouter } from "react-router-dom";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -36,7 +36,7 @@ describe("FleetUpdate", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderFleetUpdate(); const container = await renderFleetUpdate();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -1,17 +1,17 @@
import React from "react";
import { List } from "@material-ui/core"; 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 AssessmentIcon from "@material-ui/icons/Assessment";
import BuildIcon from "@material-ui/icons/Build"; 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 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 { useUserContext } from "../Contexts/UserContext";
import { Roles, hasRole } from "../../utils/roles";
import { ExpandableSideMenuItem, MenuItem } from "./MenuItem";
import SupersetDashboardList from "../SupersetDashboardList/SupersetDashboardList"; import SupersetDashboardList from "../SupersetDashboardList/SupersetDashboardList";
import { ExpandableSideMenuItem, MenuItem } from "./MenuItem";
const menuData = [ const menuData = [
{ {
@@ -24,65 +24,60 @@ const menuData = [
label: "Deployments", label: "Deployments",
to: "/packages", to: "/packages",
icon: <CloudDownloadIcon />, icon: <CloudDownloadIcon />,
roles: [Roles.READ, Roles.CREATE], rolesPerProvider: Permissions.FiskerMagnaRead,
}, },
{ {
label: "Vehicles", label: "Vehicles",
to: "/vehicles", to: "/vehicles",
icon: <DirectionsCarIcon />, icon: <DirectionsCarIcon />,
roles: [Roles.READ, Roles.CREATE], rolesPerProvider: Permissions.FiskerMagnaRead,
}, },
{ {
label: "Fleets", label: "Fleets",
to: "/fleets", to: "/fleets",
icon: <CommuteIcon />, icon: <CommuteIcon />,
roles: [Roles.READ, Roles.CREATE], rolesPerProvider: Permissions.FiskerRead,
}, },
{ {
label: "Datascope", label: "Datascope",
to: null, to: null,
icon: <AssessmentIcon />, icon: <AssessmentIcon />,
roles: [Roles.READ, Roles.CREATE], rolesPerProvider: Permissions.FiskerRead,
component: SupersetDashboardList component: SupersetDashboardList,
}, },
{ {
label: "Suppliers", label: "Suppliers",
to: "/suppliers", to: "/suppliers",
icon: <SettingsInputCompositeIcon />, icon: <SettingsInputCompositeIcon />,
roles: [Roles.APPROVESUPPLIERS], rolesPerProvider: Permissions.FiskerSupplierAdmin,
}, },
{ {
label: "Tools", label: "Tools",
to: "/tools/certificates/add", to: "/tools/certificates/add",
icon: <BuildIcon />, icon: <BuildIcon />,
roles: [Roles.CERTIFICATES, Roles.APPROVESUPPLIERS], rolesPerProvider: Permissions.FiskerTools,
submenus: [ submenus: [
{ {
label: "Certificate", label: "Certificate",
to: "/tools/certificates/add", to: "/tools/certificates/add",
roles: [Roles.CERTIFICATES], rolesPerProvider: Permissions.FiskerCertificate,
}, },
{ {
label: "SMS", label: "SMS",
to: "/tools/sms/send", to: "/tools/sms/send",
roles: [Roles.CREATE], rolesPerProvider: Permissions.FiskerCreate,
} },
], ],
}, },
]; ];
const SideMenu = () => { const SideMenu = () => {
const { groups } = useUserContext(); const { groups, providers } = useUserContext();
const [menu, setMenu] = useState(menuData);
const menu = menuData.reduce((result, item) => {
if (hasRole(item.roles, groups)) {
result.push(item);
}
return result;
}, []);
useEffect(() => {
filterMenu(groups, providers, setMenu);
}, [groups, providers]);
return ( return (
<List> <List>
@@ -96,4 +91,22 @@ const SideMenu = () => {
); );
}; };
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; export default SideMenu;

View File

@@ -1,12 +1,12 @@
jest.mock("../Contexts/UserContext"); 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 { 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 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 renderMenu = async () => {
const { container } = render( const { container } = render(
@@ -23,15 +23,20 @@ const renderMenu = async () => {
describe("SideMenu", () => { describe("SideMenu", () => {
beforeAll(() => { beforeAll(() => {
addSnapshotSerializer(expect); addSnapshotSerializer(expect);
});
it("Unauthenticated", async () => {
setToken({ idToken: { jwtToken: null } });
const container = await renderMenu();
expect(container).toMatchSnapshot();
}); });
it("Authenticated", async () => { it("Authenticated", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderMenu(); const container = await renderMenu();
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('Datascope')).toBeInTheDocument() expect(screen.getByText("Datascope")).toBeInTheDocument();
}) });
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
}); });

View File

@@ -213,6 +213,42 @@ exports[`SideMenu Authenticated 1`] = `
</li> </li>
</ul> </ul>
</li> </li>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/suppliers"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M5 2c0-.55-.45-1-1-1s-1 .45-1 1v4H1v6h6V6H5V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2H9v2zm-8 0c0 1.3.84 2.4 2 2.82V23h2v-4.18C6.16 18.4 7 17.3 7 16v-2H1v2zM21 6V2c0-.55-.45-1-1-1s-1 .45-1 1v4h-2v6h6V6h-2zm-8-4c0-.55-.45-1-1-1s-1 .45-1 1v4H9v6h6V6h-2V2zm4 14c0 1.3.84 2.4 2 2.82V23h2v-4.18c1.16-.41 2-1.51 2-2.82v-2h-6v2z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Suppliers
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
<span> <span>
<li> <li>
<a <a
@@ -303,3 +339,52 @@ exports[`SideMenu Authenticated 1`] = `
</div> </div>
</div> </div>
`; `;
exports[`SideMenu Unauthenticated 1`] = `
<div>
<div
data-testid="mocked-userprovider"
>
<ul
class="MuiList-root MuiList-padding"
>
<li>
<a
aria-disabled="false"
class="MuiButtonBase-root MuiListItem-root MuiListItem-gutters MuiListItem-button"
href="/home"
role="button"
tabindex="0"
>
<div
class="MuiListItemIcon-root"
>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"
/>
</svg>
</div>
<div
class="MuiListItemText-root"
>
<span
class="MuiTypography-root MuiListItemText-primary MuiTypography-body1 MuiTypography-displayBlock"
>
Home
</span>
</div>
<span
class="MuiTouchRipple-root"
/>
</a>
</li>
</ul>
</div>
</div>
`;

View File

@@ -1,32 +1,34 @@
import React, {useEffect, useState} from "react";
import {Redirect, useParams} from "react-router";
import { Button, FormControlLabel, Grid, Switch, Typography } from "@material-ui/core"; import { Button, FormControlLabel, Grid, Switch, Typography } from "@material-ui/core";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { Redirect, useParams } from "react-router";
import {ManifestsProvider, useManifestsContext,} from "../../Contexts/ManifestsContext";
import {CarUpdatesProvider, useCarUpdatesContext,} from "../../Contexts/CarUpdatesContext";
import {VehicleProvider} from "../../Contexts/VehicleContext";
import {useUserContext} from "../../Contexts/UserContext";
import {useStatusContext} from "../../Contexts/StatusContext";
import useStyles from "../../useStyles";
import SearchField from "../../Controls/SearchField";
import CarSelectionTable from "../../Controls/CarSelectionTable";
import { logger } from "../../../services/monitoring"; import { logger } from "../../../services/monitoring";
import { LocalDateTimeString } from "../../../utils/dates"; import { LocalDateTimeString } from "../../../utils/dates";
import FleetSelectionTable from "../../Controls/FleetSelectionTable"; import { Permissions } from "../../../utils/roles";
import { CarUpdatesProvider, useCarUpdatesContext } from "../../Contexts/CarUpdatesContext";
import { FleetProvider } from "../../Contexts/FleetContext"; import { FleetProvider } from "../../Contexts/FleetContext";
import { ManifestsProvider, useManifestsContext } from "../../Contexts/ManifestsContext";
import { useStatusContext } from "../../Contexts/StatusContext";
import { useUserContext } from "../../Contexts/UserContext";
import { VehicleProvider } from "../../Contexts/VehicleContext";
import CarSelectionTable from "../../Controls/CarSelectionTable";
import FleetSelectionTable from "../../Controls/FleetSelectionTable";
import { RoleWrap } from "../../Controls/RoleWrap";
import SearchField from "../../Controls/SearchField";
import useStyles from "../../useStyles";
const CAR_UPDATE = false; const CAR_UPDATE = false;
const FLEET_UPDATE = true; const FLEET_UPDATE = true;
const MainForm = () => { const MainForm = () => {
const [updateType, setUpdateType] = useState(CAR_UPDATE); const [updateType, setUpdateType] = useState(CAR_UPDATE);
const {manifest_id} = useParams(); const {manifest_id} = useParams();
const {getManifests, manifests, busy} = useManifestsContext(); const {getManifests, manifests, busy} = useManifestsContext();
const {deployCarUpdates, deployFleetUpdates} = useCarUpdatesContext(); const {deployCarUpdates, deployFleetUpdates} = useCarUpdatesContext();
const { const {
groups,
providers,
token: { token: {
idToken: {jwtToken: token}, idToken: {jwtToken: token},
}, },
@@ -145,12 +147,17 @@ const MainForm = () => {
>{`${selected.length} Selected`}</div> >{`${selected.length} Selected`}</div>
</Grid> </Grid>
<Grid item md={2} className={classes.textCenterAlign}> <Grid item md={2} className={classes.textCenterAlign}>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerRead}
>
<FormControlLabel control={<Switch <FormControlLabel control={<Switch
checked={updateType === CAR_UPDATE} checked={updateType === CAR_UPDATE}
onChange={handleChange} onChange={handleChange}
inputProps={{'aria-label': 'controlled'}} inputProps={{'aria-label': 'controlled'}}
/>} label="Car(default) or Fleet"/> />} label="Car(default) or Fleet"/>
</RoleWrap>
</Grid> </Grid>
<Grid item md={4} className={classes.textCenterAlign}> <Grid item md={4} className={classes.textCenterAlign}>
<SearchField classes={classes} onSearch={handleSearch}/> <SearchField classes={classes} onSearch={handleSearch}/>

View File

@@ -9,7 +9,7 @@ import { ManifestsProvider } from "../../Contexts/ManifestsContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; import { UserProvider, setToken } from "../../Contexts/UserContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import ManifestDetails from "."; import ManifestDetails from ".";
import { TEST_AUTH_OBJECT } from "../../../utils/testing"; import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
const TestComponent = (opened) => ( const TestComponent = (opened) => (
@@ -26,7 +26,7 @@ const TestComponent = (opened) => (
describe("Manifest Details Component", () => { describe("Manifest Details Component", () => {
beforeAll(() => { beforeAll(() => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
addSnapshotSerializer(expect); addSnapshotSerializer(expect);
}); });

View File

@@ -1,5 +1,3 @@
import React, { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import { import {
Grid, Grid,
Table, Table,
@@ -10,28 +8,30 @@ import {
TableRow, TableRow,
Tooltip, Tooltip,
} from "@material-ui/core"; } from "@material-ui/core";
import DeleteIcon from "@material-ui/icons/Delete";
import SendIcon from "@material-ui/icons/Send"; import SendIcon from "@material-ui/icons/Send";
import VisibilityIcon from "@material-ui/icons/Visibility"; import VisibilityIcon from "@material-ui/icons/Visibility";
import DeleteIcon from "@material-ui/icons/Delete";
import clsx from "clsx"; 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 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 = [ const tableColumns = [
{ {
@@ -71,7 +71,7 @@ const formatManifestType = (type) => {
default: default:
return "Standard"; return "Standard";
} }
} };
const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE"; const PAGE_SIZE = "MANIFEST_LIST_PAGE_SIZE";
@@ -95,6 +95,7 @@ const MainForm = () => {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups, groups,
providers,
} = useUserContext(); } = useUserContext();
const sortHandler = (event, property) => { const sortHandler = (event, property) => {
@@ -168,29 +169,30 @@ const MainForm = () => {
const Actions = (row) => { const Actions = (row) => {
let actions = []; let actions = [];
if (hasRole([Roles.CREATE, Roles.READ], groups)) { if (hasRole(groups, Permissions.FiskerMagnaRead, providers)) {
actions.push({ actions.push({
tip: `Status "${row.name} ${row.version}"`, tip: `Status "${row.name} ${row.version}"`,
link: `/package-status/${row.id}`, link: `/package-status/${row.id}`,
icon: ( icon: (
<VisibilityIcon aria-label={`Status ${row.name} ${row.version}`} /> <VisibilityIcon aria-label={`Status ${row.name} ${row.version}`} />
), ),
}, {
tip: `Update "${row.name} ${row.version}"`,
link: `/package-update/${row.id}`,
icon: (
<EditIcon aria-label={`Update ${row.name} ${row.version}`} />
),
}); });
} }
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: <EditIcon aria-label={`Update ${row.name} ${row.version}`} />,
});
}
if (hasRole(groups, Permissions.FiskerMagnaCreate, providers)) {
actions.push({ actions.push({
tip: `Deploy "${row.name} ${row.version}"`, tip: `Deploy "${row.name} ${row.version}"`,
link: `/package-deploy/${row.id}`, link: `/package-deploy/${row.id}`,
icon: <SendIcon aria-label={`Deploy ${row.name} ${row.version}`} />, icon: <SendIcon aria-label={`Deploy ${row.name} ${row.version}`} />,
}); });
} }
if (hasRole([Roles.DELETE], groups)) { if (hasRole(groups, Permissions.FiskerDelete, providers)) {
actions.push({ actions.push({
tip: `Delete "${row.name} ${row.version}"`, tip: `Delete "${row.name} ${row.version}"`,
id: row.id, id: row.id,
@@ -257,7 +259,9 @@ const MainForm = () => {
)} )}
</TableCell> </TableCell>
<TableCell align="center">{row.version}</TableCell> <TableCell align="center">{row.version}</TableCell>
<TableCell align="center">{formatManifestType(row.type)}</TableCell> <TableCell align="center">
{formatManifestType(row.type)}
</TableCell>
<TableCell align="center"> <TableCell align="center">
{LocalDateTimeString(row.created)} {LocalDateTimeString(row.created)}
</TableCell> </TableCell>

View File

@@ -1,6 +1,3 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router";
import { Link } from "react-router-dom";
import { import {
LinearProgress, LinearProgress,
Table, Table,
@@ -14,22 +11,27 @@ import {
} from "@material-ui/core"; } from "@material-ui/core";
import CancelIcon from "@material-ui/icons/Cancel"; import CancelIcon from "@material-ui/icons/Cancel";
import clsx from "clsx"; import clsx from "clsx";
import React, { useEffect, useState } from "react";
import { useParams } from "react-router";
import { Link } from "react-router-dom";
import { import { logger } from "../../../services/monitoring";
ManifestsProvider, import { LocalDateTimeString } from "../../../utils/dates";
useManifestsContext, import { Permissions } from "../../../utils/roles";
} from "../../Contexts/ManifestsContext";
import { import {
CarUpdatesProvider, CarUpdatesProvider,
useCarUpdatesContext, useCarUpdatesContext,
} from "../../Contexts/CarUpdatesContext"; } from "../../Contexts/CarUpdatesContext";
import { useUserContext } from "../../Contexts/UserContext"; import {
ManifestsProvider,
useManifestsContext,
} from "../../Contexts/ManifestsContext";
import { useStatusContext } from "../../Contexts/StatusContext"; import { useStatusContext } from "../../Contexts/StatusContext";
import useStyles from "../../useStyles"; import { useUserContext } from "../../Contexts/UserContext";
import { LocalDateTimeString } from "../../../utils/dates"; import { RoleWrap } from "../../Controls/RoleWrap";
import { logger } from "../../../services/monitoring";
import ManifestDetails from "../Details";
import { useLocalStorage } from "../../useLocalStorage"; import { useLocalStorage } from "../../useLocalStorage";
import useStyles from "../../useStyles";
import ManifestDetails from "../Details";
const PAGE_SIZE = "MANIFEST_STATUS_PAGE_SIZE"; const PAGE_SIZE = "MANIFEST_STATUS_PAGE_SIZE";
@@ -52,6 +54,8 @@ const MainForm = () => {
token: { token: {
idToken: { jwtToken: token }, idToken: { jwtToken: token },
}, },
groups,
providers,
} = useUserContext(); } = useUserContext();
useEffect(() => { useEffect(() => {
@@ -172,11 +176,18 @@ const MainForm = () => {
{LocalDateTimeString(row.updated)} {LocalDateTimeString(row.updated)}
</TableCell> </TableCell>
<TableCell> <TableCell>
<RoleWrap
groups={groups}
providers={providers}
rolesPerProvider={Permissions.FiskerCreate}
eitherComponent={<>No action</>}
>
<Tooltip key={row.vin} title={`Send cancel for ${row.vin}`}> <Tooltip key={row.vin} title={`Send cancel for ${row.vin}`}>
<Link to="#" onClick={() => sendCancel(row)}> <Link to="#" onClick={() => sendCancel(row)}>
<CancelIcon /> <CancelIcon />
</Link> </Link>
</Tooltip> </Tooltip>
</RoleWrap>
</TableCell> </TableCell>
</TableRow> </TableRow>
))} ))}

View File

@@ -49,9 +49,7 @@ const MainForm = () => {
const onSubmit = async (e) => { const onSubmit = async (e) => {
e.preventDefault(); e.preventDefault();
try { try {
const data = {name, type}; const result = await updateManifest(manifest_id, {name, type}, token);
console.log(data);
const result = await updateManifest(manifest_id, data, token);
if (!result || result.error) return; if (!result || result.error) return;
setMessage(`Updated manifest ${manifest_id}`); setMessage(`Updated manifest ${manifest_id}`);

View File

@@ -9,7 +9,7 @@ import { ManifestsProvider } from "../../Contexts/ManifestsContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; import { UserProvider, setToken } from "../../Contexts/UserContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import ManifestUpdate from "."; import ManifestUpdate from ".";
import { TEST_AUTH_OBJECT } from "../../../utils/testing"; import { TEST_AUTH_OBJECT_FISKER } from "../../../utils/testing";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
const renderManifestUpdate = async () => { const renderManifestUpdate = async () => {
@@ -33,12 +33,12 @@ const renderManifestUpdate = async () => {
describe("Manifest Details Component", () => { describe("Manifest Details Component", () => {
beforeAll(() => { beforeAll(() => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
addSnapshotSerializer(expect); addSnapshotSerializer(expect);
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const view = await renderManifestUpdate(); const view = await renderManifestUpdate();
expect(view).toMatchSnapshot(); expect(view).toMatchSnapshot();
}); });

View File

@@ -8,7 +8,7 @@ export const TYPES = {
PROTECTED: 2, PROTECTED: 2,
}; };
export const AuthRoute = ({ token, type, roles, groups, ...others }) => { export const AuthRoute = ({ token, type, groups, rolesPerGroup, providers, ...others }) => {
if (type === TYPES.PROTECTED && !token) { if (type === TYPES.PROTECTED && !token) {
return <Redirect to="/" />; return <Redirect to="/" />;
} else if (type === TYPES.GUEST && token) { } else if (type === TYPES.GUEST && token) {
@@ -16,8 +16,9 @@ export const AuthRoute = ({ token, type, roles, groups, ...others }) => {
} else if ( } else if (
type === TYPES.PROTECTED && type === TYPES.PROTECTED &&
token && token &&
roles && rolesPerGroup &&
!hasRole(roles, groups) providers &&
!hasRole(groups, rolesPerGroup, providers)
) { ) {
return <Redirect to="/home" />; return <Redirect to="/home" />;
} }

View File

@@ -1,10 +1,10 @@
import React, { Suspense } from "react"; import React, { Suspense } from "react";
import { Switch } from "react-router-dom"; import { Switch } from "react-router-dom";
import { AuthRoute, TYPES } from "../Routes/AuthRoute"; import { Permissions } from "../../utils/roles";
import { MessageBar } from "../MessageBar";
import { useUserContext } from "../Contexts/UserContext"; 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 CANFilterCreate = React.lazy(() => import("../CANFilter/Add"));
const CANFilterUpdate = React.lazy(() => import("../CANFilter/Update")); 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 SupplierDetails = React.lazy(() => import("../Suppliers/Details"));
const Datascope = React.lazy(() => import("../Dashboard")); const Datascope = React.lazy(() => import("../Dashboard"));
const SiteRoutes = () => { const SiteRoutes = () => {
const { token, groups } = useUserContext(); const { token, groups, providers } = useUserContext();
return ( return (
<Suspense fallback={"Loading..."}> <Suspense fallback={"Loading..."}>
<MessageBar /> <MessageBar />
@@ -57,7 +57,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/filter-update" path="/filter-update"
@@ -65,7 +66,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleets" path="/fleets"
@@ -73,7 +75,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet/:name/vehicle-add" path="/fleet/:name/vehicle-add"
@@ -81,7 +84,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet/:name/filter-add" path="/fleet/:name/filter-add"
@@ -89,7 +93,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet/:name/filter-update" path="/fleet/:name/filter-update"
@@ -97,7 +102,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet/:name" path="/fleet/:name"
@@ -105,7 +111,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet-add" path="/fleet-add"
@@ -113,7 +120,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/fleet-update" path="/fleet-update"
@@ -121,7 +129,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/home" path="/home"
@@ -135,7 +144,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/package-deploy/:manifest_id" path="/package-deploy/:manifest_id"
@@ -143,7 +153,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/package-status/:manifest_id" path="/package-status/:manifest_id"
@@ -151,7 +162,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/package-update/:manifest_id" path="/package-update/:manifest_id"
@@ -159,7 +171,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/vehicles" path="/vehicles"
@@ -167,7 +180,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/vehicle-add" path="/vehicle-add"
@@ -175,7 +189,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/vehicle-status/:vin/:carupdateid" path="/vehicle-status/:vin/:carupdateid"
@@ -183,7 +198,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/vehicle-status/:vin" path="/vehicle-status/:vin"
@@ -191,7 +207,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerMagnaRead}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/vehicle-update" path="/vehicle-update"
@@ -199,7 +216,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/tools/certificates/add" path="/tools/certificates/add"
@@ -207,7 +225,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CERTIFICATES]} rolesPerGroup={Permissions.FiskerMagnaCertificate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/tools/sms/send" path="/tools/sms/send"
@@ -215,7 +234,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.CREATE]} rolesPerGroup={Permissions.FiskerCreate}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/suppliers" path="/suppliers"
@@ -223,7 +243,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.APPROVESUPPLIERS]} rolesPerGroup={Permissions.FiskerSupplierAdmin}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/supplier/:email" path="/supplier/:email"
@@ -231,7 +252,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.APPROVESUPPLIERS]} rolesPerGroup={Permissions.FiskerSupplierAdmin}
providers={providers}
/> />
<AuthRoute <AuthRoute
path="/datascope" path="/datascope"
@@ -239,7 +261,8 @@ const SiteRoutes = () => {
type={TYPES.PROTECTED} type={TYPES.PROTECTED}
token={token} token={token}
groups={groups} groups={groups}
roles={[Roles.READ, Roles.CREATE]} rolesPerGroup={Permissions.FiskerRead}
providers={providers}
/> />
<PageNotFound /> <PageNotFound />
</Switch> </Switch>

View File

@@ -15,7 +15,7 @@ import { BrowserRouter } from "react-router-dom";
import { SupplierProvider } from "../../Contexts/SupplierContext"; import { SupplierProvider } from "../../Contexts/SupplierContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -43,7 +43,7 @@ describe("Supplier page", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderSupplierDetailsPage(); const container = await renderSupplierDetailsPage();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -10,7 +10,7 @@ import { BrowserRouter } from "react-router-dom";
import { SupplierProvider } from "../../Contexts/SupplierContext"; import { SupplierProvider } from "../../Contexts/SupplierContext";
import { StatusProvider } from "../../Contexts/StatusContext"; import { StatusProvider } from "../../Contexts/StatusContext";
import { UserProvider, setToken } from "../../Contexts/UserContext"; 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 MainForm from "./index";
import addSnapshotSerializer from "../../../utils/snapshot"; import addSnapshotSerializer from "../../../utils/snapshot";
@@ -38,7 +38,7 @@ describe("Suppliers page", () => {
}); });
it("Render", async () => { it("Render", async () => {
setToken(TEST_AUTH_OBJECT); setToken(TEST_AUTH_OBJECT_FISKER);
const container = await renderSuppliersPage(); const container = await renderSuppliersPage();
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });

View File

@@ -6,16 +6,12 @@ export const Roles = {
DELETE: "8f78dce7-f5f9-4033-a10c-c9c7408bfcfe", DELETE: "8f78dce7-f5f9-4033-a10c-c9c7408bfcfe",
CERTIFICATES: "746f34b0-9ba0-4b5d-8d84-0256a9c8e390", CERTIFICATES: "746f34b0-9ba0-4b5d-8d84-0256a9c8e390",
APPROVESUPPLIERS: "a6c9805e-80b2-42b2-bfbb-9df52e5504d8", APPROVESUPPLIERS: "a6c9805e-80b2-42b2-bfbb-9df52e5504d8",
MAGNAGROUP: process.env.REACT_APP_MAGNA_GROUP_ID,
}; };
export const hasRoleToken = (roles, token) => { export const Providers = {
if (!roles || roles.length === 0) return true; FISKER: "Fisker",
MAGNA: process.env.REACT_APP_MAGNA_PROVIDER,
const groups = getGroups(token);
if (!groups) return false;
return hasRole(roles, groups);
}; };
export const getGroups = (token) => { export const getGroups = (token) => {
@@ -26,13 +22,58 @@ export const getGroups = (token) => {
return payload["custom:groups"]; return payload["custom:groups"];
}; };
export const hasRole = (roles, groups) => { export const getProviders = (token) => {
if (!roles || roles.length === 0) return true; const payload = parsePayload(token);
if (!groups) return false;
for (let role of roles) { if (!payload || !payload["identities"] || payload["identities"].length === 0)
if (groups.indexOf(role) > -1) return true; 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; 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] },
FiskerMagnaCertificate: {
[Providers.FISKER]: [Roles.CERTIFICATES],
[Providers.MAGNA]: [Roles.MAGNAGROUP],
},
FiskerTools: {
[Providers.FISKER]: [Roles.APPROVESUPPLIERS, Roles.CERTIFICATES],
[Providers.MAGNA]: [Roles.MAGNAGROUP],
},
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],
},
};

View File

@@ -1,10 +1,28 @@
import {
import { hasRoleToken, Roles } from "./roles"; getGroups,
import { TEST_TOKEN } from "./testing"; getProviders,
hasRole,
Permissions,
Providers,
Roles,
} from "./roles";
import { TEST_EXPECTED_GROUPS, TEST_TOKEN } from "./testing";
describe("Roles Helper", () => { describe("Roles Helper", () => {
it("Check roles", () => { it("Check roles", () => {
expect(hasRoleToken([Roles.CREATE], TEST_TOKEN)).toEqual(true); expect(
expect(hasRoleToken([Roles.READ], TEST_TOKEN)).toEqual(false); 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"]);
});
}); });

View File

@@ -5,5 +5,13 @@ export const TEST_AUTH_OBJECT = {
jwtToken: TEST_TOKEN, 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 = 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]"; "[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]";