Implement React Query (Tanner) (#1263)

* Implement React Query (Tanner)

Also, replace PermissionCache with the framework's hooks.

* Update App.tsx

See if tools are causing cypress error

* Push div above query elements

Might solve a cypress problem.

* Remove old PermissionCache, and add docs/comments
This commit is contained in:
Tim Consolazio 2024-03-25 14:47:02 -04:00 committed by GitHub
parent b5b8031b44
commit 917ee504cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 461 additions and 229 deletions

View File

@ -25,6 +25,8 @@
"@rjsf/mui": "5.0.0-beta.20",
"@rjsf/utils": "5.0.0-beta.20",
"@rjsf/validator-ajv8": "5.0.0-beta.20",
"@tanstack/react-query": "^5.28.6",
"@tanstack/react-query-devtools": "^5.28.6",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
@ -67,6 +69,7 @@
},
"devDependencies": {
"@cypress/grep": "^3.1.0",
"@tanstack/eslint-plugin-query": "^5.28.6",
"@types/carbon__colors": "^10.31.3",
"@types/cookie": "^0.5.1",
"@types/lodash.merge": "^4.6.7",
@ -5358,6 +5361,228 @@
"node": ">=10"
}
},
"node_modules/@tanstack/eslint-plugin-query": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.28.6.tgz",
"integrity": "sha512-kIvdN/EvbOrk4bbXOBm/Ik+uhQl5hawikkF5dLLmlvK3aZJzwRaRGpezYDM5Xw/6GCsATy+woh+Wvzj//BRvsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^6.20.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"eslint": "^8.0.0"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/utils": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
"integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"semver": "^7.5.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tanstack/eslint-plugin-query/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/@tanstack/query-core": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.6.tgz",
"integrity": "sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/query-devtools": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.28.6.tgz",
"integrity": "sha512-DXJGqbrsteWU9XehDf6s3k3QxwQqGUlNXpitsF1xbwkYBcDaAakiC6hjJSMfPBHOrbZCnWfAGCVf4vh2D75/xw==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.6.tgz",
"integrity": "sha512-/DdYuDBSsA21Qbcder1R8Cr/3Nx0ZnA2lgtqKsLMvov8wL4+g0HBz/gWYZPlIsof7iyfQafyhg4wUVUsS3vWZw==",
"dependencies": {
"@tanstack/query-core": "5.28.6"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18.0.0"
}
},
"node_modules/@tanstack/react-query-devtools": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.6.tgz",
"integrity": "sha512-xSfskHlM2JkP7WpN89UqhJV2RbFxg8YnOMzQz+EEzWSsgxMI5Crce8HO9pcUAcJce8gSmw93RQwuKNdG3FbT6w==",
"dependencies": {
"@tanstack/query-devtools": "5.28.6"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"@tanstack/react-query": "^5.28.6",
"react": "^18.0.0"
}
},
"node_modules/@testing-library/dom": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz",
@ -5905,9 +6130,9 @@
}
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
@ -6061,9 +6286,9 @@
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
},
"node_modules/@types/serve-index": {
"version": "1.9.1",
@ -13256,9 +13481,9 @@
}
},
"node_modules/eslint-visitor-keys": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
"integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -31052,6 +31277,18 @@
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@ -37820,6 +38057,142 @@
"defer-to-connect": "^2.0.0"
}
},
"@tanstack/eslint-plugin-query": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.28.6.tgz",
"integrity": "sha512-kIvdN/EvbOrk4bbXOBm/Ik+uhQl5hawikkF5dLLmlvK3aZJzwRaRGpezYDM5Xw/6GCsATy+woh+Wvzj//BRvsg==",
"dev": true,
"requires": {
"@typescript-eslint/utils": "^6.20.0"
},
"dependencies": {
"@typescript-eslint/scope-manager": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz",
"integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0"
}
},
"@typescript-eslint/types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz",
"integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz",
"integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/visitor-keys": "6.21.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "9.0.3",
"semver": "^7.5.4",
"ts-api-utils": "^1.0.1"
}
},
"@typescript-eslint/utils": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz",
"integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.12",
"@types/semver": "^7.5.0",
"@typescript-eslint/scope-manager": "6.21.0",
"@typescript-eslint/types": "6.21.0",
"@typescript-eslint/typescript-estree": "6.21.0",
"semver": "^7.5.4"
}
},
"@typescript-eslint/visitor-keys": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz",
"integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==",
"dev": true,
"requires": {
"@typescript-eslint/types": "6.21.0",
"eslint-visitor-keys": "^3.4.1"
}
},
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
},
"semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"@tanstack/query-core": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.28.6.tgz",
"integrity": "sha512-hnhotV+DnQtvtR3jPvbQMPNMW4KEK0J4k7c609zJ8muiNknm+yoDyMHmxTWM5ZnlZpsz0zOxYFr+mzRJNHWJsA=="
},
"@tanstack/query-devtools": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.28.6.tgz",
"integrity": "sha512-DXJGqbrsteWU9XehDf6s3k3QxwQqGUlNXpitsF1xbwkYBcDaAakiC6hjJSMfPBHOrbZCnWfAGCVf4vh2D75/xw=="
},
"@tanstack/react-query": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.28.6.tgz",
"integrity": "sha512-/DdYuDBSsA21Qbcder1R8Cr/3Nx0ZnA2lgtqKsLMvov8wL4+g0HBz/gWYZPlIsof7iyfQafyhg4wUVUsS3vWZw==",
"requires": {
"@tanstack/query-core": "5.28.6"
}
},
"@tanstack/react-query-devtools": {
"version": "5.28.6",
"resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.28.6.tgz",
"integrity": "sha512-xSfskHlM2JkP7WpN89UqhJV2RbFxg8YnOMzQz+EEzWSsgxMI5Crce8HO9pcUAcJce8gSmw93RQwuKNdG3FbT6w==",
"requires": {
"@tanstack/query-devtools": "5.28.6"
}
},
"@testing-library/dom": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-9.2.0.tgz",
@ -38264,9 +38637,9 @@
}
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ=="
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
},
"@types/json5": {
"version": "0.0.29",
@ -38420,9 +38793,9 @@
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
"@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="
},
"@types/serve-index": {
"version": "1.9.1",
@ -44116,9 +44489,9 @@
}
},
"eslint-visitor-keys": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
"integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ=="
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="
},
"eslint-webpack-plugin": {
"version": "3.2.0",
@ -57217,6 +57590,13 @@
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
"integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA=="
},
"ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"requires": {}
},
"ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",

View File

@ -20,6 +20,8 @@
"@rjsf/mui": "5.0.0-beta.20",
"@rjsf/utils": "5.0.0-beta.20",
"@rjsf/validator-ajv8": "5.0.0-beta.20",
"@tanstack/react-query": "^5.28.6",
"@tanstack/react-query-devtools": "^5.28.6",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^14.5.2",
@ -96,6 +98,7 @@
},
"devDependencies": {
"@cypress/grep": "^3.1.0",
"@tanstack/eslint-plugin-query": "^5.28.6",
"@types/carbon__colors": "^10.31.3",
"@types/cookie": "^0.5.1",
"@types/lodash.merge": "^4.6.7",

View File

@ -1,12 +1,15 @@
import { defineAbility } from '@casl/ability';
import React from 'react';
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AbilityContext } from './contexts/Can';
import APIErrorProvider from './contexts/APIErrorContext';
import ContainerForExtensions from './ContainerForExtensions';
import PublicRoutes from './routes/PublicRoutes';
const queryClient = new QueryClient();
export default function App() {
const ability = defineAbility(() => {});
const routeComponents = () => {
@ -19,14 +22,22 @@ export default function App() {
];
};
/**
* Note that QueryClientProvider and ReactQueryDevTools
* are React Qery, now branded under the Tanstack packages.
* https://tanstack.com/query/latest
*/
const layout = () => {
return (
<div className="cds--white">
<APIErrorProvider>
<AbilityContext.Provider value={ability}>
<Outlet />
</AbilityContext.Provider>
</APIErrorProvider>
<QueryClientProvider client={queryClient}>
<APIErrorProvider>
<AbilityContext.Provider value={ability}>
<Outlet />
<ReactQueryDevtools initialIsOpen={false} />
</AbilityContext.Provider>
</APIErrorProvider>
</QueryClientProvider>
</div>
);
};

View File

@ -1,28 +1,11 @@
// We may need to update usage of Ability when we update.
// They say they are going to rename PureAbility to Ability and remove the old class.
import { AbilityBuilder, Ability } from '@casl/ability';
import { useContext, useEffect, useState } from 'react';
import { useContext, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { AbilityContext } from '../contexts/Can';
import { PermissionCheckResponseBody, PermissionsToCheck } from '../interfaces';
import HttpService from '../services/HttpService';
import {
findPermissionsInCache,
updatePermissionsCache,
} from '../services/PermissionCacheService';
export const checkPermissions = (
permissionsToCheck: PermissionsToCheck,
successCallback: Function
) => {
if (Object.keys(permissionsToCheck).length !== 0) {
HttpService.makeCallToBackend({
path: `/permissions-check`,
httpMethod: 'POST',
successCallback,
postBody: { requests_to_check: permissionsToCheck },
});
}
};
export const usePermissionFetcher = (
permissionsToCheck: PermissionsToCheck
@ -30,46 +13,53 @@ export const usePermissionFetcher = (
const ability = useContext(AbilityContext);
const [permissionsLoaded, setPermissionsLoaded] = useState<boolean>(false);
useEffect(() => {
const processPermissionResult = (result: PermissionCheckResponseBody) => {
const oldRules = ability.rules;
const { can, cannot, rules } = new AbilityBuilder(Ability);
Object.keys(result.results).forEach((url: string) => {
const permissionVerbResults = result.results[url];
Object.keys(permissionVerbResults).forEach((permissionVerb: string) => {
const hasPermission = permissionVerbResults[permissionVerb];
if (hasPermission) {
can(permissionVerb, url);
} else {
cannot(permissionVerb, url);
}
});
});
oldRules.forEach((oldRule: any) => {
if (oldRule.inverted) {
cannot(oldRule.action, oldRule.subject);
const processPermissionResult = (result: PermissionCheckResponseBody) => {
const oldRules = ability.rules;
const { can, cannot, rules } = new AbilityBuilder(Ability);
Object.keys(result.results).forEach((url: string) => {
const permissionVerbResults = result.results[url];
Object.keys(permissionVerbResults).forEach((permissionVerb: string) => {
const hasPermission = permissionVerbResults[permissionVerb];
if (hasPermission) {
can(permissionVerb, url);
} else {
can(oldRule.action, oldRule.subject);
cannot(permissionVerb, url);
}
});
ability.update(rules);
});
oldRules.forEach((oldRule: any) => {
if (oldRule.inverted) {
cannot(oldRule.action, oldRule.subject);
} else {
can(oldRule.action, oldRule.subject);
}
});
ability.update(rules);
// Update the cache with the new permissions
updatePermissionsCache(result);
setPermissionsLoaded(true);
};
setPermissionsLoaded(true);
};
/**
* Are the incoming permission requests all in the cache?
* If not, make the backend call, update the cache, and process the results.
* Otherwise, use the cached results.
*/
const foundResults = findPermissionsInCache(permissionsToCheck);
if (foundResults) {
processPermissionResult(foundResults);
} else {
checkPermissions(permissionsToCheck, processPermissionResult);
const checkPermissions = async () => {
if (Object.keys(permissionsToCheck).length !== 0) {
HttpService.makeCallToBackend({
path: `/permissions-check`,
httpMethod: 'POST',
successCallback: processPermissionResult,
postBody: { requests_to_check: permissionsToCheck },
});
}
/** Query functions used by TanStack Query (React Query)
* must always return data, but we don't need to use it
*/
return true;
};
/** TanStack (React Query) trigger to do it's SWR state/cache thing */
useQuery({
queryKey: ['permissions-check', permissionsToCheck || {}],
queryFn: () => checkPermissions(),
});
return { ability, permissionsLoaded };

View File

@ -1,56 +0,0 @@
import {
updatePermissionsCache,
inspectPermissionsCache,
clearPermissionsCache,
} from './PermissionCacheService';
describe('updatePermissionsCache', () => {
it('should update the permission cache with the provided permissions', () => {
const permissionsResponse = {
results: {
'/path1': { GET: true },
'/path2': { POST: true },
},
};
updatePermissionsCache(permissionsResponse);
// Note the cache stores the perms in an array
expect(inspectPermissionsCache().get('/path1')).toEqual([{ GET: true }]);
expect(inspectPermissionsCache().get('/path2')).toEqual([{ POST: true }]);
});
it('should update the permissions cache with the new permissions', () => {
// Add more permissions to a given path
const permissionsResponse = {
results: {
'/path1': { POST: true },
'/path2': { GET: true },
},
};
updatePermissionsCache(permissionsResponse);
// Each path should now have the new permissions added to the existing ones
expect(inspectPermissionsCache().get('/path1')).toEqual([
{ GET: true },
{ POST: true },
]);
expect(inspectPermissionsCache().get('/path2')).toEqual([
{ POST: true },
{ GET: true },
]);
});
it('cache should be unchanged if results are empty', () => {
const permissionsResponse = { results: {} };
updatePermissionsCache(permissionsResponse);
expect(inspectPermissionsCache().get('/path1')).toEqual([
{ GET: true },
{ POST: true },
]);
expect(inspectPermissionsCache().get('/path2')).toEqual([
{ POST: true },
{ GET: true },
]);
expect(inspectPermissionsCache().size).toEqual(2);
});
it('should clear the permissions cache when told to', () => {
clearPermissionsCache();
expect(inspectPermissionsCache().size).toEqual(0);
});
});

View File

@ -1,91 +0,0 @@
/**
* There can be a lot of redundant requests for permissions (probably deps/contexts firing etc.)
* This service provides a cache to check for already-processed perms.
*/
import {
PermissionCheckResponseBody,
PermissionVerbResults,
PermissionsToCheck,
} from '../interfaces';
/** Map makes sense: no prototype to hack, high perf, easily wiped. */
const permissionsCache = new Map<string, Array<PermissionVerbResults>>();
const updatePermissionsCache = (
permissionsResponse: PermissionCheckResponseBody
) => {
if (Object.entries(permissionsResponse.results).length > 0) {
Object.entries(permissionsResponse.results).forEach(
([path, permissions]) => {
if (permissionsCache.has(path)) {
const cachedPermissions = permissionsCache.get(path);
// Make sure we don't add duplicate PermissionVerb objects
if (
cachedPermissions &&
!cachedPermissions.some(
(p) => JSON.stringify(p) === JSON.stringify(permissions)
)
) {
cachedPermissions.push(permissions);
}
} else {
permissionsCache.set(path, [permissions]);
}
}
);
}
};
/**
* Look for the permissions in the cache. If satisfied, we don't need to make a backend call.
* Then, create a response object and return it so we can complete any callbacks.
*/
const findPermissionsInCache = (
permissionsToCheck: PermissionsToCheck
): PermissionCheckResponseBody | null => {
const results: Record<string, PermissionVerbResults> = {};
if (permissionsToCheck) {
Object.entries(permissionsToCheck).forEach(([path, verbs]) => {
const cachedPermissions = permissionsCache.get(path);
if (cachedPermissions) {
const found = cachedPermissions.find((p) => {
// Note that the project config doesn't seem to support "hasOwn"
return Object.prototype.hasOwnProperty.call(p, verbs[0]);
});
if (found) {
results[path] = found;
}
}
});
}
/**
* Results must have content, and must be the same number of permissions as we're checking
* TODO: This implementation can lead to redundant permissions requests if
* the same individual permission is requested by different permission sets
* (any perm in a set not found will trigger a backend call for the whole set).
* This is erring on the side of caution for now, but a more robust individual
* checker might be useful.
*/
return Object.keys(results).length > 0 &&
Object.keys(results).length === Object.keys(permissionsToCheck).length
? { results: results as any }
: null;
};
// Don't allow retrieval or manipulation of the cache directly
const inspectPermissionsCache = () => {
return new Map(permissionsCache);
};
const clearPermissionsCache = () => {
permissionsCache.clear();
};
export {
updatePermissionsCache,
findPermissionsInCache,
clearPermissionsCache,
inspectPermissionsCache,
};

View File

@ -3,7 +3,6 @@ import cookie from 'cookie';
import { BACKEND_BASE_URL } from '../config';
import { AuthenticationOption } from '../interfaces';
import { parseTaskShowUrl } from '../helpers';
import { clearPermissionsCache } from './PermissionCacheService';
// NOTE: this currently stores the jwt token in local storage
// which is considered insecure. Server set cookies seem to be considered
@ -102,10 +101,6 @@ const doLogout = () => {
logoutRedirectUrl += '&backend_only=true';
}
// Wipe all cached permissions so if user logs back in
// (either as themselves or a different user), they get the correct permissions
clearPermissionsCache();
window.location.href = logoutRedirectUrl;
};