diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c733570f07..fb972b31d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -121,5 +121,7 @@ jobs: retry_on: error command: npm ci - - run: npm run test:unit + - run: | + npm run build -- --ignore open-lens + npm run test:unit name: Run tests diff --git a/package-lock.json b/package-lock.json index 946b5f8168..c06df90bc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3313,6 +3313,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -3329,6 +3330,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -3340,6 +3342,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -3355,12 +3358,14 @@ "node_modules/@jest/console/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/@jest/console/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -3377,6 +3382,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", + "peer": true, "dependencies": { "@jest/console": "^28.1.3", "@jest/reporters": "^28.1.3", @@ -3424,6 +3430,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -3437,6 +3444,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -3448,6 +3456,7 @@ "version": "28.1.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.13", "callsites": "^3.0.0", @@ -3461,6 +3470,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -3486,6 +3496,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -3501,12 +3512,14 @@ "node_modules/@jest/core/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/@jest/core/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -3517,12 +3530,14 @@ "node_modules/@jest/core/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/@jest/core/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -3545,6 +3560,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, "engines": { "node": ">=10" }, @@ -3556,6 +3572,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, "engines": { "node": ">=8" }, @@ -3567,6 +3584,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -3591,6 +3609,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -3599,6 +3618,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -3631,6 +3651,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -3647,6 +3668,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -3658,6 +3680,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -3671,12 +3694,14 @@ "node_modules/@jest/core/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/@jest/core/node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "peer": true, "dependencies": { "glob": "^7.1.3" }, @@ -3771,6 +3796,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", + "peer": true, "dependencies": { "expect": "^28.1.3", "jest-snapshot": "^28.1.3" @@ -4094,6 +4120,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", + "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^28.1.3", @@ -4137,6 +4164,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -4148,6 +4176,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -4173,6 +4202,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -4188,17 +4218,20 @@ "node_modules/@jest/reporters/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/@jest/reporters/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/@jest/reporters/node_modules/jest-haste-map": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -4223,6 +4256,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -4231,6 +4265,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -4271,6 +4306,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", + "peer": true, "dependencies": { "@jest/console": "^28.1.3", "@jest/types": "^28.1.3", @@ -4285,6 +4321,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -4296,6 +4333,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -4311,12 +4349,14 @@ "node_modules/@jest/test-result/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/@jest/test-sequencer": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", + "peer": true, "dependencies": { "@jest/test-result": "^28.1.3", "graceful-fs": "^4.2.9", @@ -4331,6 +4371,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -4342,6 +4383,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -4357,12 +4399,14 @@ "node_modules/@jest/test-sequencer/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/@jest/test-sequencer/node_modules/jest-haste-map": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -4387,6 +4431,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -4395,6 +4440,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -9135,6 +9181,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", + "peer": true, "dependencies": { "@jest/transform": "^28.1.3", "@types/babel__core": "^7.1.14", @@ -9155,6 +9202,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -9166,6 +9214,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -9191,6 +9240,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -9206,17 +9256,20 @@ "node_modules/babel-jest/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/babel-jest/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/babel-jest/node_modules/jest-haste-map": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -9241,6 +9294,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -9249,6 +9303,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -9280,6 +9335,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", + "peer": true, "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -9384,6 +9440,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", + "peer": true, "dependencies": { "babel-plugin-jest-hoist": "^28.1.3", "babel-preset-current-node-syntax": "^1.0.0" @@ -12876,6 +12933,7 @@ "version": "0.10.2", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", + "peer": true, "engines": { "node": ">=12" }, @@ -17735,6 +17793,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", + "peer": true, "dependencies": { "@jest/core": "^28.1.3", "@jest/types": "^28.1.3", @@ -17770,6 +17829,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", + "peer": true, "dependencies": { "execa": "^5.0.0", "p-limit": "^3.1.0" @@ -17782,6 +17842,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -17804,6 +17865,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, "engines": { "node": ">=10" }, @@ -17815,6 +17877,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, "engines": { "node": ">=8" }, @@ -17826,6 +17889,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -17837,6 +17901,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -17866,6 +17931,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -17879,6 +17945,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -17890,6 +17957,7 @@ "version": "28.1.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.13", "callsites": "^3.0.0", @@ -17903,6 +17971,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -17928,6 +17997,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -17943,12 +18013,14 @@ "node_modules/jest-circus/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-circus/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -17959,12 +18031,14 @@ "node_modules/jest-circus/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/jest-circus/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -17987,6 +18061,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, "engines": { "node": ">=10" }, @@ -17998,6 +18073,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, "engines": { "node": ">=8" }, @@ -18009,6 +18085,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -18033,6 +18110,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -18041,6 +18119,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -18073,6 +18152,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -18089,6 +18169,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -18100,6 +18181,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -18113,12 +18195,14 @@ "node_modules/jest-circus/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-cli": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", + "peer": true, "dependencies": { "@jest/core": "^28.1.3", "@jest/test-result": "^28.1.3", @@ -18152,6 +18236,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -18163,6 +18248,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -18178,12 +18264,14 @@ "node_modules/jest-cli/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-cli/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -18200,6 +18288,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^28.1.3", @@ -18244,6 +18333,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -18255,6 +18345,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -18270,12 +18361,14 @@ "node_modules/jest-config/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-config/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -18287,6 +18380,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -18295,6 +18389,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -18311,6 +18406,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -18324,7 +18420,8 @@ "node_modules/jest-config/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-diff": { "version": "28.1.3", @@ -18390,6 +18487,7 @@ "version": "28.1.1", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", + "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -18401,6 +18499,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "chalk": "^4.0.0", @@ -18416,6 +18515,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -18427,6 +18527,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -18442,12 +18543,14 @@ "node_modules/jest-each/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-each/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -18459,6 +18562,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -18475,6 +18579,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -18488,7 +18593,8 @@ "node_modules/jest-each/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-environment-jsdom": { "version": "28.1.3", @@ -18767,6 +18873,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -18783,6 +18890,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -18794,6 +18902,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -18809,12 +18918,14 @@ "node_modules/jest-environment-node/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-environment-node/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -18891,6 +19002,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", + "peer": true, "dependencies": { "jest-get-type": "^28.0.2", "pretty-format": "^28.1.3" @@ -18903,6 +19015,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -18913,12 +19026,14 @@ "node_modules/jest-leak-detector/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-leak-detector/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -18930,6 +19045,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -18943,7 +19059,8 @@ "node_modules/jest-leak-detector/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-matcher-utils": { "version": "28.1.3", @@ -19171,6 +19288,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", + "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -19190,6 +19308,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", + "peer": true, "dependencies": { "jest-regex-util": "^28.0.2", "jest-snapshot": "^28.1.3" @@ -19202,6 +19321,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -19210,6 +19330,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -19221,6 +19342,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -19236,12 +19358,14 @@ "node_modules/jest-resolve/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-resolve/node_modules/jest-haste-map": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -19266,6 +19390,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -19274,6 +19399,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -19290,6 +19416,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", + "peer": true, "dependencies": { "@jest/console": "^28.1.3", "@jest/environment": "^28.1.3", @@ -19321,6 +19448,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/expect": "^28.1.3", @@ -19334,6 +19462,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -19345,6 +19474,7 @@ "version": "28.1.2", "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.13", "callsites": "^3.0.0", @@ -19358,6 +19488,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -19383,6 +19514,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -19398,17 +19530,20 @@ "node_modules/jest-runner/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-runner/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/jest-runner/node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -19431,6 +19566,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "peer": true, "engines": { "node": ">=10" }, @@ -19442,6 +19578,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "peer": true, "engines": { "node": ">=8" }, @@ -19453,6 +19590,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -19477,6 +19615,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -19485,6 +19624,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", + "peer": true, "dependencies": { "@jest/environment": "^28.1.3", "@jest/fake-timers": "^28.1.3", @@ -19517,6 +19657,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -19533,6 +19674,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -19544,6 +19686,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -19552,6 +19695,7 @@ "version": "0.5.13", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -19887,6 +20031,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -19920,6 +20065,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -19931,6 +20077,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", + "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^28.1.3", @@ -19956,6 +20103,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -19971,12 +20119,14 @@ "node_modules/jest-snapshot/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-snapshot/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -19987,12 +20137,14 @@ "node_modules/jest-snapshot/node_modules/convert-source-map": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "peer": true }, "node_modules/jest-snapshot/node_modules/jest-haste-map": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/graceful-fs": "^4.1.3", @@ -20017,6 +20169,7 @@ "version": "28.0.2", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", + "peer": true, "engines": { "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" } @@ -20025,6 +20178,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -20041,6 +20195,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -20054,7 +20209,8 @@ "node_modules/jest-snapshot/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-util": { "version": "29.5.0", @@ -20076,6 +20232,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "camelcase": "^6.2.0", @@ -20092,6 +20249,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -20103,6 +20261,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -20118,12 +20277,14 @@ "node_modules/jest-validate/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-validate/node_modules/ansi-styles": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "peer": true, "engines": { "node": ">=10" }, @@ -20135,6 +20296,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "peer": true, "engines": { "node": ">=10" }, @@ -20146,6 +20308,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "ansi-regex": "^5.0.1", @@ -20159,7 +20322,8 @@ "node_modules/jest-validate/node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "peer": true }, "node_modules/jest-watch-select-projects": { "version": "2.0.0", @@ -20526,6 +20690,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", + "peer": true, "dependencies": { "@jest/test-result": "^28.1.3", "@jest/types": "^28.1.3", @@ -20544,6 +20709,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -20555,6 +20721,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -20570,12 +20737,14 @@ "node_modules/jest-watcher/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/jest-watcher/node_modules/jest-util": { "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", + "peer": true, "dependencies": { "@jest/types": "^28.1.3", "@types/node": "*", @@ -20592,6 +20761,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", + "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -20605,6 +20775,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -20619,6 +20790,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", + "peer": true, "dependencies": { "@sinclair/typebox": "^0.24.1" }, @@ -20630,6 +20802,7 @@ "version": "28.1.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", + "peer": true, "dependencies": { "@jest/schemas": "^28.1.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -20645,7 +20818,8 @@ "node_modules/jest/node_modules/@sinclair/typebox": { "version": "0.24.51", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz", - "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==" + "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==", + "peer": true }, "node_modules/joi": { "version": "17.8.4", @@ -29856,6 +30030,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.1.tgz", "integrity": "sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==", + "peer": true, "engines": { "node": ">=10" } @@ -31513,6 +31688,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", + "peer": true, "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" @@ -31966,6 +32142,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "peer": true, "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" @@ -34197,7 +34374,6 @@ "filehound": "^1.17.6", "fs-extra": "^9.0.1", "glob-to-regexp": "^0.4.1", - "got": "^11.8.6", "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", "history": "^4.10.1", @@ -34268,7 +34444,7 @@ "@types/hapi__call": "^9.0.0", "@types/hapi__subtext": "^7.0.0", "@types/http-proxy": "^1.17.9", - "@types/jest": "^28.1.6", + "@types/jest": "^29.5.0", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.191", "@types/marked": "^4.0.8", @@ -34326,7 +34502,7 @@ "identity-obj-proxy": "^3.0.0", "ignore-loader": "^0.1.2", "include-media": "^1.4.9", - "jest": "^28.1.3", + "jest": "^29.5.0", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^28.1.3", "jest-mock-extended": "^2.0.9", @@ -34407,12 +34583,944 @@ "xterm-addon-fit": "^0.5.0" } }, + "packages/core/node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/core/node_modules/@jest/core/node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "packages/core/node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/core/node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/core/node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, + "packages/core/node_modules/@types/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "packages/core/node_modules/@types/node": { "version": "16.18.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz", "integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==", "dev": true }, + "packages/core/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/core/node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "packages/core/node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "packages/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/core/node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "packages/core/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "packages/core/node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/core/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/core/node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/core/node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/core/node_modules/jest-cli/node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "packages/core/node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/core/node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/core/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "packages/core/node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "packages/core/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "packages/core/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "packages/core/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "packages/ensure-binaries": { "name": "@k8slens/ensure-binaries", "version": "6.5.0-alpha.1", @@ -36504,7 +37612,7 @@ "esbuild-loader": "^2.20.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "html-webpack-plugin": "^5.5.0", - "jest": "^28.1.3", + "jest": "^29.5.0", "jest-environment-jsdom": "^28.1.3", "jsonfile": "^6.1.0", "mini-css-extract-plugin": "^2.7.1", @@ -36536,12 +37644,354 @@ "node": ">=16 <17" } }, + "packages/open-lens/node_modules/@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/@jest/core/node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "dependencies": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "packages/open-lens/node_modules/@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^2.0.0" + } + }, "packages/open-lens/node_modules/@types/node": { "version": "16.18.16", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.16.tgz", "integrity": "sha512-ZOzvDRWp8dCVBmgnkIqYCArgdFOO9YzocZp8Ra25N/RStKiWvMOXHMz+GjSeVNe5TstaTmTWPucGJkDw0XXJWA==", "dev": true }, + "packages/open-lens/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/open-lens/node_modules/babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "packages/open-lens/node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "packages/open-lens/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/open-lens/node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "packages/open-lens/node_modules/dotenv": { "version": "16.0.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", @@ -36551,6 +38001,571 @@ "node": ">=12" } }, + "packages/open-lens/node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "packages/open-lens/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "packages/open-lens/node_modules/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/open-lens/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/open-lens/node_modules/jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/jest-cli/node_modules/jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "packages/open-lens/node_modules/jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/open-lens/node_modules/pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "packages/open-lens/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "packages/open-lens/node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "packages/open-lens/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "packages/open-lens/node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "packages/open-lens/node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -36582,6 +38597,21 @@ "url": "https://opencollective.com/stylus" } }, + "packages/open-lens/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "packages/open-lens/node_modules/tsconfig-paths": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.2.tgz", diff --git a/packages/core/package.json b/packages/core/package.json index ad6e7e45ae..b608493c38 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -144,7 +144,6 @@ "filehound": "^1.17.6", "fs-extra": "^9.0.1", "glob-to-regexp": "^0.4.1", - "got": "^11.8.6", "grapheme-splitter": "^1.0.4", "handlebars": "^4.7.7", "history": "^4.10.1", @@ -215,7 +214,7 @@ "@types/hapi__call": "^9.0.0", "@types/hapi__subtext": "^7.0.0", "@types/http-proxy": "^1.17.9", - "@types/jest": "^28.1.6", + "@types/jest": "^29.5.0", "@types/js-yaml": "^4.0.5", "@types/lodash": "^4.14.191", "@types/marked": "^4.0.8", @@ -273,7 +272,7 @@ "identity-obj-proxy": "^3.0.0", "ignore-loader": "^0.1.2", "include-media": "^1.4.9", - "jest": "^28.1.3", + "jest": "^29.5.0", "jest-canvas-mock": "^2.3.1", "jest-environment-jsdom": "^28.1.3", "jest-mock-extended": "^2.0.9", diff --git a/packages/core/src/common/__tests__/hotbar-store.test.ts b/packages/core/src/common/__tests__/hotbar-store.test.ts deleted file mode 100644 index 474ebc1618..0000000000 --- a/packages/core/src/common/__tests__/hotbar-store.test.ts +++ /dev/null @@ -1,356 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { anyObject } from "jest-mock-extended"; -import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../catalog"; -import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; -import type { DiContainer } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../hotbars/store.injectable"; -import type { HotbarStore } from "../hotbars/store"; -import catalogEntityRegistryInjectable from "../../main/catalog/entity-registry.injectable"; -import { computed } from "mobx"; -import hasCategoryForEntityInjectable from "../catalog/has-category-for-entity.injectable"; -import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import loggerInjectable from "../logger.injectable"; -import type { Logger } from "../logger"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import writeJsonSyncInjectable from "../fs/write-json-sync.injectable"; - -function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { - return { - getName: jest.fn(() => data.metadata?.name), - getId: jest.fn(() => data.metadata?.uid), - getSource: jest.fn(() => data.metadata?.source ?? "unknown"), - isEnabled: jest.fn(() => data.status?.enabled ?? true), - onContextMenuOpen: jest.fn(), - onSettingsOpen: jest.fn(), - metadata: {}, - spec: {}, - status: {}, - ...data, - } as CatalogEntity; -} - -describe("HotbarStore", () => { - let di: DiContainer; - let hotbarStore: HotbarStore; - let testCluster: CatalogEntity; - let minikubeCluster: CatalogEntity; - let awsCluster: CatalogEntity; - let loggerMock: jest.Mocked; - - beforeEach(async () => { - di = getDiForUnitTesting(); - - testCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "some-test-id", - name: "my-test-cluster", - source: "local", - labels: {}, - }, - }); - minikubeCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "some-minikube-id", - name: "my-minikube-cluster", - source: "local", - labels: {}, - }, - }); - awsCluster = getMockCatalogEntity({ - apiVersion: "v1", - kind: "Cluster", - status: { - phase: "Running", - }, - metadata: { - uid: "some-aws-id", - name: "my-aws-cluster", - source: "local", - labels: {}, - }, - }); - - di.override(hasCategoryForEntityInjectable, () => () => true); - - loggerMock = { - warn: jest.fn(), - debug: jest.fn(), - error: jest.fn(), - info: jest.fn(), - silly: jest.fn(), - }; - - di.override(loggerInjectable, () => loggerMock); - - di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); - - const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); - const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); - - catalogEntityRegistry.addComputedSource("some-id", computed(() => [ - testCluster, - minikubeCluster, - awsCluster, - catalogCatalogEntity, - ])); - }); - - describe("given no previous data in store, running all migrations", () => { - beforeEach(() => { - hotbarStore = di.inject(hotbarStoreInjectable); - - hotbarStore.load(); - }); - - describe("load", () => { - it("loads one hotbar by default", () => { - expect(hotbarStore.hotbars.length).toEqual(1); - }); - }); - - describe("add", () => { - it("adds a hotbar", () => { - hotbarStore.add({ name: "hottest" }); - expect(hotbarStore.hotbars.length).toEqual(2); - }); - }); - - describe("hotbar items", () => { - it("initially creates 12 empty cells", () => { - expect(hotbarStore.getActive().items.length).toEqual(12); - }); - - it("initially adds catalog entity as first item", () => { - expect(hotbarStore.getActive().items[0]?.entity.name).toEqual("Catalog"); - }); - - it("adds items", () => { - hotbarStore.addToHotbar(testCluster); - const items = hotbarStore.getActive().items.filter(Boolean); - - expect(items.length).toEqual(2); - }); - - it("removes items", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.removeFromHotbar("some-test-id"); - hotbarStore.removeFromHotbar("catalog-entity"); - const items = hotbarStore.getActive().items.filter(Boolean); - - expect(items).toStrictEqual([]); - }); - - it("does nothing if removing with invalid uid", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.removeFromHotbar("invalid uid"); - const items = hotbarStore.getActive().items.filter(Boolean); - - expect(items.length).toEqual(2); - }); - - it("moves item to empty cell", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.addToHotbar(minikubeCluster); - hotbarStore.addToHotbar(awsCluster); - - expect(hotbarStore.getActive().items[6]).toBeNull(); - - hotbarStore.restackItems(1, 5); - - expect(hotbarStore.getActive().items[5]).toBeTruthy(); - expect(hotbarStore.getActive().items[5]?.entity.uid).toEqual("some-test-id"); - }); - - it("moves items down", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.addToHotbar(minikubeCluster); - hotbarStore.addToHotbar(awsCluster); - - // aws -> catalog - hotbarStore.restackItems(3, 0); - - const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - - expect(items.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]); - }); - - it("moves items up", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.addToHotbar(minikubeCluster); - hotbarStore.addToHotbar(awsCluster); - - // test -> aws - hotbarStore.restackItems(1, 3); - - const items = hotbarStore.getActive().items.map(item => item?.entity.uid || null); - - expect(items.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]); - }); - - it("logs an error if cellIndex is out of bounds", () => { - hotbarStore.add({ name: "hottest", id: "hottest" }); - hotbarStore.setActiveHotbar("hottest"); - - hotbarStore.addToHotbar(testCluster, -1); - expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); - - hotbarStore.addToHotbar(testCluster, 12); - expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); - - hotbarStore.addToHotbar(testCluster, 13); - expect(loggerMock.error).toBeCalledWith("[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range", anyObject()); - }); - - it("throws an error if getId is invalid or returns not a string", () => { - expect(() => hotbarStore.addToHotbar({} as any)).toThrowError(TypeError); - expect(() => hotbarStore.addToHotbar({ getId: () => true } as any)).toThrowError(TypeError); - }); - - it("throws an error if getName is invalid or returns not a string", () => { - expect(() => hotbarStore.addToHotbar({ getId: () => "" } as any)).toThrowError(TypeError); - expect(() => hotbarStore.addToHotbar({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError); - }); - - it("does nothing when item moved to same cell", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.restackItems(1, 1); - - expect(hotbarStore.getActive().items[1]?.entity.uid).toEqual("some-test-id"); - }); - - it("new items takes first empty cell", () => { - hotbarStore.addToHotbar(testCluster); - hotbarStore.addToHotbar(awsCluster); - hotbarStore.restackItems(0, 3); - hotbarStore.addToHotbar(minikubeCluster); - - expect(hotbarStore.getActive().items[0]?.entity.uid).toEqual("some-minikube-id"); - }); - - it("throws if invalid arguments provided", () => { - hotbarStore.addToHotbar(testCluster); - - expect(() => hotbarStore.restackItems(-5, 0)).toThrow(); - expect(() => hotbarStore.restackItems(2, -1)).toThrow(); - expect(() => hotbarStore.restackItems(14, 1)).toThrow(); - expect(() => hotbarStore.restackItems(11, 112)).toThrow(); - }); - - it("checks if entity already pinned to hotbar", () => { - hotbarStore.addToHotbar(testCluster); - - expect(hotbarStore.isAddedToActive(testCluster)).toBeTruthy(); - expect(hotbarStore.isAddedToActive(awsCluster)).toBeFalsy(); - }); - }); - }); - - describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => { - beforeEach(() => { - const writeJsonSync = di.inject(writeJsonSyncInjectable); - - writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", { - __internal__: { - migrations: { - version: "5.0.0-beta.3", - }, - }, - hotbars: [ - { - id: "3caac17f-aec2-4723-9694-ad204465d935", - name: "myhotbar", - items: [ - { - entity: { - uid: "some-aws-id", - }, - }, - { - entity: { - uid: "55b42c3c7ba3b04193416cda405269a5", - }, - }, - { - entity: { - uid: "176fd331968660832f62283219d7eb6e", - }, - }, - { - entity: { - uid: "61c4fb45528840ebad1badc25da41d14", - name: "user1-context", - source: "local", - }, - }, - { - entity: { - uid: "27d6f99fe9e7548a6e306760bfe19969", - name: "foo2", - source: "local", - }, - }, - null, - { - entity: { - uid: "c0b20040646849bb4dcf773e43a0bf27", - name: "multinode-demo", - source: "local", - }, - }, - null, - null, - null, - null, - null, - ], - }, - ], - }); - - di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10"); - - hotbarStore = di.inject(hotbarStoreInjectable); - - hotbarStore.load(); - }); - - it("allows to retrieve a hotbar", () => { - const hotbar = hotbarStore.findById("3caac17f-aec2-4723-9694-ad204465d935"); - - expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935"); - }); - - it("clears cells without entity", () => { - const items = hotbarStore.hotbars[0].items; - - expect(items[2]).toBeNull(); - }); - - it("adds extra data to cells with according entity", () => { - const items = hotbarStore.hotbars[0].items; - - expect(items[0]).toEqual({ - entity: { - name: "my-aws-cluster", - source: "local", - uid: "some-aws-id", - }, - }); - }); - }); -}); diff --git a/packages/core/src/common/__tests__/user-store.test.ts b/packages/core/src/common/__tests__/user-store.test.ts index fb8d020fb0..eee46fa9a4 100644 --- a/packages/core/src/common/__tests__/user-store.test.ts +++ b/packages/core/src/common/__tests__/user-store.test.ts @@ -2,11 +2,8 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { UserStore } from "../user-store"; -import userStoreInjectable from "../user-store/user-store.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import type { ClusterStoreModel } from "../cluster-store/cluster-store"; import { defaultThemeId } from "../vars"; import writeFileInjectable from "../fs/write-file.injectable"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; @@ -15,9 +12,16 @@ import releaseChannelInjectable from "../vars/release-channel.injectable"; import defaultUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/default-update-channel.injectable"; import writeJsonSyncInjectable from "../fs/write-json-sync.injectable"; import writeFileSyncInjectable from "../fs/write-file-sync.injectable"; +import type { UserPreferencesState } from "../../features/user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; +import userPreferencesPersistentStorageInjectable from "../../features/user-preferences/common/storage.injectable"; +import type { ResetTheme } from "../../features/user-preferences/common/reset-theme.injectable"; +import resetThemeInjectable from "../../features/user-preferences/common/reset-theme.injectable"; +import type { ClusterStoreModel } from "../../features/cluster/storage/common/storage.injectable"; describe("user store tests", () => { - let userStore: UserStore; + let state: UserPreferencesState; + let resetTheme: ResetTheme; let di: DiContainer; beforeEach(async () => { @@ -33,6 +37,8 @@ describe("user store tests", () => { await di.inject(defaultUpdateChannelInjectable).init(); + state = di.inject(userPreferencesStateInjectable); + resetTheme = di.inject(resetThemeInjectable); }); describe("for an empty config", () => { @@ -42,25 +48,23 @@ describe("user store tests", () => { writeJsonSync("/some-directory-for-user-data/lens-user-store.json", {}); writeJsonSync("/some-directory-for-user-data/kube_config", {}); - userStore = di.inject(userStoreInjectable); - - userStore.load(); + di.inject(userPreferencesPersistentStorageInjectable).loadAndStartSyncing(); }); it("allows setting and getting preferences", () => { - userStore.httpsProxy = "abcd://defg"; + state.httpsProxy = "abcd://defg"; - expect(userStore.httpsProxy).toBe("abcd://defg"); - expect(userStore.colorTheme).toBe(defaultThemeId); + expect(state.httpsProxy).toBe("abcd://defg"); + expect(state.colorTheme).toBe(defaultThemeId); - userStore.colorTheme = "light"; - expect(userStore.colorTheme).toBe("light"); + state.colorTheme = "light"; + expect(state.colorTheme).toBe("light"); }); it("correctly resets theme to default value", async () => { - userStore.colorTheme = "some other theme"; - userStore.resetTheme(); - expect(userStore.colorTheme).toBe(defaultThemeId); + state.colorTheme = "some other theme"; + resetTheme(); + expect(state.colorTheme).toBe(defaultThemeId); }); }); @@ -92,18 +96,16 @@ describe("user store tests", () => { di.override(storeMigrationVersionInjectable, () => "10.0.0"); - userStore = di.inject(userStoreInjectable); - - userStore.load(); + di.inject(userPreferencesPersistentStorageInjectable).loadAndStartSyncing(); }); it("skips clusters for adding to kube-sync with files under extension_data/", () => { - expect(userStore.syncKubeconfigEntries.has("/some-directory-for-user-data/extension_data/foo/bar")).toBe(false); - expect(userStore.syncKubeconfigEntries.has("/some/other/path")).toBe(true); + expect(state.syncKubeconfigEntries.has("/some-directory-for-user-data/extension_data/foo/bar")).toBe(false); + expect(state.syncKubeconfigEntries.has("/some/other/path")).toBe(true); }); it("allows access to the colorTheme preference", () => { - expect(userStore.colorTheme).toBe("light"); + expect(state.colorTheme).toBe("light"); }); }); }); diff --git a/packages/core/src/common/base-store/base-store.ts b/packages/core/src/common/base-store/base-store.ts deleted file mode 100644 index a1dd26f0f7..0000000000 --- a/packages/core/src/common/base-store/base-store.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type Config from "conf"; -import type { Migrations, Options as ConfOptions } from "conf/dist/source/types"; -import type { IEqualsComparer } from "mobx"; -import { makeObservable, reaction } from "mobx"; -import { disposer, isPromiseLike } from "@k8slens/utilities"; -import { broadcastMessage } from "../ipc"; -import isEqual from "lodash/isEqual"; -import { kebabCase } from "lodash"; -import type { GetConfigurationFileModel } from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import type { Logger } from "../logger"; -import type { PersistStateToConfig } from "./save-to-file"; -import type { GetBasenameOfPath } from "../path/get-basename.injectable"; -import type { EnlistMessageChannelListener } from "@k8slens/messaging"; -import { toJS } from "../utils"; - -export interface BaseStoreParams extends Omit, "migrations"> { - syncOptions?: { - fireImmediately?: boolean; - equals?: IEqualsComparer; - }; - configName: string; -} - -export interface IpcChannelPrefixes { - local: string; - remote: string; -} - -export interface BaseStoreDependencies { - readonly logger: Logger; - readonly storeMigrationVersion: string; - readonly directoryForUserData: string; - readonly migrations: Migrations>; - readonly ipcChannelPrefixes: IpcChannelPrefixes; - readonly shouldDisableSyncInListener: boolean; - getConfigurationFileModel: GetConfigurationFileModel; - persistStateToConfig: PersistStateToConfig; - getBasenameOfPath: GetBasenameOfPath; - enlistMessageChannelListener: EnlistMessageChannelListener; -} - -/** - * Note: T should only contain base JSON serializable types. - */ -export abstract class BaseStore { - private readonly syncDisposers = disposer(); - - readonly displayName = kebabCase(this.params.configName).toUpperCase(); - - /** - * @ignore - */ - protected readonly dependencies: BaseStoreDependencies; - - protected constructor( - dependencies: BaseStoreDependencies, - protected readonly params: BaseStoreParams, - ) { - this.dependencies = dependencies; - makeObservable(this); - } - - /** - * This must be called after the last child's constructor is finished (or just before it finishes) - */ - load() { - this.dependencies.logger.info(`[${this.displayName}]: LOADING ...`); - - const config = this.dependencies.getConfigurationFileModel({ - projectName: "lens", - projectVersion: this.dependencies.storeMigrationVersion, - cwd: this.cwd(), - ...this.params, - migrations: this.dependencies.migrations as Migrations, - }); - - const res = this.fromStore(config.store); - - if (isPromiseLike(res)) { - this.dependencies.logger.error(`${this.displayName} extends BaseStore's fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`); - } - - this.startSyncing(config); - this.dependencies.logger.info(`[${this.displayName}]: LOADED from ${config.path}`); - } - - protected cwd() { - return this.dependencies.directoryForUserData; - } - - private startSyncing(config: Config) { - const name = this.dependencies.getBasenameOfPath(config.path); - - const disableSync = () => this.syncDisposers(); - const enableSync = () => { - this.syncDisposers.push( - reaction( - () => toJS(this.toJSON()), // unwrap possible observables and react to everything - model => { - this.dependencies.persistStateToConfig(config, model); - broadcastMessage(`${this.dependencies.ipcChannelPrefixes.remote}:${config.path}`, model); - }, - this.params.syncOptions, - ), - this.dependencies.enlistMessageChannelListener({ - id: this.displayName, - channel: { - id: `${this.dependencies.ipcChannelPrefixes.local}:${config.path}`, - }, - handler: (model) => { - this.dependencies.logger.silly(`[${this.displayName}]: syncing ${name}`, { model }); - - if (this.dependencies.shouldDisableSyncInListener) { - disableSync(); - } - - // todo: use "resourceVersion" if merge required (to avoid equality checks => better performance) - if (!isEqual(this.toJSON(), model)) { - this.fromStore(model as T); - } - - if (this.dependencies.shouldDisableSyncInListener) { - enableSync(); - } - }, - }), - ); - }; - - enableSync(); - } - - /** - * fromStore is called internally when a child class syncs with the file - * system. - * - * Note: This function **must** be synchronous. - * - * @param data the parsed information read from the stored JSON file - */ - protected abstract fromStore(data: T): void; - - /** - * toJSON is called when syncing the store to the filesystem. It should - * produce a JSON serializable object representation of the current state. - * - * It is recommended that a round trip is valid. Namely, calling - * `this.fromStore(this.toJSON())` shouldn't change the state. - */ - abstract toJSON(): T; -} diff --git a/packages/core/src/common/base-store/channel-prefix.ts b/packages/core/src/common/base-store/channel-prefix.ts deleted file mode 100644 index f2662c65e0..0000000000 --- a/packages/core/src/common/base-store/channel-prefix.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { getInjectionToken } from "@ogre-tools/injectable"; -import type { IpcChannelPrefixes } from "./base-store"; - -export const baseStoreIpcChannelPrefixesInjectionToken = getInjectionToken({ - id: "base-store-ipc-channel-prefix-token", -}); diff --git a/packages/core/src/common/base-store/migrations.injectable.ts b/packages/core/src/common/base-store/migrations.injectable.ts deleted file mode 100644 index d97abee1ec..0000000000 --- a/packages/core/src/common/base-store/migrations.injectable.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import type { InjectionToken } from "@ogre-tools/injectable"; -import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable"; -import type Conf from "conf/dist/source"; -import type { Migrations } from "conf/dist/source/types"; -import loggerInjectable from "../logger.injectable"; -import { getOrInsert, iter } from "@k8slens/utilities"; - -export interface MigrationDeclaration { - version: string; - run(store: Conf>>): void; -} - -const storeMigrationsInjectable = getInjectable({ - id: "store-migrations", - instantiate: (di, token): Migrations> => { - const logger = di.inject(loggerInjectable); - const declarations = di.injectMany(token); - const migrations = new Map(); - - for (const decl of declarations) { - getOrInsert(migrations, decl.version, []).push(decl.run); - } - - return Object.fromEntries( - iter.map( - migrations, - ([v, fns]) => [v, (store) => { - logger.info(`Running ${v} migration for ${store.path}`); - - for (const fn of fns) { - fn(store); - } - }], - ), - ); - }, - lifecycle: lifecycleEnum.keyedSingleton({ - getInstanceKey: (di, token: InjectionToken) => token.id, - }), -}); - -export default storeMigrationsInjectable; diff --git a/packages/core/src/common/catalog-entities/web-link.ts b/packages/core/src/common/catalog-entities/web-link.ts index 833f05d65b..4b18ac7f73 100644 --- a/packages/core/src/common/catalog-entities/web-link.ts +++ b/packages/core/src/common/catalog-entities/web-link.ts @@ -4,10 +4,10 @@ */ import { getEnvironmentSpecificLegacyGlobalDiForExtensionApi } from "../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import removeWeblinkInjectable from "../../features/weblinks/common/remove.injectable"; import type { CatalogEntityContextMenuContext, CatalogEntityMetadata, CatalogEntityStatus } from "../catalog"; import { CatalogCategory, CatalogEntity, categoryVersion } from "../catalog/catalog-entity"; import productNameInjectable from "../vars/product-name.injectable"; -import weblinkStoreInjectable from "../weblinks-store/weblink-store.injectable"; export type WebLinkStatusPhase = "available" | "unavailable"; @@ -34,13 +34,13 @@ export class WebLink extends CatalogEntity weblinkStore.removeById(this.getId()), + onClick: () => removeWeblink(this.getId()), confirm: { message: `Remove Web Link "${this.getName()}" from ${productName}?`, }, diff --git a/packages/core/src/common/catalog/helpers.ts b/packages/core/src/common/catalog/helpers.ts new file mode 100644 index 0000000000..75cdf726aa --- /dev/null +++ b/packages/core/src/common/catalog/helpers.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import type { CatalogEntity } from "./catalog-entity"; +import GraphemeSplitter from "grapheme-splitter"; +import { hasOwnProperty, hasTypedProperty, isObject, isString, iter } from "@k8slens/utilities"; + +function getNameParts(name: string): string[] { + const byWhitespace = name.split(/\s+/); + + if (byWhitespace.length > 1) { + return byWhitespace; + } + + const byDashes = name.split(/[-_]+/); + + if (byDashes.length > 1) { + return byDashes; + } + + return name.split(/@+/); +} + +export function limitGraphemeLengthOf(src: string, count: number): string { + const splitter = new GraphemeSplitter(); + + return iter + .chain(splitter.iterateGraphemes(src)) + .take(count) + .join(""); +} + +export function computeDefaultShortName(name: string) { + if (!name || typeof name !== "string") { + return "??"; + } + + const [rawFirst, rawSecond, rawThird] = getNameParts(name); + const splitter = new GraphemeSplitter(); + const first = splitter.iterateGraphemes(rawFirst); + const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first; + const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty(); + + return iter.chain(iter.take(first, 1)) + .concat(iter.take(second, 1)) + .concat(iter.take(third, 1)) + .join(""); +} + +export function getShortName(entity: CatalogEntity): string { + return entity.metadata.shortName || computeDefaultShortName(entity.getName()); +} + +export function getIconColourHash(entity: CatalogEntity): string { + return `${entity.metadata.name}-${entity.metadata.source}`; +} + +export function getIconBackground(entity: CatalogEntity): string | undefined { + if (isObject(entity.spec.icon)) { + if (hasTypedProperty(entity.spec.icon, "background", isString)) { + return entity.spec.icon.background; + } + + return hasOwnProperty(entity.spec.icon, "src") + ? "transparent" + : undefined; + } + + return undefined; +} + +export function getIconMaterial(entity: CatalogEntity): string | undefined { + if ( + isObject(entity.spec.icon) + && hasTypedProperty(entity.spec.icon, "material", isString) + ) { + return entity.spec.icon.material; + } + + return undefined; +} diff --git a/packages/core/src/common/cluster-store/cluster-store.injectable.ts b/packages/core/src/common/cluster-store/cluster-store.injectable.ts deleted file mode 100644 index 79eb02e36c..0000000000 --- a/packages/core/src/common/cluster-store/cluster-store.injectable.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { ClusterStore } from "./cluster-store"; -import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; -import emitAppEventInjectable from "../app-event-bus/emit-event.injectable"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../logger.injectable"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import storeMigrationsInjectable from "../base-store/migrations.injectable"; -import { clusterStoreMigrationInjectionToken } from "./migration-token"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync"; -import { persistStateToConfigInjectionToken } from "../base-store/save-to-file"; -import getBasenameOfPathInjectable from "../path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; - -const clusterStoreInjectable = getInjectable({ - id: "cluster-store", - - instantiate: (di) => new ClusterStore({ - readClusterConfigSync: di.inject(readClusterConfigSyncInjectable), - emitAppEvent: di.inject(emitAppEventInjectable), - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: di.inject(storeMigrationsInjectable, clusterStoreMigrationInjectionToken), - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - }), -}); - -export default clusterStoreInjectable; diff --git a/packages/core/src/common/cluster-store/cluster-store.ts b/packages/core/src/common/cluster-store/cluster-store.ts deleted file mode 100644 index 8d283411c9..0000000000 --- a/packages/core/src/common/cluster-store/cluster-store.ts +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - - -import { action, comparer, computed, makeObservable, observable } from "mobx"; -import type { BaseStoreDependencies } from "../base-store/base-store"; -import { BaseStore } from "../base-store/base-store"; -import { Cluster } from "../cluster/cluster"; -import { toJS } from "../utils"; -import type { ClusterModel, ClusterId } from "../cluster-types"; -import type { ReadClusterConfigSync } from "./read-cluster-config.injectable"; -import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; - -export interface ClusterStoreModel { - clusters?: ClusterModel[]; -} - -interface Dependencies extends BaseStoreDependencies { - readClusterConfigSync: ReadClusterConfigSync; - emitAppEvent: EmitAppEvent; -} - -export class ClusterStore extends BaseStore { - readonly clusters = observable.map(); - - constructor(protected readonly dependencies: Dependencies) { - super(dependencies, { - configName: "lens-cluster-store", - accessPropertiesByDotNotation: false, // To make dots safe in cluster context names - syncOptions: { - equals: comparer.structural, - }, - }); - - makeObservable(this); - } - - @computed get clustersList(): Cluster[] { - return Array.from(this.clusters.values()); - } - - @computed get connectedClustersList(): Cluster[] { - return this.clustersList.filter((c) => !c.disconnected); - } - - hasClusters() { - return this.clusters.size > 0; - } - - getById(id: ClusterId | undefined): Cluster | undefined { - if (id) { - return this.clusters.get(id); - } - - return undefined; - } - - addCluster(clusterOrModel: ClusterModel | Cluster): Cluster { - this.dependencies.emitAppEvent({ name: "cluster", action: "add" }); - - const cluster = clusterOrModel instanceof Cluster - ? clusterOrModel - : new Cluster( - clusterOrModel, - this.dependencies.readClusterConfigSync(clusterOrModel), - ); - - this.clusters.set(cluster.id, cluster); - - return cluster; - } - - @action - protected fromStore({ clusters = [] }: ClusterStoreModel = {}) { - const currentClusters = new Map(this.clusters); - const newClusters = new Map(); - - // update new clusters - for (const clusterModel of clusters) { - try { - let cluster = currentClusters.get(clusterModel.id); - - if (cluster) { - cluster.updateModel(clusterModel); - } else { - cluster = new Cluster( - clusterModel, - this.dependencies.readClusterConfigSync(clusterModel), - ); - } - newClusters.set(clusterModel.id, cluster); - } catch (error) { - this.dependencies.logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`); - } - } - - this.clusters.replace(newClusters); - } - - toJSON(): ClusterStoreModel { - return toJS({ - clusters: this.clustersList.map(cluster => cluster.toJSON()), - }); - } -} diff --git a/packages/core/src/common/error-reporting/initialize-sentry-reporting.injectable.ts b/packages/core/src/common/error-reporting/initialize-sentry-reporting.injectable.ts index 677c18a586..3a72ef7b12 100644 --- a/packages/core/src/common/error-reporting/initialize-sentry-reporting.injectable.ts +++ b/packages/core/src/common/error-reporting/initialize-sentry-reporting.injectable.ts @@ -9,7 +9,7 @@ import isProductionInjectable from "../vars/is-production.injectable"; import sentryDataSourceNameInjectable from "../vars/sentry-dsn-url.injectable"; import { Dedupe, Offline } from "@sentry/integrations"; import { inspect } from "util"; -import userStoreInjectable from "../user-store/user-store.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; export type InitializeSentryReportingWith = (initSentry: (opts: BrowserOptions | ElectronMainOptions) => void) => void; @@ -20,7 +20,7 @@ const initializeSentryReportingWithInjectable = getInjectable({ instantiate: (di): InitializeSentryReportingWith => { const sentryDataSourceName = di.inject(sentryDataSourceNameInjectable); const isProduction = di.inject(isProductionInjectable); - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); if (!sentryDataSourceName) { return () => {}; @@ -28,7 +28,7 @@ const initializeSentryReportingWithInjectable = getInjectable({ return (initSentry) => initSentry({ beforeSend: (event) => { - if (userStore.allowErrorReporting) { + if (state.allowErrorReporting) { return event; } diff --git a/packages/core/src/common/fetch/proxy-fetch.injectable.ts b/packages/core/src/common/fetch/proxy-fetch.injectable.ts index f13842c410..433670e003 100644 --- a/packages/core/src/common/fetch/proxy-fetch.injectable.ts +++ b/packages/core/src/common/fetch/proxy-fetch.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { HttpsProxyAgent } from "hpagent"; -import userStoreInjectable from "../user-store/user-store.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; import type { Fetch } from "./fetch.injectable"; import fetchInjectable from "./fetch.injectable"; @@ -12,7 +12,7 @@ const proxyFetchInjectable = getInjectable({ id: "proxy-fetch", instantiate: (di): Fetch => { const fetch = di.inject(fetchInjectable); - const { httpsProxy, allowUntrustedCAs } = di.inject(userStoreInjectable); + const { httpsProxy, allowUntrustedCAs } = di.inject(userPreferencesStateInjectable); const agent = httpsProxy ? new HttpsProxyAgent({ proxy: httpsProxy, diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts index 0ad7ed3d88..c6a525269c 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/config-maps/config-maps-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const configMapsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts index 00002620ee..cef3bdcb5a 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/horizontal-pod-autoscalers/horizontal-pod-autoscalers-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const horizontalPodAutoscalersRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts index ea4eb2ae59..6da3564c8f 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/leases/leases-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const leasesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts index 0536e76004..a80a9e8ef4 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/limit-ranges/limit-ranges-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const limitRangesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts index 12ce0a2138..e297c7396e 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/pod-disruption-budgets/pod-disruption-budgets-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podDisruptionBudgetsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts index 75194b0541..ea424ec86d 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/priority-classes/priority-classes-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const priorityClassesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts index 4905b70890..96704bbc80 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/resource-quotas/resource-quotas-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const resourceQuotasRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts index beab83754f..a088ee2ba4 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/runtime-classes/runtime-classes-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const runtimeClassesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts index 7442de14b1..38343e4729 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/secrets/secrets-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const secretsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/config/vertical-pod-autoscalers/vertical-pod-autoscalers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/config/vertical-pod-autoscalers/vertical-pod-autoscalers-route.injectable.ts index 57776c8237..07559a8d2a 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/config/vertical-pod-autoscalers/vertical-pod-autoscalers-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/config/vertical-pod-autoscalers/vertical-pod-autoscalers-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const verticalPodAutoscalersRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts index 728f80c21d..fd4dfef75d 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/events/events-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const eventsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts index 0b57c8d573..6fb569fc11 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/namespaces/namespaces-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const namespacesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts index 50b3c72e4c..d8dcf8aa8f 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/network/endpoints/endpoints-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const endpointsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/network/ingress-class/ingress-classeses-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/network/ingress-class/ingress-classeses-route.injectable.ts index e036d4b6a6..efde1d8d7a 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/network/ingress-class/ingress-classeses-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/network/ingress-class/ingress-classeses-route.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; import { shouldShowResourceInjectionToken, -} from "../../../../../cluster-store/allowed-resources-injection-token"; +} from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; const ingressClassesesRouteInjectable = getInjectable({ id: "ingress-classes-route", diff --git a/packages/core/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts index 3a6669d99f..c2a6fe33cc 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/network/ingresses/ingresses-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { computedOr } from "@k8slens/utilities"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; diff --git a/packages/core/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts index 38a1b8a7e2..664eefa9f1 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/network/network-policies/network-policies-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const networkPoliciesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts index 223ded1e65..e9b130b318 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/network/services/services-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const servicesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts index e6ca61346e..1fe80a799b 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/nodes/nodes-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const nodesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts index e559c35079..bf9c3961ed 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/overview/cluster-overview-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../front-end-route-injection-token"; const clusterOverviewRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts index dbda527555..c8d7b64f8d 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volume-claims/persistent-volume-claims-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumeClaimsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts index 7a06d9df58..a93ac0a3c8 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/storage/persistent-volumes/persistent-volumes-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const persistentVolumesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts index 8702ab1602..0645c0f4eb 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/storage/storage-classes/storage-classes-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const storageClassesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts index 0903d5fced..158563f8d5 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-role-bindings/cluster-role-bindings-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRoleBindingsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts index 9fce206667..db28d8dfff 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/cluster-roles/cluster-roles-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const clusterRolesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts index 2f35986916..27f9165be8 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/pod-security-policies/pod-security-policies-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podSecurityPoliciesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts index 759c1b8eda..195210df23 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/role-bindings/role-bindings-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const roleBindingsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts index efe4cad810..807a12177c 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/roles/roles-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const rolesRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts index 65d02135c7..aecc5a3640 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/user-management/service-accounts/service-accounts-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const serviceAccountsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts index 33453a2247..0899486298 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/cron-jobs/cron-jobs-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const cronJobsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts index f1ec2008fa..42f1329551 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/daemonsets/daemonsets-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const daemonsetsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts index 84c059780f..222f842981 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/deployments/deployments-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const deploymentsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts index 39cc89e88f..4933c69531 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/jobs/jobs-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const jobsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts index d013f872f0..6563896893 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/pods/pods-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const podsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts index b790ce13ec..bff60b2b8d 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicasets/replicasets-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const replicasetsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts index 77d87abc96..c6ce4afafe 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/replicationcontrollers/replicationcontrollers-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const replicationControllersRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts b/packages/core/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts index 72c81b3bee..0a0160ccd9 100644 --- a/packages/core/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts +++ b/packages/core/src/common/front-end-routing/routes/cluster/workloads/statefulsets/statefulsets-route.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldShowResourceInjectionToken } from "../../../../../cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { frontEndRouteInjectionToken } from "../../../../front-end-route-injection-token"; const statefulsetsRouteInjectable = getInjectable({ diff --git a/packages/core/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts b/packages/core/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts index 44f86f270d..a13a3f8929 100644 --- a/packages/core/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts +++ b/packages/core/src/common/front-end-routing/verify-that-all-routes-have-route-component.test.ts @@ -6,22 +6,14 @@ import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; import { routeSpecificComponentInjectionToken } from "../../renderer/routes/route-specific-component-injection-token"; import { frontEndRouteInjectionToken } from "./front-end-route-injection-token"; import { filter, map } from "lodash/fp"; -import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; -import type { ClusterStore } from "../cluster-store/cluster-store"; import { pipeline } from "@ogre-tools/fp"; describe("verify-that-all-routes-have-component", () => { it("verify that routes have route component", () => { const rendererDi = getDiForUnitTesting(); - rendererDi.override(clusterStoreInjectable, () => ({ - getById: () => null, - } as unknown as ClusterStore)); - const routes = rendererDi.injectMany(frontEndRouteInjectionToken); - const routeComponents = rendererDi.injectMany( - routeSpecificComponentInjectionToken, - ); + const routeComponents = rendererDi.injectMany(routeSpecificComponentInjectionToken); const routesMissingComponent = pipeline( routes, diff --git a/packages/core/src/common/fs/fs.injectable.ts b/packages/core/src/common/fs/fs.injectable.ts index b42a51aad7..16fbbd73aa 100644 --- a/packages/core/src/common/fs/fs.injectable.ts +++ b/packages/core/src/common/fs/fs.injectable.ts @@ -22,6 +22,7 @@ const fsInjectable = getInjectable({ access, stat, unlink, + rename, }, ensureDir, ensureDirSync, @@ -58,6 +59,7 @@ const fsInjectable = getInjectable({ createReadStream, stat, unlink, + rename, }; }, causesSideEffects: true, diff --git a/packages/core/src/common/get-configuration-file-model/get-configuration-file-model.global-override-for-injectable.ts b/packages/core/src/common/get-configuration-file-model/get-configuration-file-model.global-override-for-injectable.ts index 95e2845825..5b83b7b573 100644 --- a/packages/core/src/common/get-configuration-file-model/get-configuration-file-model.global-override-for-injectable.ts +++ b/packages/core/src/common/get-configuration-file-model/get-configuration-file-model.global-override-for-injectable.ts @@ -10,7 +10,7 @@ import getConfigurationFileModelInjectable from "./get-configuration-file-model. import type Config from "conf"; import readJsonSyncInjectable from "../fs/read-json-sync.injectable"; import writeJsonSyncInjectable from "../fs/write-json-sync.injectable"; -import { get, set } from "lodash"; +import { get, has, set } from "lodash"; import semver from "semver"; const MIGRATION_KEY = `__internal__.migrations.version`; @@ -64,7 +64,7 @@ export default getGlobalOverride(getConfigurationFileModelInjectable, (di) => { path: configFilePath, get: (key: string) => get(store, key), set: (key: string, value: unknown) => { - let currentState: object; + let currentState: Partial>; try { currentState = readJsonSync(configFilePath); @@ -78,6 +78,25 @@ export default getGlobalOverride(getConfigurationFileModelInjectable, (di) => { }); store = readJsonSync(configFilePath); }, + delete: (key: string) => { + let currentState: Partial>; + + try { + currentState = readJsonSync(configFilePath); + } catch { + currentState = {}; + } + + delete currentState[key]; + + writeJsonSync(configFilePath, currentState); + store = readJsonSync(configFilePath); + }, + has: (key: string) => has(store, key), + clear: () => { + writeJsonSync(configFilePath, {}); + store = readJsonSync(configFilePath); + }, } as Partial as Config; // Migrate diff --git a/packages/core/src/common/hotbars/add-hotbar.injectable.ts b/packages/core/src/common/hotbars/add-hotbar.injectable.ts deleted file mode 100644 index 25ee0f588a..0000000000 --- a/packages/core/src/common/hotbars/add-hotbar.injectable.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "./store.injectable"; -import type { CreateHotbarData, CreateHotbarOptions } from "./types"; - -export type AddHotbar = (data: CreateHotbarData, opts?: CreateHotbarOptions) => void; - -const addHotbarInjectable = getInjectable({ - id: "add-hotbar", - instantiate: (di): AddHotbar => { - const store = di.inject(hotbarStoreInjectable); - - return (data, opts) => store.add(data, opts); - }, -}); - -export default addHotbarInjectable; diff --git a/packages/core/src/common/hotbars/store.injectable.ts b/packages/core/src/common/hotbars/store.injectable.ts deleted file mode 100644 index ea25840e11..0000000000 --- a/packages/core/src/common/hotbars/store.injectable.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import catalogCatalogEntityInjectable from "../catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import { HotbarStore } from "./store"; -import loggerInjectable from "../logger.injectable"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import storeMigrationsInjectable from "../base-store/migrations.injectable"; -import { hotbarStoreMigrationInjectionToken } from "./migrations-token"; -import getBasenameOfPathInjectable from "../path/get-basename.injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix"; -import { persistStateToConfigInjectionToken } from "../base-store/save-to-file"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync"; - -const hotbarStoreInjectable = getInjectable({ - id: "hotbar-store", - - instantiate: (di) => new HotbarStore({ - catalogCatalogEntity: di.inject(catalogCatalogEntityInjectable), - logger: di.inject(loggerInjectable), - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: di.inject(storeMigrationsInjectable, hotbarStoreMigrationInjectionToken), - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - }), -}); - -export default hotbarStoreInjectable; diff --git a/packages/core/src/common/hotbars/store.ts b/packages/core/src/common/hotbars/store.ts deleted file mode 100644 index 709242f20e..0000000000 --- a/packages/core/src/common/hotbars/store.ts +++ /dev/null @@ -1,351 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { action, comparer, observable, makeObservable, computed } from "mobx"; -import type { BaseStoreDependencies } from "../base-store/base-store"; -import { BaseStore } from "../base-store/base-store"; -import { toJS } from "../utils"; -import type { CatalogEntity } from "../catalog"; -import { broadcastMessage } from "../ipc"; -import type { Hotbar, CreateHotbarData, CreateHotbarOptions } from "./types"; -import { defaultHotbarCells, getEmptyHotbar } from "./types"; -import { hotbarTooManyItemsChannel } from "../ipc/hotbar"; -import type { GeneralEntity } from "../catalog-entities"; -import type { Logger } from "../logger"; -import assert from "assert"; - -export interface HotbarStoreModel { - hotbars: Hotbar[]; - activeHotbarId: string; -} - -interface Dependencies extends BaseStoreDependencies { - readonly catalogCatalogEntity: GeneralEntity; - readonly logger: Logger; -} - -export class HotbarStore extends BaseStore { - @observable hotbars: Hotbar[] = []; - @observable private _activeHotbarId!: string; - - constructor(protected readonly dependencies: Dependencies) { - super(dependencies, { - configName: "lens-hotbar-store", - accessPropertiesByDotNotation: false, // To make dots safe in cluster context names - syncOptions: { - equals: comparer.structural, - }, - }); - makeObservable(this); - } - - @computed get activeHotbarId() { - return this._activeHotbarId; - } - - /** - * If `hotbar` points to a known hotbar, make it active. Otherwise, ignore - * @param hotbar The hotbar instance, or the index, or its ID - */ - setActiveHotbar(hotbar: Hotbar | number | string) { - if (typeof hotbar === "number") { - if (hotbar >= 0 && hotbar < this.hotbars.length) { - this._activeHotbarId = this.hotbars[hotbar].id; - } - } else if (typeof hotbar === "string") { - if (this.findById(hotbar)) { - this._activeHotbarId = hotbar; - } - } else { - if (this.hotbars.indexOf(hotbar) >= 0) { - this._activeHotbarId = hotbar.id; - } - } - } - - private hotbarIndexById(id: string) { - return this.hotbars.findIndex((hotbar) => hotbar.id === id); - } - - private hotbarIndex(hotbar: Hotbar) { - return this.hotbars.indexOf(hotbar); - } - - @computed get activeHotbarIndex() { - return this.hotbarIndexById(this.activeHotbarId); - } - - @action - protected fromStore(data: Partial = {}) { - if (!data.hotbars || !data.hotbars.length) { - const hotbar = getEmptyHotbar("Default"); - const { - metadata: { uid, name, source }, - } = this.dependencies.catalogCatalogEntity; - const initialItem = { entity: { uid, name, source }}; - - hotbar.items[0] = initialItem; - - this.hotbars = [hotbar]; - } else { - this.hotbars = data.hotbars; - } - - this.hotbars.forEach(ensureExactHotbarItemLength); - - if (data.activeHotbarId) { - this._activeHotbarId = data.activeHotbarId; - } - - if (!this._activeHotbarId) { - this._activeHotbarId = this.hotbars[0].id; - } - } - - toJSON(): HotbarStoreModel { - return toJS({ - hotbars: this.hotbars, - activeHotbarId: this.activeHotbarId, - }); - } - - getActive(): Hotbar { - const hotbar = this.findById(this.activeHotbarId); - - assert(hotbar, "There MUST always be an active hotbar"); - - return hotbar; - } - - findByName(name: string) { - return this.hotbars.find((hotbar) => hotbar.name === name); - } - - findById(id: string) { - return this.hotbars.find((hotbar) => hotbar.id === id); - } - - @action - add(data: CreateHotbarData, { setActive = false }: CreateHotbarOptions = {}) { - const hotbar = getEmptyHotbar(data.name, data.id); - - this.hotbars.push(hotbar); - - if (setActive) { - this._activeHotbarId = hotbar.id; - } - } - - @action - setHotbarName(id: string, name: string): void { - const index = this.hotbars.findIndex((hotbar) => hotbar.id === id); - - if (index < 0) { - return this.dependencies.logger.warn( - `[HOTBAR-STORE]: cannot setHotbarName: unknown id`, - { id }, - ); - } - - this.hotbars[index].name = name; - } - - @action - remove(hotbar: Hotbar) { - assert(this.hotbars.length >= 2, "Cannot remove the last hotbar"); - - this.hotbars = this.hotbars.filter((h) => h !== hotbar); - - if (this.activeHotbarId === hotbar.id) { - this.setActiveHotbar(0); - } - } - - @action - addToHotbar(item: CatalogEntity, cellIndex?: number) { - const hotbar = this.getActive(); - const uid = item.getId(); - const name = item.getName(); - - if (typeof uid !== "string") { - throw new TypeError("CatalogEntity's ID must be a string"); - } - - if (typeof name !== "string") { - throw new TypeError("CatalogEntity's NAME must be a string"); - } - - if (this.isAddedToActive(item)) { - return; - } - - const entity = { - uid, - name, - source: item.metadata.source, - }; - const newItem = { entity }; - - if (cellIndex === undefined) { - // Add item to empty cell - const emptyCellIndex = hotbar.items.indexOf(null); - - if (emptyCellIndex != -1) { - hotbar.items[emptyCellIndex] = newItem; - } else { - broadcastMessage(hotbarTooManyItemsChannel); - } - } else if (0 <= cellIndex && cellIndex < hotbar.items.length) { - hotbar.items[cellIndex] = newItem; - } else { - this.dependencies.logger.error( - `[HOTBAR-STORE]: cannot pin entity to hotbar outside of index range`, - { entityId: uid, hotbarId: hotbar.id, cellIndex }, - ); - } - } - - @action - removeFromHotbar(uid: string): void { - const hotbar = this.getActive(); - const index = hotbar.items.findIndex((item) => item?.entity.uid === uid); - - if (index < 0) { - return; - } - - hotbar.items[index] = null; - } - - /** - * Remove all hotbar items that reference the `uid`. - * @param uid The `EntityId` that each hotbar item refers to - * @returns A function that will (in an action) undo the removing of the hotbar items. This function will not complete if the hotbar has changed. - */ - @action - removeAllHotbarItems(uid: string) { - for (const hotbar of this.hotbars) { - const index = hotbar.items.findIndex((i) => i?.entity.uid === uid); - - if (index >= 0) { - hotbar.items[index] = null; - } - } - } - - findClosestEmptyIndex(from: number, direction = 1) { - let index = from; - const hotbar = this.getActive(); - - while (hotbar.items[index] != null) { - index += direction; - } - - return index; - } - - @action - restackItems(from: number, to: number): void { - const { items } = this.getActive(); - const source = items[from]; - const moveDown = from < to; - - if ( - from < 0 || - to < 0 || - from >= items.length || - to >= items.length || - isNaN(from) || - isNaN(to) - ) { - throw new Error("Invalid 'from' or 'to' arguments"); - } - - if (from == to) { - return; - } - - items.splice(from, 1, null); - - if (items[to] == null) { - items.splice(to, 1, source); - } else { - // Move cells up or down to closes empty cell - items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1); - items.splice(to, 0, source); - } - } - - switchToPrevious() { - let index = this.activeHotbarIndex - 1; - - if (index < 0) { - index = this.hotbars.length - 1; - } - - this.setActiveHotbar(index); - } - - switchToNext() { - let index = this.activeHotbarIndex + 1; - - if (index >= this.hotbars.length) { - index = 0; - } - - this.setActiveHotbar(index); - } - - /** - * Checks if entity already pinned to the active hotbar - */ - isAddedToActive(entity: CatalogEntity | null | undefined): boolean { - if (!entity) { - return false; - } - - const indexInActiveHotbar = this.getActive().items.findIndex(item => item?.entity.uid === entity.getId()); - - return indexInActiveHotbar >= 0; - } - - getDisplayLabel(hotbar: Hotbar): string { - return `${this.getDisplayIndex(hotbar)}: ${hotbar.name}`; - } - - getDisplayIndex(hotbar: Hotbar): string { - const index = this.hotbarIndex(hotbar); - - if (index < 0) { - return "??"; - } - - return `${index + 1}`; - } -} - -/** - * This function ensures that there are always exactly `defaultHotbarCells` - * worth of items in the hotbar. - * @param hotbar The hotbar to modify - */ -function ensureExactHotbarItemLength(hotbar: Hotbar) { - // if there are not enough items - while (hotbar.items.length < defaultHotbarCells) { - hotbar.items.push(null); - } - - // if for some reason the hotbar was overfilled before, remove as many entries - // as needed, but prefer empty slots and items at the end first. - while (hotbar.items.length > defaultHotbarCells) { - const lastNull = hotbar.items.lastIndexOf(null); - - if (lastNull >= 0) { - hotbar.items.splice(lastNull, 1); - } else { - hotbar.items.length = defaultHotbarCells; - } - } -} diff --git a/packages/core/src/common/os/home-directory-path.injectable.ts b/packages/core/src/common/os/home-directory-path.injectable.ts index 83b4b0cdff..c8831eebab 100644 --- a/packages/core/src/common/os/home-directory-path.injectable.ts +++ b/packages/core/src/common/os/home-directory-path.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import userInfoInjectable from "../user-store/user-info.injectable"; +import userInfoInjectable from "../vars/user-info.injectable"; const homeDirectoryPathInjectable = getInjectable({ id: "home-directory-path", diff --git a/packages/core/src/common/persistent-storage/channel-prefix.ts b/packages/core/src/common/persistent-storage/channel-prefix.ts new file mode 100644 index 0000000000..aeb7da7a72 --- /dev/null +++ b/packages/core/src/common/persistent-storage/channel-prefix.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; + +export interface IpcChannelPrefixes { + readonly local: string; + readonly remote: string; +} + +export const persistentStorageIpcChannelPrefixesInjectionToken = getInjectionToken({ + id: "persistent-storage-ipc-channel-prefix-token", +}); diff --git a/packages/core/src/common/persistent-storage/create.injectable.ts b/packages/core/src/common/persistent-storage/create.injectable.ts new file mode 100644 index 0000000000..da54076a5a --- /dev/null +++ b/packages/core/src/common/persistent-storage/create.injectable.ts @@ -0,0 +1,158 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { disposer, isPromiseLike } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { Options } from "conf/dist/source"; +import { isEqual, kebabCase } from "lodash"; +import type { IEqualsComparer } from "mobx"; +import { reaction } from "mobx"; +import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; +import loggerInjectable from "../logger.injectable"; +import getBasenameOfPathInjectable from "../path/get-basename.injectable"; +import { enlistMessageChannelListenerInjectionToken, sendMessageToChannelInjectionToken } from "@k8slens/messaging"; +import type { MessageChannel } from "@k8slens/messaging"; +import { persistentStorageIpcChannelPrefixesInjectionToken } from "./channel-prefix"; +import { shouldPersistentStorageDisableSyncInIpcListenerInjectionToken } from "./disable-sync"; +import { persistStateToConfigInjectionToken } from "./save-to-file"; +import type { Migrations } from "./migrations.injectable"; +import { nextTick } from "process"; + +export interface PersistentStorage { + /** + * This method does the initial synchronous load from disk and then starts writing the state + * back to disk whenever it changes. + */ + loadAndStartSyncing: () => void; +} + +export interface PersistentStorageParams extends Omit, "migrations"> { + readonly syncOptions?: { + readonly fireImmediately?: boolean; + equals?: IEqualsComparer; + }; + readonly configName: string; + readonly migrations?: Migrations; + + /** + * fromStore is called internally when a child class syncs with the file + * system. + * + * Note: This function **must** be synchronous. + * + * @param data the parsed information read from the stored JSON file + */ + fromStore(data: Partial): void; + + /** + * toJSON is called when syncing the store to the filesystem. It should + * produce a JSON serializable object representation of the current state. + * + * It is recommended that a round trip is valid. Namely, calling + * `this.fromStore(this.toJSON())` shouldn't change the state. + */ + toJSON(): T; +} + +export type CreatePersistentStorage = (params: PersistentStorageParams) => PersistentStorage; + +const createPersistentStorageInjectable = getInjectable({ + id: "create-persistent-storage", + instantiate: (di): CreatePersistentStorage => { + const directoryForUserData = di.inject(directoryForUserDataInjectable); + const getConfigurationFileModel = di.inject(getConfigurationFileModelInjectable); + const logger = di.inject(loggerInjectable); + const getBasenameOfPath = di.inject(getBasenameOfPathInjectable); + const ipcChannelPrefixes = di.inject(persistentStorageIpcChannelPrefixesInjectionToken); + const persistStateToConfig = di.inject(persistStateToConfigInjectionToken); + const enlistMessageChannelListener = di.inject(enlistMessageChannelListenerInjectionToken); + const shouldDisableSyncInListener = di.inject(shouldPersistentStorageDisableSyncInIpcListenerInjectionToken); + const sendMessageToChannel = di.inject(sendMessageToChannelInjectionToken); + + return (rawParams: PersistentStorageParams) => { + const { + fromStore, + toJSON, + syncOptions, + migrations, + cwd = directoryForUserData, + ...params + } = rawParams; + const displayName = kebabCase(params.configName).toUpperCase(); + + const loadAndStartSyncing = () => { + logger.info(`[${displayName}]: LOADING ...`); + + const config = getConfigurationFileModel({ + projectName: "lens", + cwd, + migrations: migrations as Options["migrations"], + ...params, + }); + + const res = fromStore(config.store); + + if (isPromiseLike(res)) { + logger.error(`${displayName} extends BaseStore's fromStore method returns a Promise or promise-like object. This is an error and must be fixed.`); + } + + logger.info(`[${displayName}]: LOADED from ${config.path}`); + + const syncDisposers = disposer(); + const sendChannel: MessageChannel = { + id: `${ipcChannelPrefixes.remote}:${config.path}`, + }; + const receiveChannel: MessageChannel = { + id: `${ipcChannelPrefixes.local}:${config.path}`, + }; + const name = getBasenameOfPath(config.path); + + const disableSync = () => syncDisposers(); + const enableSync = () => { + syncDisposers.push( + reaction( + () => toJSON(), + model => { + persistStateToConfig(config, model); + sendMessageToChannel(sendChannel, model); + }, + syncOptions, + ), + enlistMessageChannelListener({ + id: "persistent-storage-sync", + channel: receiveChannel, + handler: (model) => { + logger.silly(`[${displayName}]: syncing ${name}`, { model }); + + if (shouldDisableSyncInListener) { + disableSync(); + } + + // todo: use "resourceVersion" if merge required (to avoid equality checks => better performance) + if (!isEqual(toJSON(), model)) { + fromStore(model); + } + + if (shouldDisableSyncInListener) { + nextTick(() => { + enableSync(); + }); + } + }, + }), + ); + }; + + enableSync(); + }; + + return { + loadAndStartSyncing, + }; + }; + }, +}); + +export default createPersistentStorageInjectable; diff --git a/packages/core/src/common/base-store/disable-sync.ts b/packages/core/src/common/persistent-storage/disable-sync.ts similarity index 54% rename from packages/core/src/common/base-store/disable-sync.ts rename to packages/core/src/common/persistent-storage/disable-sync.ts index ce7abd16a1..db24561936 100644 --- a/packages/core/src/common/base-store/disable-sync.ts +++ b/packages/core/src/common/persistent-storage/disable-sync.ts @@ -5,6 +5,6 @@ import { getInjectionToken } from "@ogre-tools/injectable"; -export const shouldBaseStoreDisableSyncInIpcListenerInjectionToken = getInjectionToken({ - id: "should-base-store-disable-sync-in-ipc-listener-token", +export const shouldPersistentStorageDisableSyncInIpcListenerInjectionToken = getInjectionToken({ + id: "should-persistent-storage-disable-sync-in-ipc-listener-token", }); diff --git a/packages/core/src/common/persistent-storage/migrations.injectable.ts b/packages/core/src/common/persistent-storage/migrations.injectable.ts new file mode 100644 index 0000000000..e4bd43d1de --- /dev/null +++ b/packages/core/src/common/persistent-storage/migrations.injectable.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { InjectionToken } from "@ogre-tools/injectable"; +import { lifecycleEnum, getInjectable } from "@ogre-tools/injectable"; +import loggerInjectable from "../logger.injectable"; +import { getOrInsert, iter, object } from "@k8slens/utilities"; + +export type AllowedSetValue = T extends (...args: any[]) => any + ? never + : T extends undefined | symbol + ? never + : T; + +export interface MigrationStore { + readonly path: string; + get(key: string): unknown; + delete(key: string): void; + has(key: string): boolean; + clear(): void; + set(key: string, value: AllowedSetValue): void; +} + +export type Migrations = Partial void>>; + +export interface MigrationDeclaration { + version: string; + run(store: MigrationStore): void; +} + +const persistentStorageMigrationsInjectable = getInjectable({ + id: "persistent-storage-migrations", + instantiate: (di, token) => { + const logger = di.inject(loggerInjectable); + const declarations = di.injectMany(token); + const migrations = new Map(); + + for (const decl of declarations) { + getOrInsert(migrations, decl.version, []).push(decl.run); + } + + return iter.chain(migrations.entries()) + .map(([v, fns]) => [v, (store: MigrationStore) => { + logger.info(`Running ${v} migration for ${store.path}`); + + for (const fn of fns) { + fn(store); + } + }] as const) + .collect(object.fromEntries); + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, token: InjectionToken) => token.id, + }), +}); + +export default persistentStorageMigrationsInjectable; diff --git a/packages/core/src/common/base-store/save-to-file.ts b/packages/core/src/common/persistent-storage/save-to-file.ts similarity index 100% rename from packages/core/src/common/base-store/save-to-file.ts rename to packages/core/src/common/persistent-storage/save-to-file.ts diff --git a/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts b/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts new file mode 100644 index 0000000000..6568e8f983 --- /dev/null +++ b/packages/core/src/common/persistent-storage/storage-migration-version.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { InjectionToken } from "@ogre-tools/injectable"; +import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; +import * as semver from "semver"; +import type { MigrationDeclaration } from "./migrations.injectable"; + +/** + * NOTE: not all stores can use this computed version, namely if any migration uses a range for + * the version selector. + */ +const storageMigrationVersionInjectable = getInjectable({ + id: "storage-migration-version", + instantiate: (di, token) => { + const declarations = di.injectMany(token); + + return declarations.reduce((version, decl) => { + if (semver.gte(decl.version, version)) { + return decl.version; + } + + return version; + }, "1.0.0"); + }, + lifecycle: lifecycleEnum.keyedSingleton({ + getInstanceKey: (di, token: InjectionToken) => token.id, + }), +}); + +export default storageMigrationVersionInjectable; diff --git a/packages/core/src/common/protocol-handler/router.ts b/packages/core/src/common/protocol-handler/router.ts index 0018c385c2..7a48dbb343 100644 --- a/packages/core/src/common/protocol-handler/router.ts +++ b/packages/core/src/common/protocol-handler/router.ts @@ -10,13 +10,13 @@ import { isDefined, iter } from "@k8slens/utilities"; import { pathToRegexp } from "path-to-regexp"; import type Url from "url-parse"; import { RoutingError, RoutingErrorType } from "./error"; -import type { ExtensionsStore } from "../../extensions/extensions-store/extensions-store"; import type { ExtensionLoader } from "../../extensions/extension-loader"; import type { LensExtension } from "../../extensions/lens-extension"; import type { RouteHandler, RouteParams } from "./registration"; import { when } from "mobx"; import { ipcRenderer } from "electron"; import type { Logger } from "../logger"; +import type { IsExtensionEnabled } from "../../features/extensions/enabled/common/is-enabled.injectable"; // IPC channel for protocol actions. Main broadcasts the open-url events to this channel. export const ProtocolHandlerIpcPrefix = "protocol-handler"; @@ -65,8 +65,8 @@ export function foldAttemptResults(mainAttempt: RouteAttempt, rendererAttempt: R export interface LensProtocolRouterDependencies { readonly extensionLoader: ExtensionLoader; - readonly extensionsStore: ExtensionsStore; readonly logger: Logger; + isExtensionEnabled: IsExtensionEnabled; } export abstract class LensProtocolRouter { @@ -209,7 +209,7 @@ export abstract class LensProtocolRouter { return name; } - if (!this.dependencies.extensionsStore.isEnabled(extension)) { + if (!this.dependencies.isExtensionEnabled(extension)) { this.dependencies.logger.info(`${LensProtocolRouter.LoggingPrefix}: Extension ${name} matched, but not enabled`); return name; diff --git a/packages/core/src/common/user-store/index.ts b/packages/core/src/common/user-store/index.ts deleted file mode 100644 index 026167519b..0000000000 --- a/packages/core/src/common/user-store/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export * from "./user-store"; -export type { KubeconfigSyncEntry, KubeconfigSyncValue, UserPreferencesModel } from "./preferences-helpers"; diff --git a/packages/core/src/common/user-store/migrations-token.ts b/packages/core/src/common/user-store/migrations-token.ts deleted file mode 100644 index f3959beb3a..0000000000 --- a/packages/core/src/common/user-store/migrations-token.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { getInjectionToken } from "@ogre-tools/injectable"; -import type { MigrationDeclaration } from "../base-store/migrations.injectable"; - -export const userStoreMigrationInjectionToken = getInjectionToken({ - id: "user-store-migration-token", -}); diff --git a/packages/core/src/common/user-store/user-store.injectable.ts b/packages/core/src/common/user-store/user-store.injectable.ts deleted file mode 100644 index 10806007a2..0000000000 --- a/packages/core/src/common/user-store/user-store.injectable.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { UserStore } from "./user-store"; -import selectedUpdateChannelInjectable from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; -import emitAppEventInjectable from "../app-event-bus/emit-event.injectable"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../logger.injectable"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import storeMigrationsInjectable from "../base-store/migrations.injectable"; -import { userStoreMigrationInjectionToken } from "./migrations-token"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync"; -import { persistStateToConfigInjectionToken } from "../base-store/save-to-file"; -import getBasenameOfPathInjectable from "../path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; -import userStorePreferenceDescriptorsInjectable from "./preference-descriptors.injectable"; - -const userStoreInjectable = getInjectable({ - id: "user-store", - - instantiate: (di) => new UserStore({ - selectedUpdateChannel: di.inject(selectedUpdateChannelInjectable), - emitAppEvent: di.inject(emitAppEventInjectable), - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: di.inject(storeMigrationsInjectable, userStoreMigrationInjectionToken), - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - preferenceDescriptors: di.inject(userStorePreferenceDescriptorsInjectable), - }), -}); - -export default userStoreInjectable; diff --git a/packages/core/src/common/user-store/user-store.ts b/packages/core/src/common/user-store/user-store.ts deleted file mode 100644 index 4ffa31fac1..0000000000 --- a/packages/core/src/common/user-store/user-store.ts +++ /dev/null @@ -1,156 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { action, observable, makeObservable, isObservableArray, isObservableSet, isObservableMap } from "mobx"; -import type { BaseStoreDependencies } from "../base-store/base-store"; -import { BaseStore } from "../base-store/base-store"; -import { getOrInsertSet, toggle, object } from "@k8slens/utilities"; -import type { UserPreferencesModel, StoreType } from "./preferences-helpers"; -import type { EmitAppEvent } from "../app-event-bus/emit-event.injectable"; - -// TODO: Remove coupling with Feature -import type { SelectedUpdateChannel } from "../../features/application-update/common/selected-update-channel/selected-update-channel.injectable"; -import type { ReleaseChannel } from "../../features/application-update/common/update-channels"; -import type { PreferenceDescriptors } from "./preference-descriptors.injectable"; -import { toJS } from "../utils"; - -export interface UserStoreModel { - preferences: UserPreferencesModel; -} - -interface Dependencies extends BaseStoreDependencies { - readonly selectedUpdateChannel: SelectedUpdateChannel; - readonly preferenceDescriptors: PreferenceDescriptors; - emitAppEvent: EmitAppEvent; -} - -export class UserStore extends BaseStore /* implements UserStoreFlatModel (when strict null is enabled) */ { - constructor(protected readonly dependencies: Dependencies) { - super(dependencies, { - configName: "lens-user-store", - }); - - makeObservable(this); - } - - /** - * @deprecated No longer used - */ - @observable seenContexts = observable.set(); - - /** - * @deprecated No longer used - */ - @observable newContexts = observable.set(); - - @observable allowErrorReporting!: StoreType; - @observable allowUntrustedCAs!: StoreType; - @observable colorTheme!: StoreType; - @observable terminalTheme!: StoreType; - @observable localeTimezone!: StoreType; - @observable downloadMirror!: StoreType; - @observable httpsProxy!: StoreType; - @observable shell!: StoreType; - @observable downloadBinariesPath!: StoreType; - @observable kubectlBinariesPath!: StoreType; - @observable terminalCopyOnSelect!: StoreType; - @observable terminalConfig!: StoreType; - @observable extensionRegistryUrl!: StoreType; - - /** - * Download kubectl binaries matching cluster version - */ - @observable downloadKubectlBinaries!: StoreType; - - /** - * Whether the application should open itself at login. - */ - @observable openAtLogin!: StoreType; - - /** - * The column IDs under each configurable table ID that have been configured - * to not be shown - */ - @observable hiddenTableColumns!: StoreType; - - /** - * Monaco editor configs - */ - @observable editorConfiguration!: StoreType; - - /** - * The set of file/folder paths to be synced - */ - @observable syncKubeconfigEntries!: StoreType; - - /** - * Checks if a column (by ID) for a table (by ID) is configured to be hidden - * @param tableId The ID of the table to be checked against - * @param columnIds The list of IDs the check if one is hidden - * @returns true if at least one column under the table is set to hidden - */ - isTableColumnHidden(tableId: string, ...columnIds: (string | undefined)[]): boolean { - if (columnIds.length === 0) { - return false; - } - - const config = this.hiddenTableColumns.get(tableId); - - if (!config) { - return false; - } - - return columnIds.some(columnId => columnId && config.has(columnId)); - } - - /** - * Toggles the hidden configuration of a table's column - */ - toggleTableColumnVisibility(tableId: string, columnId: string) { - toggle(getOrInsertSet(this.hiddenTableColumns, tableId), columnId); - } - - @action - resetTheme() { - this.colorTheme = this.dependencies.preferenceDescriptors.colorTheme.fromStore(undefined); - } - - @action - protected fromStore({ preferences }: Partial = {}) { - this.dependencies.logger.debug("UserStore.fromStore()", { preferences }); - - for (const [key, { fromStore }] of object.entries(this.dependencies.preferenceDescriptors)) { - const curVal = this[key]; - const newVal = fromStore((preferences)?.[key] as never) as never; - - if (isObservableArray(curVal)) { - curVal.replace(newVal); - } else if (isObservableSet(curVal) || isObservableMap(curVal)) { - curVal.replace(newVal); - } else { - this[key] = newVal; - } - } - - // TODO: Switch to action-based saving instead saving stores by reaction - if (preferences?.updateChannel) { - this.dependencies.selectedUpdateChannel.setValue(preferences?.updateChannel as ReleaseChannel); - } - } - - toJSON(): UserStoreModel { - const preferences = object.fromEntries( - object.entries(this.dependencies.preferenceDescriptors) - .map(([key, { toStore }]) => [key, toStore(this[key] as never)]), - ) as UserPreferencesModel; - - return toJS({ - preferences: { - ...preferences, - updateChannel: this.dependencies.selectedUpdateChannel.value.get().id, - }, - }); - } -} diff --git a/packages/core/src/common/user-store/current-timezone.global-override-for-injectable.ts b/packages/core/src/common/vars/current-timezone.global-override-for-injectable.ts similarity index 100% rename from packages/core/src/common/user-store/current-timezone.global-override-for-injectable.ts rename to packages/core/src/common/vars/current-timezone.global-override-for-injectable.ts diff --git a/packages/core/src/common/user-store/current-timezone.injectable.ts b/packages/core/src/common/vars/current-timezone.injectable.ts similarity index 100% rename from packages/core/src/common/user-store/current-timezone.injectable.ts rename to packages/core/src/common/vars/current-timezone.injectable.ts diff --git a/packages/core/src/common/user-store/user-info.global-override-for-injectable.ts b/packages/core/src/common/vars/user-info.global-override-for-injectable.ts similarity index 100% rename from packages/core/src/common/user-store/user-info.global-override-for-injectable.ts rename to packages/core/src/common/vars/user-info.global-override-for-injectable.ts diff --git a/packages/core/src/common/user-store/user-info.injectable.ts b/packages/core/src/common/vars/user-info.injectable.ts similarity index 100% rename from packages/core/src/common/user-store/user-info.injectable.ts rename to packages/core/src/common/vars/user-info.injectable.ts diff --git a/packages/core/src/common/weblinks-store/weblink-store.injectable.ts b/packages/core/src/common/weblinks-store/weblink-store.injectable.ts deleted file mode 100644 index 843c8716e5..0000000000 --- a/packages/core/src/common/weblinks-store/weblink-store.injectable.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../base-store/disable-sync"; -import storeMigrationsInjectable from "../base-store/migrations.injectable"; -import { persistStateToConfigInjectionToken } from "../base-store/save-to-file"; -import getConfigurationFileModelInjectable from "../get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../logger.injectable"; -import getBasenameOfPathInjectable from "../path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import { weblinkStoreMigrationInjectionToken } from "./migration-token"; -import { WeblinkStore } from "./weblink-store"; - -const weblinkStoreInjectable = getInjectable({ - id: "weblink-store", - instantiate: (di) => new WeblinkStore({ - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: di.inject(storeMigrationsInjectable, weblinkStoreMigrationInjectionToken), - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - }), -}); - -export default weblinkStoreInjectable; diff --git a/packages/core/src/common/weblinks-store/weblink-store.ts b/packages/core/src/common/weblinks-store/weblink-store.ts deleted file mode 100644 index 2044ca08c1..0000000000 --- a/packages/core/src/common/weblinks-store/weblink-store.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { action, comparer, observable, makeObservable } from "mobx"; -import type { BaseStoreDependencies } from "../base-store/base-store"; -import { BaseStore } from "../base-store/base-store"; -import * as uuid from "uuid"; -import { toJS } from "../utils"; - -export interface WeblinkData { - id: string; - name: string; - url: string; -} - -export interface WeblinkCreateOptions { - id?: string; - name: string; - url: string; -} - - -export interface WeblinkStoreModel { - weblinks: WeblinkData[]; -} - -export class WeblinkStore extends BaseStore { - @observable weblinks: WeblinkData[] = []; - - constructor(deps: BaseStoreDependencies) { - super(deps, { - configName: "lens-weblink-store", - accessPropertiesByDotNotation: false, // To make dots safe in cluster context names - syncOptions: { - equals: comparer.structural, - }, - }); - makeObservable(this); - this.load(); - } - - @action - protected fromStore(data: Partial = {}) { - this.weblinks = data.weblinks || []; - } - - add(data: WeblinkCreateOptions) { - const { - id = uuid.v4(), - name, - url, - } = data; - const weblink: WeblinkData = { id, name, url }; - - this.weblinks.push(weblink); - - return weblink; - } - - @action - removeById(id: string) { - this.weblinks = this.weblinks.filter((w) => w.id !== id); - } - - toJSON(): WeblinkStoreModel { - const model: WeblinkStoreModel = { - weblinks: this.weblinks, - }; - - return toJS(model); - } -} diff --git a/packages/core/src/extensions/__tests__/extension-loader.test.ts b/packages/core/src/extensions/__tests__/extension-loader.test.ts index 4ded8cdbb9..9be0507f6d 100644 --- a/packages/core/src/extensions/__tests__/extension-loader.test.ts +++ b/packages/core/src/extensions/__tests__/extension-loader.test.ts @@ -5,14 +5,16 @@ import type { ExtensionLoader } from "../extension-loader"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; +import type { ObservableMap } from "mobx"; import { runInAction } from "mobx"; -import updateExtensionsStateInjectable from "../extension-loader/update-extensions-state/update-extensions-state.injectable"; import { delay } from "@k8slens/utilities"; import { getDiForUnitTesting } from "../../renderer/getDiForUnitTesting"; import ipcRendererInjectable from "../../renderer/utils/channel/ipc-renderer.injectable"; import type { IpcRenderer } from "electron"; import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import currentlyInClusterFrameInjectable from "../../renderer/routes/currently-in-cluster-frame.injectable"; +import type { LensExtensionState } from "../../features/extensions/enabled/common/state.injectable"; +import enabledExtensionsStateInjectable from "../../features/extensions/enabled/common/state.injectable"; const manifestPath = "manifest/path"; const manifestPath2 = "manifest/path2"; @@ -20,7 +22,7 @@ const manifestPath3 = "manifest/path3"; describe("ExtensionLoader", () => { let extensionLoader: ExtensionLoader; - let updateExtensionStateMock: jest.Mock; + let enabledExtensionsState: ObservableMap; beforeEach(() => { const di = getDiForUnitTesting(); @@ -106,69 +108,60 @@ describe("ExtensionLoader", () => { }, }) as unknown as IpcRenderer); - updateExtensionStateMock = jest.fn(); - - di.override(updateExtensionsStateInjectable, () => updateExtensionStateMock); - extensionLoader = di.inject(extensionLoaderInjectable); + enabledExtensionsState = di.inject(enabledExtensionsStateInjectable); }); it("renderer updates extension after ipc broadcast", async () => { - expect(extensionLoader.userExtensions).toMatchInlineSnapshot(`Map {}`); + expect(extensionLoader.userExtensions).toEqual(new Map()); await extensionLoader.init(); await delay(10); // Assert the extensions after the extension broadcast event - expect(extensionLoader.userExtensions).toMatchInlineSnapshot(` - Map { - "manifest/path" => Object { - "absolutePath": "/test/1", - "id": "manifest/path", - "isBundled": false, - "isEnabled": true, - "manifest": Object { - "name": "TestExtension", - "version": "1.0.0", + expect(extensionLoader.userExtensions).toEqual( + new Map([ + ["manifest/path", { + absolutePath: "/test/1", + id: "manifest/path", + isBundled: false, + isEnabled: true, + manifest: { + name: "TestExtension", + version: "1.0.0", }, - "manifestPath": "manifest/path", - }, - "manifest/path3" => Object { - "absolutePath": "/test/3", - "id": "manifest/path3", - "isBundled": false, - "isEnabled": true, - "manifest": Object { - "name": "TestExtension3", - "version": "3.0.0", + manifestPath: "manifest/path", + }], + ["manifest/path3", { + absolutePath: "/test/3", + id: "manifest/path3", + isBundled: false, + isEnabled: true, + manifest: { + name: "TestExtension3", + version: "3.0.0", }, - "manifestPath": "manifest/path3", - }, - } - `); + manifestPath: "manifest/path3", + }], + ]), + ); }); it("updates ExtensionsStore after isEnabled is changed", async () => { await extensionLoader.init(); - expect(updateExtensionStateMock).not.toHaveBeenCalled(); - runInAction(() => { extensionLoader.setIsEnabled("manifest/path", false); }); - expect(updateExtensionStateMock).toHaveBeenCalledWith( - expect.objectContaining({ - "manifest/path": { - enabled: false, - name: "TestExtension", - }, - - "manifest/path2": { - enabled: true, - name: "TestExtension2", - }, - }), - ); + expect(enabledExtensionsState.size).toBe(2); + expect(enabledExtensionsState.get("manifest/path")).toMatchObject({ + enabled: false, + name: "TestExtension", + }); + expect(enabledExtensionsState.get("manifest/path2")).toMatchObject({ + enabled: true, + name: "TestExtension2", + }); }); }); diff --git a/packages/core/src/extensions/base-extension-store.ts b/packages/core/src/extensions/base-extension-store.ts new file mode 100644 index 0000000000..703edfafd0 --- /dev/null +++ b/packages/core/src/extensions/base-extension-store.ts @@ -0,0 +1,103 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import * as path from "path"; +import type { LensExtension } from "./lens-extension"; +import type { StaticThis } from "../common/utils/singleton"; +import { getOrInsertWith } from "@k8slens/utilities"; +import { getLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; +import type { PersistentStorage, PersistentStorageParams } from "../common/persistent-storage/create.injectable"; +import createPersistentStorageInjectable from "../common/persistent-storage/create.injectable"; +import directoryForUserDataInjectable from "../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import assert from "assert"; +import type { Options } from "conf"; +import type { Migrations } from "../common/persistent-storage/migrations.injectable"; + +export interface ExtensionStoreParams extends Omit, "migrations" | "cwd" | "fromStore" | "toJSON"> { + migrations?: Options["migrations"]; + cwd?: string; +} + +export abstract class BaseExtensionStore { + private static readonly instances = new WeakMap(); + + /** + * @deprecated This is a form of global shared state. Just call `new Store(...)` + */ + static createInstance(this: StaticThis, ...args: R): T { + return getOrInsertWith(BaseExtensionStore.instances, this, () => new this(...args)) as T; + } + + /** + * @deprecated This is a form of global shared state. Just call `new Store(...)` + */ + static getInstance(this: StaticThis, strict?: true): T; + static getInstance(this: StaticThis, strict: false): T | undefined; + static getInstance(this: StaticThis, strict = true): T | undefined { + if (!BaseExtensionStore.instances.has(this) && strict) { + throw new TypeError(`instance of ${this.name} is not created`); + } + + return BaseExtensionStore.instances.get(this) as (T | undefined); + } + + protected persistentStorage?: PersistentStorage; + private readonly dependencies = (() => { + const di = getLegacyGlobalDiForExtensionApi(); + + return { + createPersistentStorage: di.inject(createPersistentStorageInjectable), + directoryForUserData: di.inject(directoryForUserDataInjectable), + } as const; + })(); + + constructor(protected readonly rawParams: ExtensionStoreParams) { } + + /** + * @deprecated This is a form of global shared state. Just call `new Store(...)` + */ + static resetInstance() { + BaseExtensionStore.instances.delete(this); + } + + protected extension?: LensExtension; + + loadExtension(extension: LensExtension) { + this.extension = extension; + const { + projectVersion = this.extension.version, + cwd: _cwd, // This is ignored to maintain backwards compatibility + migrations = {}, + ...params + } = this.rawParams; + + this.persistentStorage = this.dependencies.createPersistentStorage({ + ...params, + cwd: this.cwd(), + projectVersion, + migrations: migrations as Migrations, + fromStore: (data) => this.fromStore(data), + toJSON: () => this.toJSON(), + }); + + this.persistentStorage.loadAndStartSyncing(); + } + + /** + * @deprecated Never use this method. Instead call {@link BaseExtensionStore.loadExtension} + */ + load() { + this.persistentStorage?.loadAndStartSyncing(); + } + + protected cwd() { + assert(this.extension, "cwd can only be called in loadExtension"); + + return this.rawParams.cwd ?? path.join(this.dependencies.directoryForUserData, "extension-store", this.extension.storeName); + } + + abstract fromStore(data: Partial): void; + abstract toJSON(): T; +} diff --git a/packages/core/src/extensions/common-api/app.ts b/packages/core/src/extensions/common-api/app.ts index 8c190b984b..9e0c84f4e0 100644 --- a/packages/core/src/extensions/common-api/app.ts +++ b/packages/core/src/extensions/common-api/app.ts @@ -8,21 +8,21 @@ import isLinuxInjectable from "../../common/vars/is-linux.injectable"; import isMacInjectable from "../../common/vars/is-mac.injectable"; import isSnapPackageInjectable from "../../common/vars/is-snap-package.injectable"; import isWindowsInjectable from "../../common/vars/is-windows.injectable"; -import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import { getLegacyGlobalDiForExtensionApi } from "../as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import getEnabledExtensionsInjectable from "./get-enabled-extensions/get-enabled-extensions.injectable"; import { issuesTrackerUrl } from "../../common/vars"; import { buildVersionInjectionToken } from "../../common/vars/build-semantic-version.injectable"; import { asLegacyGlobalForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-object-for-extension-api"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; +import enabledExtensionsInjectable from "../../features/extensions/enabled/common/enabled-extensions.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; -const userStore = asLegacyGlobalForExtensionApi(userStoreInjectable); +const userStore = asLegacyGlobalForExtensionApi(userPreferencesStateInjectable); +const enabledExtensions = asLegacyGlobalForExtensionApi(enabledExtensionsInjectable); export const App = { Preferences: { getKubectlPath: () => userStore.kubectlBinariesPath, }, - getEnabledExtensions: asLegacyGlobalFunctionForExtensionApi(getEnabledExtensionsInjectable), + getEnabledExtensions: () => enabledExtensions.get(), get version() { const di = getLegacyGlobalDiForExtensionApi(); @@ -54,7 +54,7 @@ export const App = { return di.inject(isLinuxInjectable); }, /** - * @deprecated This value is now `""` and is left here for backwards compatability. + * @deprecated This value is now `""` and is left here for backwards compatibility. */ slackUrl: "", issuesTrackerUrl, diff --git a/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts b/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts deleted file mode 100644 index 747dfa4f42..0000000000 --- a/packages/core/src/extensions/common-api/get-enabled-extensions/get-enabled-extensions.injectable.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable"; - -const getEnabledExtensionsInjectable = getInjectable({ - id: "get-enabled-extensions", - - instantiate: (di) => () => - di.inject(extensionsStoreInjectable).enabledExtensions, -}); - -export default getEnabledExtensionsInjectable; diff --git a/packages/core/src/extensions/common-api/stores.ts b/packages/core/src/extensions/common-api/stores.ts index 023fc5c7a4..4585a1661d 100644 --- a/packages/core/src/extensions/common-api/stores.ts +++ b/packages/core/src/extensions/common-api/stores.ts @@ -3,10 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { BaseStoreParams } from "../../common/base-store/base-store"; -import { ExtensionStore } from "../extension-store"; +import type { PersistentStorageParams } from "../../common/persistent-storage/create.injectable"; +import type { ExtensionStoreParams } from "../base-extension-store"; +import { BaseExtensionStore as ExtensionStore } from "../base-extension-store"; -export { - BaseStoreParams, - ExtensionStore, +export type { + ExtensionStoreParams, + PersistentStorageParams, }; + +export { ExtensionStore }; diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts index 5e2a4cd84d..92b1c8cf77 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.injectable.ts @@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionDiscovery } from "./extension-discovery"; import extensionLoaderInjectable from "../extension-loader/extension-loader.injectable"; import isCompatibleExtensionInjectable from "./is-compatible-extension/is-compatible-extension.injectable"; -import extensionsStoreInjectable from "../extensions-store/extensions-store.injectable"; import extensionInstallationStateStoreInjectable from "../extension-installation-state-store/extension-installation-state-store.injectable"; import installExtensionInjectable from "../install-extension/install-extension.injectable"; import extensionPackageRootDirectoryInjectable from "../install-extension/extension-package-root-directory.injectable"; @@ -28,13 +27,14 @@ import joinPathsInjectable from "../../common/path/join-paths.injectable"; import removePathInjectable from "../../common/fs/remove.injectable"; import homeDirectoryPathInjectable from "../../common/os/home-directory-path.injectable"; import lensResourcesDirInjectable from "../../common/vars/lens-resources-dir.injectable"; +import isExtensionEnabledInjectable from "../../features/extensions/enabled/common/is-enabled.injectable"; const extensionDiscoveryInjectable = getInjectable({ id: "extension-discovery", instantiate: (di) => new ExtensionDiscovery({ extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(extensionsStoreInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), extensionInstallationStateStore: di.inject(extensionInstallationStateStoreInjectable), isCompatibleExtension: di.inject(isCompatibleExtensionInjectable), installExtension: di.inject(installExtensionInjectable), diff --git a/packages/core/src/extensions/extension-discovery/extension-discovery.ts b/packages/core/src/extensions/extension-discovery/extension-discovery.ts index 88864d8663..7a39fa7467 100644 --- a/packages/core/src/extensions/extension-discovery/extension-discovery.ts +++ b/packages/core/src/extensions/extension-discovery/extension-discovery.ts @@ -9,7 +9,6 @@ import { makeObservable, observable, reaction, when } from "mobx"; import { broadcastMessage, ipcMainHandle, ipcRendererOn } from "../../common/ipc"; import { toJS } from "../../common/utils"; import { isErrnoException } from "@k8slens/utilities"; -import type { ExtensionsStore } from "../extensions-store/extensions-store"; import type { ExtensionLoader } from "../extension-loader"; import type { InstalledExtension, LensExtensionId, LensExtensionManifest } from "@k8slens/legacy-extensions"; import type { ExtensionInstallationStateStore } from "../extension-installation-state-store/extension-installation-state-store"; @@ -31,10 +30,10 @@ import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable" import type { GetRelativePath } from "../../common/path/get-relative-path.injectable"; import type { RemovePath } from "../../common/fs/remove.injectable"; import type TypedEventEmitter from "typed-emitter"; +import type { IsExtensionEnabled } from "../../features/extensions/enabled/common/is-enabled.injectable"; interface Dependencies { readonly extensionLoader: ExtensionLoader; - readonly extensionsStore: ExtensionsStore; readonly extensionInstallationStateStore: ExtensionInstallationStateStore; readonly extensionPackageRootDirectory: string; readonly resourcesDirectory: string; @@ -42,6 +41,7 @@ interface Dependencies { readonly isProduction: boolean; readonly fileSystemSeparator: string; readonly homeDirectoryPath: string; + isExtensionEnabled: IsExtensionEnabled; isCompatibleExtension: (manifest: LensExtensionManifest) => boolean; installExtension: (name: string) => Promise; readJsonFile: ReadJson; @@ -334,7 +334,7 @@ export class ExtensionDiscovery { try { const manifest = await this.dependencies.readJsonFile(manifestPath) as unknown as LensExtensionManifest; const id = isBundled ? manifestPath : this.getInstalledManifestPath(manifest.name); - const isEnabled = this.dependencies.extensionsStore.isEnabled({ id, isBundled }); + const isEnabled = this.dependencies.isExtensionEnabled({ id, isBundled }); const extensionDir = this.dependencies.getDirnameOfPath(manifestPath); const npmPackage = this.dependencies.joinPaths(extensionDir, `${manifest.name}-${manifest.version}.tgz`); const absolutePath = this.dependencies.isProduction && await this.dependencies.pathExists(npmPackage) diff --git a/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts b/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts index e635a6cca1..12187c38c0 100644 --- a/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts +++ b/packages/core/src/extensions/extension-loader/extension-loader.injectable.ts @@ -4,7 +4,6 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { ExtensionLoader } from "./extension-loader"; -import updateExtensionsStateInjectable from "./update-extensions-state/update-extensions-state.injectable"; import { createExtensionInstanceInjectionToken } from "./create-extension-instance.token"; import extensionInstancesInjectable from "./extension-instances.injectable"; import type { LensExtension } from "../lens-extension"; @@ -14,6 +13,7 @@ import joinPathsInjectable from "../../common/path/join-paths.injectable"; import getDirnameOfPathInjectable from "../../common/path/get-dirname.injectable"; import { bundledExtensionInjectionToken } from "@k8slens/legacy-extensions"; import { extensionEntryPointNameInjectionToken } from "./entry-point-name"; +import updateExtensionsStateInjectable from "../../features/extensions/enabled/common/update-state.injectable"; const extensionLoaderInjectable = getInjectable({ id: "extension-loader", diff --git a/packages/core/src/extensions/extension-loader/extension-loader.ts b/packages/core/src/extensions/extension-loader/extension-loader.ts index 7d41ec1fda..4a24ad23a1 100644 --- a/packages/core/src/extensions/extension-loader/extension-loader.ts +++ b/packages/core/src/extensions/extension-loader/extension-loader.ts @@ -10,7 +10,6 @@ import { action, computed, makeObservable, toJS, observable, observe, reaction, import { broadcastMessage, ipcMainOn, ipcRendererOn, ipcMainHandle } from "../../common/ipc"; import { isDefined } from "@k8slens/utilities"; import type { LensExtension } from "../lens-extension"; -import type { LensExtensionState } from "../extensions-store/extensions-store"; import { extensionLoaderFromMainChannel, extensionLoaderFromRendererChannel } from "../../common/ipc/extension-handling"; import { requestExtensionLoaderInitialState } from "../../renderer/ipc"; import assert from "assert"; @@ -21,6 +20,7 @@ import type { Logger } from "../../common/logger"; import type { JoinPaths } from "../../common/path/join-paths.injectable"; import type { GetDirnameOfPath } from "../../common/path/get-dirname.injectable"; import type { LensExtensionId, BundledExtension, InstalledExtension, LensExtensionConstructor } from "@k8slens/legacy-extensions"; +import type { UpdateExtensionsState } from "../../features/extensions/enabled/common/update-state.injectable"; const logModule = "[EXTENSIONS-LOADER]"; @@ -29,7 +29,7 @@ interface Dependencies { readonly bundledExtensions: BundledExtension[]; readonly logger: Logger; readonly extensionEntryPointName: "main" | "renderer"; - updateExtensionsState: (extensionsState: Record) => void; + updateExtensionsState: UpdateExtensionsState; createExtensionInstance: CreateExtensionInstance; getExtension: (instance: LensExtension) => Extension; joinPaths: JoinPaths; @@ -125,13 +125,11 @@ export class ExtensionLoader { // Transform userExtensions to a state object for storing into ExtensionsStore @computed get storeState() { - return Object.fromEntries( - Array.from(this.userExtensions) - .map(([extId, extension]) => [extId, { - enabled: extension.isEnabled, - name: extension.manifest.name, - }]), - ); + return Array.from(this.userExtensions) + .map(([extId, extension]) => [extId, { + enabled: extension.isEnabled, + name: extension.manifest.name, + }] as const); } @action diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store-injection-token.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store-injection-token.ts index d7635a71b2..297238787e 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store-injection-token.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store-injection-token.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { MigrationDeclaration } from "../../../common/base-store/migrations.injectable"; +import type { MigrationDeclaration } from "../../../common/persistent-storage/migrations.injectable"; -export const fileSystemProvisionerStoreInjectionToken = getInjectionToken({ - id: "file-system-provisioner-store-injection-token", +export const fileSystemProvisionerStoreMigrationDeclarationInjectionToken = getInjectionToken({ + id: "file-system-provisioner-store-migration-declaration", }); diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts index 2295608520..fe78674d11 100644 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts +++ b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable.ts @@ -3,36 +3,36 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { FileSystemProvisionerStore } from "./file-system-provisioner-store"; -import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../../../common/logger.injectable"; -import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../../../common/base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../../common/base-store/disable-sync"; -import { persistStateToConfigInjectionToken } from "../../../common/base-store/save-to-file"; -import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; -import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable"; import { registeredExtensionsInjectable } from "./registered-extensions.injectable"; +import createPersistentStorageInjectable from "../../../common/persistent-storage/create.injectable"; +import { action } from "mobx"; +import { object } from "@k8slens/utilities"; +import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; const fileSystemProvisionerStoreInjectable = getInjectable({ id: "file-system-provisioner-store", - instantiate: (di) => new FileSystemProvisionerStore({ - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: {}, - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), - registeredExtensions: di.inject(registeredExtensionsInjectable), - }), + instantiate: (di) => { + const registeredExtensions = di.inject(registeredExtensionsInjectable); + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const storeMigrationVersion = di.inject(storeMigrationVersionInjectable); + + const store = createPersistentStorage({ + configName: "lens-filesystem-provisioner-store", + accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + projectVersion: storeMigrationVersion, + fromStore: action(({ extensions = {}}) => { + registeredExtensions.replace(object.entries(extensions)); + }), + toJSON: () => ({ + extensions: Object.fromEntries(registeredExtensions), + }), + }); + + return { + load: () => store.loadAndStartSyncing(), + }; + }, }); export default fileSystemProvisionerStoreInjectable; diff --git a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts b/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts deleted file mode 100644 index 6c48210479..0000000000 --- a/packages/core/src/extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { LensExtensionId } from "@k8slens/legacy-extensions"; -import type { ObservableMap } from "mobx"; -import { action, makeObservable } from "mobx"; -import type { BaseStoreDependencies } from "../../../common/base-store/base-store"; -import { BaseStore } from "../../../common/base-store/base-store"; -import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable"; - -interface FSProvisionModel { - extensions: Record; // extension names to paths -} - -interface Dependencies extends BaseStoreDependencies { - ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension; - registeredExtensions: ObservableMap; -} - -export class FileSystemProvisionerStore extends BaseStore { - constructor(protected readonly dependencies: Dependencies) { - super(dependencies, { - configName: "lens-filesystem-provisioner-store", - accessPropertiesByDotNotation: false, // To make dots safe in cluster context names - }); - - makeObservable(this); - } - - /** - * This function retrieves the saved path to the folder which the extension - * can saves files to. If the folder is not present then it is created. - * @param extensionName the name of the extension requesting the path - * @returns path to the folder that the extension can safely write files to. - */ - async requestDirectory(extensionName: string): Promise { - return this.dependencies.ensureHashedDirectoryForExtension(extensionName); - } - - @action - protected fromStore({ extensions }: FSProvisionModel = { extensions: {}}): void { - this.dependencies.registeredExtensions.merge(extensions); - } - - toJSON(): FSProvisionModel { - return { - extensions: Object.fromEntries(this.dependencies.registeredExtensions.toJSON()), - }; - } -} diff --git a/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts b/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts deleted file mode 100644 index 754375dd5a..0000000000 --- a/packages/core/src/extensions/extension-loader/update-extensions-state/update-extensions-state.injectable.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import extensionsStoreInjectable from "../../extensions-store/extensions-store.injectable"; - -const updateExtensionsStateInjectable = getInjectable({ - id: "update-extensions-state", - instantiate: (di) => di.inject(extensionsStoreInjectable).mergeState, -}); - -export default updateExtensionsStateInjectable; diff --git a/packages/core/src/extensions/extension-store.ts b/packages/core/src/extensions/extension-store.ts deleted file mode 100644 index fe9cff2a02..0000000000 --- a/packages/core/src/extensions/extension-store.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { BaseStoreParams } from "../common/base-store/base-store"; -import { BaseStore } from "../common/base-store/base-store"; -import * as path from "path"; -import type { LensExtension } from "./lens-extension"; -import assert from "assert"; -import type { StaticThis } from "../common/utils/singleton"; -import { getOrInsertWith } from "@k8slens/utilities"; -import { getLegacyGlobalDiForExtensionApi } from "./as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import directoryForUserDataInjectable from "../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import getConfigurationFileModelInjectable from "../common/get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../common/logger.injectable"; -import storeMigrationVersionInjectable from "../common/vars/store-migration-version.injectable"; -import type { Migrations } from "conf/dist/source/types"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../common/base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../common/base-store/disable-sync"; -import { persistStateToConfigInjectionToken } from "../common/base-store/save-to-file"; -import getBasenameOfPathInjectable from "../common/path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; - -export interface ExtensionStoreParams extends BaseStoreParams { - migrations?: Migrations; -} - -export abstract class ExtensionStore extends BaseStore { - private static readonly instances = new WeakMap(); - - /** - * @deprecated This is a form of global shared state. Just call `new Store(...)` - */ - static createInstance(this: StaticThis, ...args: R): T { - return getOrInsertWith(ExtensionStore.instances, this, () => new this(...args)) as T; - } - - /** - * @deprecated This is a form of global shared state. Just call `new Store(...)` - */ - static getInstance(this: StaticThis, strict?: true): T; - static getInstance(this: StaticThis, strict: false): T | undefined; - static getInstance(this: StaticThis, strict = true): T | undefined { - if (!ExtensionStore.instances.has(this) && strict) { - throw new TypeError(`instance of ${this.name} is not created`); - } - - return ExtensionStore.instances.get(this) as (T | undefined); - } - - constructor({ migrations, ...params }: ExtensionStoreParams) { - const di = getLegacyGlobalDiForExtensionApi(); - - super({ - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: migrations as Migrations>, - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - }, params); - } - - /** - * @deprecated This is a form of global shared state. Just call `new Store(...)` - */ - static resetInstance() { - ExtensionStore.instances.delete(this); - } - - protected extension?: LensExtension; - - loadExtension(extension: LensExtension) { - this.extension = extension; - - this.params.projectVersion ??= this.extension.version; - - return super.load(); - } - - load() { - if (!this.extension) { return; } - - return super.load(); - } - - protected cwd() { - assert(this.extension, "must call this.load() first"); - - return path.join(super.cwd(), "extension-store", this.extension.storeName); - } -} diff --git a/packages/core/src/extensions/extensions-store/extensions-store.injectable.ts b/packages/core/src/extensions/extensions-store/extensions-store.injectable.ts deleted file mode 100644 index 9e08a96aac..0000000000 --- a/packages/core/src/extensions/extensions-store/extensions-store.injectable.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import directoryForUserDataInjectable from "../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync"; -import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file"; -import getConfigurationFileModelInjectable from "../../common/get-configuration-file-model/get-configuration-file-model.injectable"; -import loggerInjectable from "../../common/logger.injectable"; -import getBasenameOfPathInjectable from "../../common/path/get-basename.injectable"; -import { enlistMessageChannelListenerInjectionToken } from "@k8slens/messaging"; -import storeMigrationVersionInjectable from "../../common/vars/store-migration-version.injectable"; -import { ExtensionsStore } from "./extensions-store"; - -const extensionsStoreInjectable = getInjectable({ - id: "extensions-store", - instantiate: (di) => new ExtensionsStore({ - directoryForUserData: di.inject(directoryForUserDataInjectable), - getConfigurationFileModel: di.inject(getConfigurationFileModelInjectable), - logger: di.inject(loggerInjectable), - storeMigrationVersion: di.inject(storeMigrationVersionInjectable), - migrations: {}, - getBasenameOfPath: di.inject(getBasenameOfPathInjectable), - ipcChannelPrefixes: di.inject(baseStoreIpcChannelPrefixesInjectionToken), - persistStateToConfig: di.inject(persistStateToConfigInjectionToken), - enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken), - shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken), - }), -}); - -export default extensionsStoreInjectable; diff --git a/packages/core/src/extensions/extensions-store/extensions-store.ts b/packages/core/src/extensions/extensions-store/extensions-store.ts deleted file mode 100644 index 17766d26d7..0000000000 --- a/packages/core/src/extensions/extensions-store/extensions-store.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { LensExtensionId } from "@k8slens/legacy-extensions"; -import { action, computed, makeObservable, observable } from "mobx"; -import type { BaseStoreDependencies } from "../../common/base-store/base-store"; -import { BaseStore } from "../../common/base-store/base-store"; - -export interface LensExtensionsStoreModel { - extensions: Record; -} - -export interface LensExtensionState { - enabled?: boolean; - name: string; -} - -export interface IsEnabledExtensionDescriptor { - id: string; - isBundled: boolean; -} - -export class ExtensionsStore extends BaseStore { - constructor(deps: BaseStoreDependencies) { - super(deps, { - configName: "lens-extensions", - }); - makeObservable(this); - this.load(); - } - - @computed - get enabledExtensions() { - return Array.from(this.state.values()) - .filter(({ enabled }) => enabled) - .map(({ name }) => name); - } - - protected state = observable.map(); - - isEnabled({ id, isBundled }: IsEnabledExtensionDescriptor): boolean { - // By default false, so that copied extensions are disabled by default. - // If user installs the extension from the UI, the Extensions component will specifically enable it. - return isBundled || Boolean(this.state.get(id)?.enabled); - } - - mergeState = action((extensionsState: Record | [LensExtensionId, LensExtensionState][]) => { - this.state.merge(extensionsState); - }); - - @action - protected fromStore({ extensions }: LensExtensionsStoreModel) { - this.state.merge(extensions); - } - - toJSON(): LensExtensionsStoreModel { - return { - extensions: Object.fromEntries(this.state.toJSON()), - }; - } -} diff --git a/packages/core/src/extensions/lens-extension-set-dependencies.ts b/packages/core/src/extensions/lens-extension-set-dependencies.ts index 7b7c62597a..aa22b7a1a1 100644 --- a/packages/core/src/extensions/lens-extension-set-dependencies.ts +++ b/packages/core/src/extensions/lens-extension-set-dependencies.ts @@ -10,13 +10,13 @@ import type { Route } from "../common/front-end-routing/front-end-route-injectio import type { CatalogEntityRegistry as MainCatalogEntityRegistry } from "../main/catalog"; import type { CatalogEntityRegistry as RendererCatalogEntityRegistry } from "../renderer/api/catalog/entity/registry"; import type { GetExtensionPageParameters } from "../renderer/routes/get-extension-page-parameters.injectable"; -import type { FileSystemProvisionerStore } from "./extension-loader/file-system-provisioner-store/file-system-provisioner-store"; import type { NavigateForExtension } from "../main/start-main-application/lens-window/navigate-for-extension.injectable"; import type { Logger } from "../common/logger"; +import type { EnsureHashedDirectoryForExtension } from "./extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable"; export interface LensExtensionDependencies { - readonly fileSystemProvisionerStore: FileSystemProvisionerStore; readonly logger: Logger; + ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension; } export interface LensMainExtensionDependencies extends LensExtensionDependencies { diff --git a/packages/core/src/extensions/lens-extension.ts b/packages/core/src/extensions/lens-extension.ts index 83ba4ebaec..ec5eca0cd9 100644 --- a/packages/core/src/extensions/lens-extension.ts +++ b/packages/core/src/extensions/lens-extension.ts @@ -83,7 +83,7 @@ export class LensExtension< */ async getExtensionFileFolder(): Promise { // storeName is read from the manifest and has a fallback to the manifest name, which equals id - return this[lensExtensionDependencies].fileSystemProvisionerStore.requestDirectory(this.storeName); + return this[lensExtensionDependencies].ensureHashedDirectoryForExtension(this.storeName); } @action diff --git a/packages/core/src/extensions/renderer-api/k8s-api.ts b/packages/core/src/extensions/renderer-api/k8s-api.ts index 1dfdc0e075..98e2cfcdfb 100644 --- a/packages/core/src/extensions/renderer-api/k8s-api.ts +++ b/packages/core/src/extensions/renderer-api/k8s-api.ts @@ -37,7 +37,7 @@ import namespaceApiInjectable from "../../common/k8s-api/endpoints/namespace.api import kubeEventApiInjectable from "../../common/k8s-api/endpoints/events.api.injectable"; import roleBindingApiInjectable from "../../common/k8s-api/endpoints/role-binding.api.injectable"; import customResourceDefinitionApiInjectable from "../../common/k8s-api/endpoints/custom-resource-definition.api.injectable"; -import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { asLegacyGlobalFunctionForExtensionApi } from "../as-legacy-globals-for-extension-api/as-legacy-global-function-for-extension-api"; import requestMetricsInjectable from "../../common/k8s-api/endpoints/metrics.api/request-metrics.injectable"; diff --git a/packages/core/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap b/packages/core/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap index 03e2a2cd8a..a63146ddcc 100644 --- a/packages/core/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap +++ b/packages/core/src/features/__snapshots__/extension-special-characters-in-page-registrations.test.tsx.snap @@ -230,7 +230,7 @@ exports[`extension special characters in page registrations renders 1`] = ` class="HotbarSelector" > mac", - "root -> mac -> about", - "root -> mac -> separator-1", - "root -> mac -> navigate-to-preferences", - "root -> mac -> navigate-to-extensions", - "root -> mac -> separator-2", - "root -> mac -> services", - "root -> mac -> separator-3", - "root -> mac -> hide", - "root -> mac -> hide-others", - "root -> mac -> unhide", - "root -> mac -> separator-4", - "root -> mac -> quit", - "root -> file", - "root -> file -> add-cluster", - "root -> file -> separator-1-for-file", - "root -> file -> close-window", - "root -> edit", - "root -> edit -> undo", - "root -> edit -> redo", - "root -> edit -> separator-1-in-edit", - "root -> edit -> cut", - "root -> edit -> copy", - "root -> edit -> paste", - "root -> edit -> delete", - "root -> edit -> separator-2-in-edit", - "root -> edit -> selectAll", - "root -> view", - "root -> view -> navigate-to-catalog", - "root -> view -> open-command-palette", - "root -> view -> separator-1-for-view", - "root -> view -> go-back", - "root -> view -> go-forward", - "root -> view -> reload", - "root -> view -> toggle-dev-tools", - "root -> view -> separator-2-for-view", - "root -> view -> reset-zoom", - "root -> view -> zoom-in", - "root -> view -> zoom-out", - "root -> view -> separator-3-for-view", - "root -> view -> toggle-full-screen", - "root -> help", - "root -> help -> navigate-to-welcome", - "root -> help -> open-documentation", - "root -> help -> open-support", -] +"[ + 'root', + 'root -> mac', + 'root -> mac -> about', + 'root -> mac -> separator-1', + 'root -> mac -> navigate-to-preferences', + 'root -> mac -> navigate-to-extensions', + 'root -> mac -> separator-2', + 'root -> mac -> services', + 'root -> mac -> separator-3', + 'root -> mac -> hide', + 'root -> mac -> hide-others', + 'root -> mac -> unhide', + 'root -> mac -> separator-4', + 'root -> mac -> quit', + 'root -> file', + 'root -> file -> add-cluster', + 'root -> file -> separator-1-for-file', + 'root -> file -> close-window', + 'root -> edit', + 'root -> edit -> undo', + 'root -> edit -> redo', + 'root -> edit -> separator-1-in-edit', + 'root -> edit -> cut', + 'root -> edit -> copy', + 'root -> edit -> paste', + 'root -> edit -> delete', + 'root -> edit -> separator-2-in-edit', + 'root -> edit -> selectAll', + 'root -> view', + 'root -> view -> navigate-to-catalog', + 'root -> view -> open-command-palette', + 'root -> view -> separator-1-for-view', + 'root -> view -> go-back', + 'root -> view -> go-forward', + 'root -> view -> reload', + 'root -> view -> toggle-dev-tools', + 'root -> view -> separator-2-for-view', + 'root -> view -> reset-zoom', + 'root -> view -> zoom-in', + 'root -> view -> zoom-out', + 'root -> view -> separator-3-for-view', + 'root -> view -> toggle-full-screen', + 'root -> help', + 'root -> help -> navigate-to-welcome', + 'root -> help -> open-documentation', + 'root -> help -> open-support' +]" `; exports[`application-menu, given platform is 'linux' given enough time passes populates application menu 1`] = ` -Array [ - "root", - "root -> file", - "root -> file -> add-cluster", - "root -> file -> navigate-to-preferences", - "root -> file -> navigate-to-extensions", - "root -> file -> quit", - "root -> edit", - "root -> edit -> undo", - "root -> edit -> redo", - "root -> edit -> separator-1-in-edit", - "root -> edit -> cut", - "root -> edit -> copy", - "root -> edit -> paste", - "root -> edit -> delete", - "root -> edit -> separator-2-in-edit", - "root -> edit -> selectAll", - "root -> view", - "root -> view -> navigate-to-catalog", - "root -> view -> open-command-palette", - "root -> view -> separator-1-for-view", - "root -> view -> go-back", - "root -> view -> go-forward", - "root -> view -> reload", - "root -> view -> toggle-dev-tools", - "root -> view -> separator-2-for-view", - "root -> view -> reset-zoom", - "root -> view -> zoom-in", - "root -> view -> zoom-out", - "root -> view -> separator-3-for-view", - "root -> view -> toggle-full-screen", - "root -> help", - "root -> help -> navigate-to-welcome", - "root -> help -> open-documentation", - "root -> help -> open-support", - "root -> help -> about", -] +"[ + 'root', + 'root -> file', + 'root -> file -> add-cluster', + 'root -> file -> navigate-to-preferences', + 'root -> file -> navigate-to-extensions', + 'root -> file -> quit', + 'root -> edit', + 'root -> edit -> undo', + 'root -> edit -> redo', + 'root -> edit -> separator-1-in-edit', + 'root -> edit -> cut', + 'root -> edit -> copy', + 'root -> edit -> paste', + 'root -> edit -> delete', + 'root -> edit -> separator-2-in-edit', + 'root -> edit -> selectAll', + 'root -> view', + 'root -> view -> navigate-to-catalog', + 'root -> view -> open-command-palette', + 'root -> view -> separator-1-for-view', + 'root -> view -> go-back', + 'root -> view -> go-forward', + 'root -> view -> reload', + 'root -> view -> toggle-dev-tools', + 'root -> view -> separator-2-for-view', + 'root -> view -> reset-zoom', + 'root -> view -> zoom-in', + 'root -> view -> zoom-out', + 'root -> view -> separator-3-for-view', + 'root -> view -> toggle-full-screen', + 'root -> help', + 'root -> help -> navigate-to-welcome', + 'root -> help -> open-documentation', + 'root -> help -> open-support', + 'root -> help -> about' +]" `; exports[`application-menu, given platform is 'win32' given enough time passes populates application menu 1`] = ` -Array [ - "root", - "root -> file", - "root -> file -> add-cluster", - "root -> file -> navigate-to-preferences", - "root -> file -> navigate-to-extensions", - "root -> file -> quit", - "root -> edit", - "root -> edit -> undo", - "root -> edit -> redo", - "root -> edit -> separator-1-in-edit", - "root -> edit -> cut", - "root -> edit -> copy", - "root -> edit -> paste", - "root -> edit -> delete", - "root -> edit -> separator-2-in-edit", - "root -> edit -> selectAll", - "root -> view", - "root -> view -> navigate-to-catalog", - "root -> view -> open-command-palette", - "root -> view -> separator-1-for-view", - "root -> view -> go-back", - "root -> view -> go-forward", - "root -> view -> reload", - "root -> view -> toggle-dev-tools", - "root -> view -> separator-2-for-view", - "root -> view -> reset-zoom", - "root -> view -> zoom-in", - "root -> view -> zoom-out", - "root -> view -> separator-3-for-view", - "root -> view -> toggle-full-screen", - "root -> help", - "root -> help -> navigate-to-welcome", - "root -> help -> open-documentation", - "root -> help -> open-support", - "root -> help -> about", -] +"[ + 'root', + 'root -> file', + 'root -> file -> add-cluster', + 'root -> file -> navigate-to-preferences', + 'root -> file -> navigate-to-extensions', + 'root -> file -> quit', + 'root -> edit', + 'root -> edit -> undo', + 'root -> edit -> redo', + 'root -> edit -> separator-1-in-edit', + 'root -> edit -> cut', + 'root -> edit -> copy', + 'root -> edit -> paste', + 'root -> edit -> delete', + 'root -> edit -> separator-2-in-edit', + 'root -> edit -> selectAll', + 'root -> view', + 'root -> view -> navigate-to-catalog', + 'root -> view -> open-command-palette', + 'root -> view -> separator-1-for-view', + 'root -> view -> go-back', + 'root -> view -> go-forward', + 'root -> view -> reload', + 'root -> view -> toggle-dev-tools', + 'root -> view -> separator-2-for-view', + 'root -> view -> reset-zoom', + 'root -> view -> zoom-in', + 'root -> view -> zoom-out', + 'root -> view -> separator-3-for-view', + 'root -> view -> toggle-full-screen', + 'root -> help', + 'root -> help -> navigate-to-welcome', + 'root -> help -> open-documentation', + 'root -> help -> open-support', + 'root -> help -> about' +]" `; diff --git a/packages/core/src/features/application-menu/application-menu.test.ts b/packages/core/src/features/application-menu/application-menu.test.ts index 84dddb2531..7f17c9eceb 100644 --- a/packages/core/src/features/application-menu/application-menu.test.ts +++ b/packages/core/src/features/application-menu/application-menu.test.ts @@ -8,6 +8,7 @@ import populateApplicationMenuInjectable from "./main/populate-application-menu. import { advanceFakeTime, testUsingFakeTime } from "../../test-utils/use-fake-time"; import { getCompositePaths } from "../../common/utils/composite/get-composite-paths/get-composite-paths"; import platformInjectable, { allPlatforms } from "../../common/vars/platform.injectable"; +import { inspect } from "util"; describe.each(allPlatforms)("application-menu, given platform is '%s'", (platform) => { let builder: ApplicationBuilder; @@ -53,7 +54,14 @@ describe.each(allPlatforms)("application-menu, given platform is '%s'", (platfor }); it("populates application menu", () => { - expect(applicationMenuPaths.map(x => x.join(" -> "))).toMatchSnapshot(); + expect(inspect(applicationMenuPaths.map(x => x.join(" -> ")), { + compact: false, + breakLength: Infinity, + colors: false, + depth: Infinity, + maxArrayLength: Infinity, + maxStringLength: Infinity, + })).toMatchSnapshot(); }); }); }); diff --git a/packages/core/src/features/application-update/__snapshots__/installing-update.test.ts.snap b/packages/core/src/features/application-update/__snapshots__/installing-update.test.ts.snap index cd0d55e25f..30b66e8970 100644 --- a/packages/core/src/features/application-update/__snapshots__/installing-update.test.ts.snap +++ b/packages/core/src/features/application-update/__snapshots__/installing-update.test.ts.snap @@ -231,7 +231,7 @@ exports[`installing update when started renders 1`] = ` class="HotbarSelector" > { let builder: ApplicationBuilder; let rendered: RenderResult; let windowDi: DiContainer; - let cluster: Cluster; let clusterEntity: KubernetesCluster; let localClusterEntity: KubernetesCluster; let otherEntity: WebLink; @@ -26,20 +25,7 @@ describe("opening catalog entity details panel", () => { beforeEach(async () => { builder = getApplicationBuilder(); - builder.beforeWindowStart(({ windowDi }) => { - // TODO: remove once ClusterStore can be used without overriding it - windowDi.override(getClusterByIdInjectable, () => (clusterId) => { - if (clusterId === cluster?.id) { - return cluster; - } - - return undefined; - }); - }); - - testUsingFakeTime(); - - builder.afterWindowStart(({ windowDi }) => { + builder.afterWindowStart(async ({ windowDi }) => { clusterEntity = new KubernetesCluster({ metadata: { labels: {}, @@ -82,12 +68,33 @@ describe("opening catalog entity details panel", () => { phase: "available", }, }); - cluster = new Cluster({ - contextName: clusterEntity.spec.kubeconfigContext, + + const writeJsonFile = windowDi.inject(writeJsonFileInjectable); + const addCluster = windowDi.inject(addClusterInjectable); + + await writeJsonFile(clusterEntity.spec.kubeconfigPath, { + contexts: [{ + name: clusterEntity.spec.kubeconfigContext, + context: { + cluster: "some-cluster", + user: "some-user", + }, + }], + clusters: [{ + name: "some-cluster", + cluster: { + server: "https://localhost:9999", + }, + }], + users: [{ + name: "some-user", + }], + }); + + addCluster({ id: clusterEntity.getId(), kubeConfigPath: clusterEntity.spec.kubeconfigPath, - }, { - clusterServerUrl: "https://localhost:9999", + contextName: clusterEntity.spec.kubeconfigContext, }); // TODO: replace with proper entity source once syncing entities between main and windows is injectable diff --git a/packages/core/src/features/cluster/activation/main/request-activation.injectable.ts b/packages/core/src/features/cluster/activation/main/request-activation.injectable.ts index c498040fc0..0bc51a6f6f 100644 --- a/packages/core/src/features/cluster/activation/main/request-activation.injectable.ts +++ b/packages/core/src/features/cluster/activation/main/request-activation.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable"; +import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable"; import { requestClusterActivationInjectionToken } from "../common/request-token"; const requestClusterActivationInjectable = getInjectable({ diff --git a/packages/core/src/features/cluster/activation/main/request-deactivation.injectable.ts b/packages/core/src/features/cluster/activation/main/request-deactivation.injectable.ts index 05fc00911c..45585419dc 100644 --- a/packages/core/src/features/cluster/activation/main/request-deactivation.injectable.ts +++ b/packages/core/src/features/cluster/activation/main/request-deactivation.injectable.ts @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; import clusterFramesInjectable from "../../../../common/cluster-frames.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; import clusterConnectionInjectable from "../../../../main/cluster/cluster-connection.injectable"; +import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable"; import { requestClusterDeactivationInjectionToken } from "../common/request-token"; const requestClusterDeactivationInjectable = getInjectable({ diff --git a/packages/core/src/features/cluster/delete-dialog/__snapshots__/delete-cluster-dialog.test.tsx.snap b/packages/core/src/features/cluster/delete-dialog/__snapshots__/delete-cluster-dialog.test.tsx.snap index 980e1a5461..41695a2f1f 100644 --- a/packages/core/src/features/cluster/delete-dialog/__snapshots__/delete-cluster-dialog.test.tsx.snap +++ b/packages/core/src/features/cluster/delete-dialog/__snapshots__/delete-cluster-dialog.test.tsx.snap @@ -542,7 +542,7 @@ exports[`Deleting a cluster when an internal kubeconfig cluster is used when the class="HotbarSelector" > { const emitAppEvent = di.inject(emitAppEventInjectable); - const clusterStore = di.inject(clusterStoreInjectable); const clusterFrames = di.inject(clusterFramesInjectable); const joinPaths = di.inject(joinPathsInjectable); const directoryForLensLocalStorage = di.inject(directoryForLensLocalStorageInjectable); const deleteFile = di.inject(removePathInjectable); + const clustersState = di.inject(clustersStateInjectable); return async (clusterId) => { emitAppEvent({ name: "cluster", action: "remove" }); - const cluster = clusterStore.getById(clusterId); + const cluster = clustersState.get(clusterId); if (!cluster) { return; @@ -37,9 +37,7 @@ const deleteClusterChannelListenerInjectable = getRequestChannelListenerInjectab clusterConnection.disconnect(); clusterFrames.delete(cluster.id); - - // Remove from the cluster store as well, this should clear any old settings - clusterStore.clusters.delete(cluster.id); + clustersState.delete(cluster.id); // remove the local storage file const localStorageFilePath = joinPaths(directoryForLensLocalStorage, `${cluster.id}.json`); diff --git a/packages/core/src/features/cluster/refresh-accessibility-technical.test.ts b/packages/core/src/features/cluster/refresh-accessibility-technical.test.ts index 73ff6151b9..4851cca084 100644 --- a/packages/core/src/features/cluster/refresh-accessibility-technical.test.ts +++ b/packages/core/src/features/cluster/refresh-accessibility-technical.test.ts @@ -6,7 +6,6 @@ import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; import type { AuthorizationV1Api, CoreV1Api, V1APIGroupList, V1APIVersions, V1NamespaceList, V1SelfSubjectAccessReview, V1SelfSubjectRulesReview } from "@kubernetes/client-node"; -import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable"; import type { Cluster } from "../../common/cluster/cluster"; import createAuthorizationApiInjectable from "../../common/cluster/create-authorization-api.injectable"; import writeJsonFileInjectable from "../../common/fs/write-json-file.injectable"; @@ -26,6 +25,7 @@ import type { KubeAuthProxy } from "../../main/kube-auth-proxy/create-kube-auth- import createKubeAuthProxyInjectable from "../../main/kube-auth-proxy/create-kube-auth-proxy.injectable"; import type { Mocked } from "../../test-utils/mock-interface"; import { flushPromises } from "@k8slens/test-utils"; +import addClusterInjectable from "./storage/common/add.injectable"; describe("Refresh Cluster Accessibility Technical Tests", () => { let builder: ApplicationBuilder; @@ -79,7 +79,7 @@ describe("Refresh Cluster Accessibility Technical Tests", () => { beforeEach(async () => { const mainDi = builder.mainDi; - const clusterStore = mainDi.inject(clusterStoreInjectable); + const addCluster = mainDi.inject(addClusterInjectable); const writeJsonFile = mainDi.inject(writeJsonFileInjectable); await writeJsonFile("/some-kube-config-path", { @@ -103,13 +103,11 @@ describe("Refresh Cluster Accessibility Technical Tests", () => { }], }); - clusterStore.addCluster({ + cluster = addCluster({ contextName: "some-cluster-context", id: "some-cluster-id", kubeConfigPath: "/some-kube-config-path", }); - - cluster = clusterStore.getById("some-cluster-id") ?? (() => { throw new Error("missing cluster"); })(); clusterConnection = mainDi.inject(clusterConnectionInjectable, cluster); refreshPromise = clusterConnection.refreshAccessibilityAndMetadata(); }); diff --git a/packages/core/src/common/cluster-store/allowed-resources-injection-token.ts b/packages/core/src/features/cluster/showing-kube-resources/common/allowed-resources-injection-token.ts similarity index 84% rename from packages/core/src/common/cluster-store/allowed-resources-injection-token.ts rename to packages/core/src/features/cluster/showing-kube-resources/common/allowed-resources-injection-token.ts index 5b71038d04..317ed5a0ec 100644 --- a/packages/core/src/common/cluster-store/allowed-resources-injection-token.ts +++ b/packages/core/src/features/cluster/showing-kube-resources/common/allowed-resources-injection-token.ts @@ -5,7 +5,7 @@ import { getInjectionToken } from "@ogre-tools/injectable"; import type { IComputedValue } from "mobx"; -import type { KubeApiResourceDescriptor } from "../rbac"; +import type { KubeApiResourceDescriptor } from "../../../../common/rbac"; export const shouldShowResourceInjectionToken = getInjectionToken, KubeApiResourceDescriptor>({ id: "should-show-resource", diff --git a/packages/core/src/features/cluster/state-sync/main/handle-initial.injectable.ts b/packages/core/src/features/cluster/state-sync/main/handle-initial.injectable.ts index db65024973..75a68273a0 100644 --- a/packages/core/src/features/cluster/state-sync/main/handle-initial.injectable.ts +++ b/packages/core/src/features/cluster/state-sync/main/handle-initial.injectable.ts @@ -2,17 +2,17 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import { getRequestChannelListenerInjectable } from "@k8slens/messaging"; +import clustersInjectable from "../../storage/common/clusters.injectable"; import { initialClusterStatesChannel } from "../common/channels"; const handleInitialClusterStateSyncInjectable = getRequestChannelListenerInjectable({ id: "handle-initial-cluster-state-sync", channel: initialClusterStatesChannel, getHandler: (di) => { - const clusterStore = di.inject(clusterStoreInjectable); + const clusters = di.inject(clustersInjectable); - return () => clusterStore.clustersList.map(cluster => ({ + return () => clusters.get().map(cluster => ({ clusterId: cluster.id, state: cluster.getState(), })); diff --git a/packages/core/src/features/cluster/state-sync/main/setup-sync.injectable.ts b/packages/core/src/features/cluster/state-sync/main/setup-sync.injectable.ts index 348450958d..927c93c148 100644 --- a/packages/core/src/features/cluster/state-sync/main/setup-sync.injectable.ts +++ b/packages/core/src/features/cluster/state-sync/main/setup-sync.injectable.ts @@ -5,22 +5,22 @@ import { getInjectable } from "@ogre-tools/injectable"; import { isEqual } from "lodash"; import { autorun } from "mobx"; -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import type { ClusterId, ClusterState } from "../../../../common/cluster-types"; import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; -import initClusterStoreInjectable from "../../store/main/init.injectable"; +import initClusterStoreInjectable from "../../storage/main/init.injectable"; import emitClusterStateUpdateInjectable from "./emit-update.injectable"; +import clustersInjectable from "../../storage/common/clusters.injectable"; const setupClusterStateBroadcastingInjectable = getInjectable({ id: "setup-cluster-state-broadcasting", instantiate: (di) => ({ run: () => { const emitClusterStateUpdate = di.inject(emitClusterStateUpdateInjectable); - const clusterStore = di.inject(clusterStoreInjectable); + const clusters = di.inject(clustersInjectable); const prevStates = new Map(); autorun(() => { - for (const cluster of clusterStore.clusters.values()) { + for (const cluster of clusters.get()) { const prevState = prevStates.get(cluster.id); const curState = cluster.getState(); diff --git a/packages/core/src/features/cluster/state-sync/renderer/listener.injectable.ts b/packages/core/src/features/cluster/state-sync/renderer/listener.injectable.ts index a3778d096e..ec329f2c9d 100644 --- a/packages/core/src/features/cluster/state-sync/renderer/listener.injectable.ts +++ b/packages/core/src/features/cluster/state-sync/renderer/listener.injectable.ts @@ -2,8 +2,8 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; import { getMessageChannelListenerInjectable } from "@k8slens/messaging"; +import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable"; import { clusterStateSyncChannel } from "../common/channels"; const clusterStateListenerInjectable = getMessageChannelListenerInjectable({ diff --git a/packages/core/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts b/packages/core/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts index da43ace234..43e1025f0a 100644 --- a/packages/core/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts +++ b/packages/core/src/features/cluster/state-sync/renderer/setup-sync.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; -import initClusterStoreInjectable from "../../store/renderer/init.injectable"; +import getClusterByIdInjectable from "../../storage/common/get-by-id.injectable"; +import initClusterStoreInjectable from "../../storage/renderer/init.injectable"; import requestInitialClusterStatesInjectable from "./request-initial.injectable"; const setupClusterStateSyncInjectable = getInjectable({ diff --git a/packages/core/src/common/__tests__/cluster-store.test.ts b/packages/core/src/features/cluster/storage/cluster-storage.test.ts similarity index 70% rename from packages/core/src/common/__tests__/cluster-store.test.ts rename to packages/core/src/features/cluster/storage/cluster-storage.test.ts index 5b052548a5..f11ae3a4c5 100644 --- a/packages/core/src/common/__tests__/cluster-store.test.ts +++ b/packages/core/src/features/cluster/storage/cluster-storage.test.ts @@ -3,29 +3,35 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterStore } from "../cluster-store/cluster-store"; -import type { GetCustomKubeConfigFilePath } from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; -import getCustomKubeConfigFilePathInjectable from "../app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; -import clusterStoreInjectable from "../cluster-store/cluster-store.injectable"; +import type { GetCustomKubeConfigFilePath } from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; +import getCustomKubeConfigFilePathInjectable from "../../../common/app-paths/get-custom-kube-config-directory/get-custom-kube-config-directory.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; import assert from "assert"; -import directoryForTempInjectable from "../app-paths/directory-for-temp/directory-for-temp.injectable"; -import kubectlBinaryNameInjectable from "../../main/kubectl/binary-name.injectable"; -import kubectlDownloadingNormalizedArchInjectable from "../../main/kubectl/normalized-arch.injectable"; -import normalizedPlatformInjectable from "../vars/normalized-platform.injectable"; -import storeMigrationVersionInjectable from "../vars/store-migration-version.injectable"; -import type { WriteJsonSync } from "../fs/write-json-sync.injectable"; -import writeJsonSyncInjectable from "../fs/write-json-sync.injectable"; -import type { ReadFileSync } from "../fs/read-file-sync.injectable"; -import readFileSyncInjectable from "../fs/read-file-sync.injectable"; +import directoryForTempInjectable from "../../../common/app-paths/directory-for-temp/directory-for-temp.injectable"; +import kubectlBinaryNameInjectable from "../../../main/kubectl/binary-name.injectable"; +import kubectlDownloadingNormalizedArchInjectable from "../../../main/kubectl/normalized-arch.injectable"; +import normalizedPlatformInjectable from "../../../common/vars/normalized-platform.injectable"; +import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; +import type { WriteJsonSync } from "../../../common/fs/write-json-sync.injectable"; +import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable"; +import type { ReadFileSync } from "../../../common/fs/read-file-sync.injectable"; +import readFileSyncInjectable from "../../../common/fs/read-file-sync.injectable"; import { readFileSync } from "fs"; -import type { WriteFileSync } from "../fs/write-file-sync.injectable"; -import writeFileSyncInjectable from "../fs/write-file-sync.injectable"; -import type { WriteBufferSync } from "../fs/write-buffer-sync.injectable"; -import writeBufferSyncInjectable from "../fs/write-buffer-sync.injectable"; -import { Cluster } from "../cluster/cluster"; +import type { WriteFileSync } from "../../../common/fs/write-file-sync.injectable"; +import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable"; +import type { WriteBufferSync } from "../../../common/fs/write-buffer-sync.injectable"; +import writeBufferSyncInjectable from "../../../common/fs/write-buffer-sync.injectable"; +import { Cluster } from "../../../common/cluster/cluster"; +import clustersPersistentStorageInjectable from "./common/storage.injectable"; +import type { PersistentStorage } from "../../../common/persistent-storage/create.injectable"; +import type { AddCluster } from "./common/add.injectable"; +import addClusterInjectable from "./common/add.injectable"; +import type { GetClusterById } from "./common/get-by-id.injectable"; +import getClusterByIdInjectable from "./common/get-by-id.injectable"; +import type { IComputedValue } from "mobx"; +import clustersInjectable from "./common/clusters.injectable"; // NOTE: this is intended to read the actual file system const testDataIcon = readFileSync("test-data/cluster-store-migration-icon.png"); @@ -54,15 +60,18 @@ users: token: kubeconfig-user-q4lm4:xxxyyyy `; -describe("cluster-store", () => { +describe("cluster storage technical tests", () => { let di: DiContainer; - let clusterStore: ClusterStore; + let clustersPersistentStorage: PersistentStorage; let writeJsonSync: WriteJsonSync; let writeFileSync: WriteFileSync; let writeBufferSync: WriteBufferSync; let readFileSync: ReadFileSync; let getCustomKubeConfigFilePath: GetCustomKubeConfigFilePath; let writeFileSyncAndReturnPath: (filePath: string, contents: string) => string; + let addCluster: AddCluster; + let getClusterById: GetClusterById; + let clusters: IComputedValue; beforeEach(async () => { di = getDiForUnitTesting(); @@ -76,6 +85,9 @@ describe("cluster-store", () => { writeFileSync = di.inject(writeFileSyncInjectable); writeBufferSync = di.inject(writeBufferSyncInjectable); readFileSync = di.inject(readFileSyncInjectable); + addCluster = di.inject(addClusterInjectable); + getClusterById = di.inject(getClusterByIdInjectable); + clusters = di.inject(clustersInjectable); writeFileSyncAndReturnPath = (filePath, contents) => (writeFileSync(filePath, contents), filePath); }); @@ -84,8 +96,8 @@ describe("cluster-store", () => { getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable); writeJsonSync("/some-directory-for-user-data/lens-cluster-store.json", {}); - clusterStore = di.inject(clusterStoreInjectable); - clusterStore.load(); + clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable); + clustersPersistentStorage.loadAndStartSyncing(); }); describe("with foo cluster added", () => { @@ -106,11 +118,11 @@ describe("cluster-store", () => { clusterServerUrl, }); - clusterStore.addCluster(cluster); + addCluster(cluster); }); it("adds new cluster to store", async () => { - const storedCluster = clusterStore.getById("foo"); + const storedCluster = getClusterById("foo"); assert(storedCluster); @@ -124,9 +136,7 @@ describe("cluster-store", () => { describe("with prod and dev clusters added", () => { beforeEach(() => { - const store = clusterStore; - - store.addCluster({ + addCluster({ id: "prod", contextName: "foo", preferences: { @@ -137,7 +147,7 @@ describe("cluster-store", () => { kubeconfig, ), }); - store.addCluster({ + addCluster({ id: "dev", contextName: "foo2", preferences: { @@ -151,8 +161,7 @@ describe("cluster-store", () => { }); it("check if store can contain multiple clusters", () => { - expect(clusterStore.hasClusters()).toBeTruthy(); - expect(clusterStore.clusters.size).toBe(2); + expect(clusters.get().length).toBe(2); }); it("check if cluster's kubeconfig file saved", () => { @@ -199,11 +208,11 @@ describe("cluster-store", () => { getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable); - clusterStore = di.inject(clusterStoreInjectable); - clusterStore.load(); + clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable); + clustersPersistentStorage.loadAndStartSyncing(); }); it("allows to retrieve a cluster", () => { - const storedCluster = clusterStore.getById("cluster1"); + const storedCluster = getClusterById("cluster1"); assert(storedCluster); @@ -212,7 +221,7 @@ describe("cluster-store", () => { }); it("allows getting all of the clusters", async () => { - const storedClusters = clusterStore.clustersList; + const storedClusters = clusters.get(); expect(storedClusters.length).toBe(3); expect(storedClusters[0].id).toBe("cluster1"); @@ -253,12 +262,12 @@ describe("cluster-store", () => { getCustomKubeConfigFilePath = di.inject(getCustomKubeConfigFilePathInjectable); - clusterStore = di.inject(clusterStoreInjectable); - clusterStore.load(); + clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable); + clustersPersistentStorage.loadAndStartSyncing(); }); it("does not enable clusters with invalid kubeconfig", () => { - const storedClusters = clusterStore.clustersList; + const storedClusters = clusters.get(); expect(storedClusters.length).toBe(1); }); @@ -290,18 +299,18 @@ describe("cluster-store", () => { writeBufferSync("/some-directory-for-user-data/icon_path", testDataIcon); - clusterStore = di.inject(clusterStoreInjectable); - clusterStore.load(); + clustersPersistentStorage = di.inject(clustersPersistentStorageInjectable); + clustersPersistentStorage.loadAndStartSyncing(); }); it("migrates to modern format with kubeconfig in a file", async () => { - const configPath = clusterStore.clustersList[0].kubeConfigPath.get(); + const configPath = clusters.get()[0].kubeConfigPath.get(); expect(readFileSync(configPath)).toBe(minimalValidKubeConfig); }); it("migrates to modern format with icon not in file", async () => { - expect(clusterStore.clustersList[0].preferences.icon).toMatch(/data:;base64,/); + expect(clusters.get()[0].preferences.icon).toMatch(/data:;base64,/); }); }); }); diff --git a/packages/core/src/features/cluster/storage/common/add.injectable.ts b/packages/core/src/features/cluster/storage/common/add.injectable.ts new file mode 100644 index 0000000000..051aa6d23b --- /dev/null +++ b/packages/core/src/features/cluster/storage/common/add.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import emitAppEventInjectable from "../../../../common/app-event-bus/emit-event.injectable"; +import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; +import type { ClusterModel } from "../../../../common/cluster-types"; +import { Cluster } from "../../../../common/cluster/cluster"; +import clustersStateInjectable from "./state.injectable"; + +export type AddCluster = (clusterOrModel: ClusterModel | Cluster) => Cluster; + +const addClusterInjectable = getInjectable({ + id: "add-cluster", + instantiate: (di): AddCluster => { + const clustersState = di.inject(clustersStateInjectable); + const emitAppEvent = di.inject(emitAppEventInjectable); + const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable); + + return action((clusterOrModel) => { + emitAppEvent({ name: "cluster", action: "add" }); + + const cluster = clusterOrModel instanceof Cluster + ? clusterOrModel + : new Cluster( + clusterOrModel, + readClusterConfigSync(clusterOrModel), + ); + + clustersState.set(cluster.id, cluster); + + return cluster; + }); + }, +}); + +export default addClusterInjectable; diff --git a/packages/core/src/features/cluster/storage/common/clusters.injectable.ts b/packages/core/src/features/cluster/storage/common/clusters.injectable.ts new file mode 100644 index 0000000000..d251cf784f --- /dev/null +++ b/packages/core/src/features/cluster/storage/common/clusters.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import clustersStateInjectable from "./state.injectable"; + +const clustersInjectable = getInjectable({ + id: "clusters", + instantiate: (di) => { + const clustersState = di.inject(clustersStateInjectable); + + return computed(() => [...clustersState.values()]); + }, +}); + +export default clustersInjectable; diff --git a/packages/core/src/common/cluster-store/get-by-id.injectable.ts b/packages/core/src/features/cluster/storage/common/get-by-id.injectable.ts similarity index 59% rename from packages/core/src/common/cluster-store/get-by-id.injectable.ts rename to packages/core/src/features/cluster/storage/common/get-by-id.injectable.ts index 534bdb5e76..2e3128f4d6 100644 --- a/packages/core/src/common/cluster-store/get-by-id.injectable.ts +++ b/packages/core/src/features/cluster/storage/common/get-by-id.injectable.ts @@ -3,18 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { ClusterId } from "../cluster-types"; -import type { Cluster } from "../cluster/cluster"; -import clusterStoreInjectable from "./cluster-store.injectable"; +import type { ClusterId } from "../../../../common/cluster-types"; +import type { Cluster } from "../../../../common/cluster/cluster"; +import clustersStateInjectable from "./state.injectable"; export type GetClusterById = (id: ClusterId) => Cluster | undefined; const getClusterByIdInjectable = getInjectable({ id: "get-cluster-by-id", instantiate: (di): GetClusterById => { - const store = di.inject(clusterStoreInjectable); + const clustersState = di.inject(clustersStateInjectable); - return (id) => store.getById(id); + return (id) => clustersState.get(id); }, }); diff --git a/packages/core/src/common/cluster-store/migration-token.ts b/packages/core/src/features/cluster/storage/common/migration-token.ts similarity index 76% rename from packages/core/src/common/cluster-store/migration-token.ts rename to packages/core/src/features/cluster/storage/common/migration-token.ts index 86489509a2..8fa8064cf6 100644 --- a/packages/core/src/common/cluster-store/migration-token.ts +++ b/packages/core/src/features/cluster/storage/common/migration-token.ts @@ -4,7 +4,7 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { MigrationDeclaration } from "../base-store/migrations.injectable"; +import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable"; export const clusterStoreMigrationInjectionToken = getInjectionToken({ id: "cluster-store-migration", diff --git a/packages/core/src/common/cluster-store/read-cluster-config.injectable.ts b/packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts similarity index 77% rename from packages/core/src/common/cluster-store/read-cluster-config.injectable.ts rename to packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts index 9fea013b16..9b3be7733e 100644 --- a/packages/core/src/common/cluster-store/read-cluster-config.injectable.ts +++ b/packages/core/src/features/cluster/storage/common/read-cluster-config.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type { ClusterConfigData, ClusterModel } from "../cluster-types"; -import readFileSyncInjectable from "../fs/read-file-sync.injectable"; -import { loadConfigFromString, validateKubeConfig } from "../kube-helpers"; +import type { ClusterConfigData, ClusterModel } from "../../../../common/cluster-types"; +import readFileSyncInjectable from "../../../../common/fs/read-file-sync.injectable"; +import { loadConfigFromString, validateKubeConfig } from "../../../../common/kube-helpers"; export type ReadClusterConfigSync = (model: ClusterModel) => ClusterConfigData; diff --git a/packages/core/src/features/cluster/storage/common/state.injectable.ts b/packages/core/src/features/cluster/storage/common/state.injectable.ts new file mode 100644 index 0000000000..a3234dcb62 --- /dev/null +++ b/packages/core/src/features/cluster/storage/common/state.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { ClusterId } from "../../../../common/cluster-types"; +import type { Cluster } from "../../../../common/cluster/cluster"; + +const clustersStateInjectable = getInjectable({ + id: "clusters-state", + instantiate: () => observable.map(), +}); + +export default clustersStateInjectable; diff --git a/packages/core/src/features/cluster/storage/common/storage.injectable.ts b/packages/core/src/features/cluster/storage/common/storage.injectable.ts new file mode 100644 index 0000000000..eaac8376cf --- /dev/null +++ b/packages/core/src/features/cluster/storage/common/storage.injectable.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { comparer, action } from "mobx"; +import { clusterStoreMigrationInjectionToken } from "./migration-token"; +import readClusterConfigSyncInjectable from "./read-cluster-config.injectable"; +import type { ClusterId, ClusterModel } from "../../../../common/cluster-types"; +import { Cluster } from "../../../../common/cluster/cluster"; +import loggerInjectable from "../../../../common/logger.injectable"; +import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable"; +import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable"; +import clustersStateInjectable from "./state.injectable"; + +export interface ClusterStoreModel { + clusters?: ClusterModel[]; +} + +const clustersPersistentStorageInjectable = getInjectable({ + id: "clusters-persistent-storage", + instantiate: (di) => { + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const readClusterConfigSync = di.inject(readClusterConfigSyncInjectable); + const clustersState = di.inject(clustersStateInjectable); + const logger = di.inject(loggerInjectable); + + return createPersistentStorage({ + configName: "lens-cluster-store", + accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + syncOptions: { + equals: comparer.structural, + }, + projectVersion: di.inject(storeMigrationVersionInjectable), + migrations: di.inject(persistentStorageMigrationsInjectable, clusterStoreMigrationInjectionToken), + fromStore: action(({ clusters = [] }) => { + const currentClusters = new Map(clustersState); + const newClusters = new Map(); + + // update new clusters + for (const clusterModel of clusters) { + try { + let cluster = currentClusters.get(clusterModel.id); + + if (cluster) { + cluster.updateModel(clusterModel); + } else { + cluster = new Cluster( + clusterModel, + readClusterConfigSync(clusterModel), + ); + } + newClusters.set(clusterModel.id, cluster); + } catch (error) { + logger.warn(`[CLUSTER-STORE]: Failed to update/create a cluster: ${error}`); + } + } + + clustersState.replace(newClusters); + }), + toJSON: () => ({ + clusters: iter.chain(clustersState.values()) + .map(cluster => cluster.toJSON()) + .toArray(), + }), + }); + }, +}); + +export default clustersPersistentStorageInjectable; diff --git a/packages/core/src/features/cluster/store/main/init.injectable.ts b/packages/core/src/features/cluster/storage/main/init.injectable.ts similarity index 60% rename from packages/core/src/features/cluster/store/main/init.injectable.ts rename to packages/core/src/features/cluster/storage/main/init.injectable.ts index c7a6bc1c9a..7a6c906386 100644 --- a/packages/core/src/features/cluster/store/main/init.injectable.ts +++ b/packages/core/src/features/cluster/storage/main/init.injectable.ts @@ -3,19 +3,19 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; -import initUserStoreInjectable from "../../../../main/stores/init-user-store.injectable"; +import clustersPersistentStorageInjectable from "../common/storage.injectable"; +import loadUserPreferencesStorageInjectable from "../../../user-preferences/main/load-storage.injectable"; const initClusterStoreInjectable = getInjectable({ id: "init-cluster-store", instantiate: (di) => ({ run: () => { - const clusterStore = di.inject(clusterStoreInjectable); + const storage = di.inject(clustersPersistentStorageInjectable); - clusterStore.load(); + storage.loadAndStartSyncing(); }, - runAfter: initUserStoreInjectable, + runAfter: loadUserPreferencesStorageInjectable, }), injectionToken: beforeApplicationIsLoadingInjectionToken, }); diff --git a/packages/core/src/features/cluster/store/renderer/init.injectable.ts b/packages/core/src/features/cluster/storage/renderer/init.injectable.ts similarity index 67% rename from packages/core/src/features/cluster/store/renderer/init.injectable.ts rename to packages/core/src/features/cluster/storage/renderer/init.injectable.ts index f937796bae..cdedc3fceb 100644 --- a/packages/core/src/features/cluster/store/renderer/init.injectable.ts +++ b/packages/core/src/features/cluster/storage/renderer/init.injectable.ts @@ -3,17 +3,17 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; -import initUserStoreInjectable from "../../../../renderer/stores/init-user-store.injectable"; +import initUserStoreInjectable from "../../../user-preferences/renderer/load-storage.injectable"; +import clustersPersistentStorageInjectable from "../common/storage.injectable"; const initClusterStoreInjectable = getInjectable({ id: "init-cluster-store", instantiate: (di) => ({ run: () => { - const clusterStore = di.inject(clusterStoreInjectable); + const storage = di.inject(clustersPersistentStorageInjectable); - clusterStore.load(); + storage.loadAndStartSyncing(); }, runAfter: initUserStoreInjectable, }), diff --git a/packages/core/src/features/cluster/visibility-of-sidebar-items.test.tsx b/packages/core/src/features/cluster/visibility-of-sidebar-items.test.tsx index bc96800562..46a8916241 100644 --- a/packages/core/src/features/cluster/visibility-of-sidebar-items.test.tsx +++ b/packages/core/src/features/cluster/visibility-of-sidebar-items.test.tsx @@ -13,7 +13,7 @@ import { frontEndRouteInjectionToken } from "../../common/front-end-routing/fron import type { ApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { getApplicationBuilder } from "../../renderer/components/test-utils/get-application-builder"; import { navigateToRouteInjectionToken } from "../../common/front-end-routing/navigate-to-route-injection-token"; -import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "./showing-kube-resources/common/allowed-resources-injection-token"; describe("cluster - visibility of sidebar items", () => { let builder: ApplicationBuilder; diff --git a/packages/core/src/features/command-pallet/__snapshots__/keyboard-shortcuts.test.tsx.snap b/packages/core/src/features/command-pallet/__snapshots__/keyboard-shortcuts.test.tsx.snap index 0269032531..d21ce102d2 100644 --- a/packages/core/src/features/command-pallet/__snapshots__/keyboard-shortcuts.test.tsx.snap +++ b/packages/core/src/features/command-pallet/__snapshots__/keyboard-shortcuts.test.tsx.snap @@ -322,7 +322,7 @@ exports[`Command Pallet: keyboard shortcut tests when on linux renders 1`] = ` class="HotbarSelector" > { let builder: ApplicationBuilder; @@ -19,23 +19,11 @@ describe("Showing correct entity settings", () => { let clusterEntity: KubernetesCluster; let localClusterEntity: KubernetesCluster; let otherEntity: WebLink; - let cluster: Cluster; beforeEach(async () => { builder = getApplicationBuilder(); - builder.beforeWindowStart(({ windowDi }) => { - // TODO: remove once ClusterStore can be used without overriding it - windowDi.override(getClusterByIdInjectable, () => (clusterId) => { - if (clusterId === cluster.id) { - return cluster; - } - - return undefined; - }); - }); - - builder.afterWindowStart(({ windowDi }) => { + builder.afterWindowStart(async ({ windowDi }) => { clusterEntity = new KubernetesCluster({ metadata: { labels: {}, @@ -78,14 +66,34 @@ describe("Showing correct entity settings", () => { phase: "available", }, }); - cluster = new Cluster({ - contextName: clusterEntity.spec.kubeconfigContext, - id: clusterEntity.getId(), - kubeConfigPath: clusterEntity.spec.kubeconfigPath, - }, { - clusterServerUrl: "https://localhost:9999", + + const writeJsonFile = windowDi.inject(writeJsonFileInjectable); + const addCluster = windowDi.inject(addClusterInjectable); + + await writeJsonFile(clusterEntity.spec.kubeconfigPath, { + contexts: [{ + name: clusterEntity.spec.kubeconfigContext, + context: { + cluster: "some-cluster", + user: "some-user", + }, + }], + clusters: [{ + name: "some-cluster", + cluster: { + server: "https://localhost:9999", + }, + }], + users: [{ + name: "some-user", + }], }); + addCluster({ + id: clusterEntity.getId(), + kubeConfigPath: clusterEntity.spec.kubeconfigPath, + contextName: clusterEntity.spec.kubeconfigContext, + }); // TODO: replace with proper entity source once syncing entities between main and windows is injectable const catalogEntityRegistry = windowDi.inject(catalogEntityRegistryInjectable); diff --git a/packages/core/src/features/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap b/packages/core/src/features/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap index 53dc28e961..dbaf12b978 100644 --- a/packages/core/src/features/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap +++ b/packages/core/src/features/extensions/__snapshots__/navigation-using-application-menu.test.ts.snap @@ -230,7 +230,7 @@ exports[`extensions - navigation using application menu renders 1`] = ` class="HotbarSelector" > { + const state = di.inject(enabledExtensionsStateInjectable); + + return computed(() => ( + iter.chain(state.values()) + .filter(({ enabled }) => enabled) + .map(({ name }) => name) + .toArray() + )); + }, +}); + +export default enabledExtensionsInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts b/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts new file mode 100644 index 0000000000..bb7f531cb3 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/is-enabled.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +export interface IsEnabledExtensionDescriptor { + readonly id: string; + readonly isBundled: boolean; +} + +export type IsExtensionEnabled = (desc: IsEnabledExtensionDescriptor) => boolean; + +const isExtensionEnabledInjectable = getInjectable({ + id: "is-extension-enabled", + instantiate: (di): IsExtensionEnabled => { + const state = di.inject(enabledExtensionsStateInjectable); + + return ({ id, isBundled }) => isBundled || (state.get(id)?.enabled ?? false); + }, +}); + +export default isExtensionEnabledInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/migrations.ts b/packages/core/src/features/extensions/enabled/common/migrations.ts new file mode 100644 index 0000000000..eef1c4c996 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/migrations.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable"; + +export const enabledExtensionsMigrationDeclarationInjectionToken = getInjectionToken({ + id: "enabled-extensions-migration-declaration", +}); diff --git a/packages/core/src/features/extensions/enabled/common/state.injectable.ts b/packages/core/src/features/extensions/enabled/common/state.injectable.ts new file mode 100644 index 0000000000..6cf87ace88 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/state.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; + +export interface LensExtensionState { + enabled?: boolean; + name: string; +} + +const enabledExtensionsStateInjectable = getInjectable({ + id: "enabled-extensions-state", + instantiate: () => observable.map(), +}); + +export default enabledExtensionsStateInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/storage.injectable.ts b/packages/core/src/features/extensions/enabled/common/storage.injectable.ts new file mode 100644 index 0000000000..47a9930ccf --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/storage.injectable.ts @@ -0,0 +1,39 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import { action, toJS } from "mobx"; +import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable"; +import storageMigrationVersionInjectable from "../../../../common/persistent-storage/storage-migration-version.injectable"; +import { enabledExtensionsMigrationDeclarationInjectionToken } from "./migrations"; +import type { LensExtensionState } from "./state.injectable"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +interface EnabledExtensionsStorageModal { + extensions: [LensExtensionId, LensExtensionState][]; +} + +const enabledExtensionsPersistentStorageInjectable = getInjectable({ + id: "enabled-extensions-persistent-storage", + instantiate: (di) => { + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const state = di.inject(enabledExtensionsStateInjectable); + + return createPersistentStorage({ + configName: "lens-extensions", + fromStore: action(({ extensions = [] }) => { + state.replace(extensions); + }), + toJSON: () => ({ + extensions: [...toJS(state)], + }), + projectVersion: di.inject(storageMigrationVersionInjectable, enabledExtensionsMigrationDeclarationInjectionToken), + migrations: di.inject(persistentStorageMigrationsInjectable, enabledExtensionsMigrationDeclarationInjectionToken), + }); + }, +}); + +export default enabledExtensionsPersistentStorageInjectable; diff --git a/packages/core/src/features/extensions/enabled/common/update-state.injectable.ts b/packages/core/src/features/extensions/enabled/common/update-state.injectable.ts new file mode 100644 index 0000000000..e12c2d32d9 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/common/update-state.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { IObservableMapInitialValues } from "mobx"; +import { action } from "mobx"; +import type { LensExtensionState } from "./state.injectable"; +import enabledExtensionsStateInjectable from "./state.injectable"; + +export type UpdateExtensionsState = (state: IObservableMapInitialValues) => void; + +const updateExtensionsStateInjectable = getInjectable({ + id: "update-extensions-state", + instantiate: (di): UpdateExtensionsState => { + const state = di.inject(enabledExtensionsStateInjectable); + + return action((newState) => state.merge(newState)); + }, +}); + +export default updateExtensionsStateInjectable; diff --git a/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts b/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts new file mode 100644 index 0000000000..e3c301627b --- /dev/null +++ b/packages/core/src/features/extensions/enabled/main/load-storage.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; +import { getInjectable } from "@ogre-tools/injectable"; +import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable"; + +const loadEnabledExtensionsStorageInjectable = getInjectable({ + id: "load-enabled-extensions-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(enabledExtensionsPersistentStorageInjectable); + + storage.loadAndStartSyncing(); + }, + }), + injectionToken: beforeApplicationIsLoadingInjectionToken, +}); + +export default loadEnabledExtensionsStorageInjectable; diff --git a/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts b/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts new file mode 100644 index 0000000000..f658180535 --- /dev/null +++ b/packages/core/src/features/extensions/enabled/main/v6.5.0-migration.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { isObject } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { enabledExtensionsMigrationDeclarationInjectionToken } from "../common/migrations"; + +const enabledExtensionsMigrationV650Injectable = getInjectable({ + id: "enabled-extensions-migration-v650", + instantiate: () => ({ + version: "6.5.0", + run: (store) => { + const extensions = store.get("extensions"); + + if (!isObject(extensions)) { + store.delete("extensions"); + } else { + store.set("extensions", Object.entries(extensions)); + } + }, + }), + injectionToken: enabledExtensionsMigrationDeclarationInjectionToken, +}); + +export default enabledExtensionsMigrationV650Injectable; diff --git a/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts b/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts new file mode 100644 index 0000000000..50be3cd88d --- /dev/null +++ b/packages/core/src/features/extensions/enabled/renderer/load-storage.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; +import enabledExtensionsPersistentStorageInjectable from "../common/storage.injectable"; + +const loadEnabledExtensionsStorageInjectable = getInjectable({ + id: "load-enabled-extensions-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(enabledExtensionsPersistentStorageInjectable); + + storage.loadAndStartSyncing(); + }, + }), + injectionToken: beforeFrameStartsSecondInjectionToken, +}); + +export default loadEnabledExtensionsStorageInjectable; diff --git a/packages/core/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap b/packages/core/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap index 7932686bbf..7de4de3f87 100644 --- a/packages/core/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap +++ b/packages/core/src/features/helm-charts/__snapshots__/add-custom-helm-repository-in-preferences.test.ts.snap @@ -649,7 +649,7 @@ exports[`add custom helm repository in preferences when navigating to preference class="HotbarSelector" > { + const computeHotbarIndex = di.inject(computeHotbarIndexInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + + return computed(() => { + const activeId = activeHotbarId.get(); + + return (activeId && computeHotbarIndex(activeId)) || 0; + }); + }, +}); + +export default activeHotbarIndexInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/active-id.injectable.ts b/packages/core/src/features/hotbar/storage/common/active-id.injectable.ts new file mode 100644 index 0000000000..2514bc834d --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/active-id.injectable.ts @@ -0,0 +1,13 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; + +const activeHotbarIdInjectable = getInjectable({ + id: "active-hotbar-id", + instantiate: () => observable.box(), +}); + +export default activeHotbarIdInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/active.injectable.ts b/packages/core/src/features/hotbar/storage/common/active.injectable.ts new file mode 100644 index 0000000000..433a6f4323 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/active.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import activeHotbarIdInjectable from "./active-id.injectable"; +import hotbarsStateInjectable from "./state.injectable"; + +const activeHotbarInjectable = getInjectable({ + id: "active-hotbar", + instantiate: (di) => { + const state = di.inject(hotbarsStateInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + + return computed(() => { + const id = activeHotbarId.get(); + + return (id && state.get(id)) || undefined; + }); + }, +}); + +export default activeHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/add.injectable.ts b/packages/core/src/features/hotbar/storage/common/add.injectable.ts new file mode 100644 index 0000000000..19cbadc7c6 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/add.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import type { CreateHotbarData, CreateHotbarOptions } from "./types"; +import activeHotbarIdInjectable from "./active-id.injectable"; +import hotbarsStateInjectable from "./state.injectable"; +import createHotbarInjectable from "./create-hotbar.injectable"; + +export type AddHotbar = (data: CreateHotbarData, { setActive }?: CreateHotbarOptions) => void; + +const addHotbarInjectable = getInjectable({ + id: "add-hotbar", + instantiate: (di): AddHotbar => { + const state = di.inject(hotbarsStateInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + const createHotbar = di.inject(createHotbarInjectable); + + return action((data, { setActive = false } = {}) => { + const hotbar = createHotbar(data); + + state.set(hotbar.id, hotbar); + + if (setActive) { + activeHotbarId.set(hotbar.id); + } + }); + }, +}); + +export default addHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/compute-display-index.injectable.ts b/packages/core/src/features/hotbar/storage/common/compute-display-index.injectable.ts new file mode 100644 index 0000000000..c67b75db39 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/compute-display-index.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import computeHotbarIndexInjectable from "./compute-hotbar-index.injectable"; + +export type ComputeDisplayIndex = (hotbarId: string) => string; + +const computeDisplayIndexInjectable = getInjectable({ + id: "compute-display-index", + instantiate: (di): ComputeDisplayIndex => { + const computeHotbarIndex = di.inject(computeHotbarIndexInjectable); + + return (hotbarId) => `${computeHotbarIndex(hotbarId) + 1}`; + }, +}); + +export default computeDisplayIndexInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/compute-display-label.injectable.ts b/packages/core/src/features/hotbar/storage/common/compute-display-label.injectable.ts new file mode 100644 index 0000000000..5900563cb2 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/compute-display-label.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Hotbar } from "./hotbar"; +import computeDisplayIndexInjectable from "./compute-display-index.injectable"; + +export type ComputeHotbarDisplayLabel = (hotbar: Hotbar) => string; + +const computeHotbarDisplayLabelInjectable = getInjectable({ + id: "compute-hotbar-display-label", + instantiate: (di): ComputeHotbarDisplayLabel => { + const computeDisplayIndex = di.inject(computeDisplayIndexInjectable); + + return (hotbar) => `${computeDisplayIndex(hotbar.id)}: ${hotbar.name}`; + }, +}); + +export default computeHotbarDisplayLabelInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/compute-hotbar-index.injectable.ts b/packages/core/src/features/hotbar/storage/common/compute-hotbar-index.injectable.ts new file mode 100644 index 0000000000..b54954c618 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/compute-hotbar-index.injectable.ts @@ -0,0 +1,31 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import hotbarsStateInjectable from "./state.injectable"; + +export type ComputeHotbarIndex = (hotbarId: string) => number; + +const computeHotbarIndexInjectable = getInjectable({ + id: "compute-hotbar-index", + instantiate: (di): ComputeHotbarIndex => { + const state = di.inject(hotbarsStateInjectable); + + return (hotbarId) => { + let i = 0; + + for (const hotbar of state.values()) { + if (hotbar.id === hotbarId) { + return i; + } + + i += 1; + } + + return 0; + }; + }, +}); + +export default computeHotbarIndexInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/create-hotbar.injectable.ts b/packages/core/src/features/hotbar/storage/common/create-hotbar.injectable.ts new file mode 100644 index 0000000000..8d35af255b --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/create-hotbar.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { CreateHotbarData } from "./types"; +import prefixedLoggerInjectable from "../../../../common/logger/prefixed-logger.injectable"; +import type { HotbarDependencies } from "./hotbar"; +import { Hotbar } from "./hotbar"; + +export type CreateHotbar = (data: CreateHotbarData) => Hotbar; + +const createHotbarInjectable = getInjectable({ + id: "create-hotbar", + instantiate: (di): CreateHotbar => { + const deps: HotbarDependencies = { + logger: di.inject(prefixedLoggerInjectable, "HOTBAR"), + }; + + return (data) => new Hotbar(deps, data); + }, +}); + +export default createHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/find-by-name.injectable.ts b/packages/core/src/features/hotbar/storage/common/find-by-name.injectable.ts new file mode 100644 index 0000000000..321bc74b2e --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/find-by-name.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import type { Hotbar } from "./hotbar"; +import hotbarsStateInjectable from "./state.injectable"; + +export type FindHotbarByName = (name: string) => Hotbar | undefined; + +const findHotbarByNameInjectable = getInjectable({ + id: "find-hotbar-by-name", + instantiate: (di): FindHotbarByName => { + const state = di.inject(hotbarsStateInjectable); + + return (name) => iter.find(state.values(), hotbar => hotbar.name.get() === name); + }, +}); + +export default findHotbarByNameInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/get-by-id.injectable.ts b/packages/core/src/features/hotbar/storage/common/get-by-id.injectable.ts new file mode 100644 index 0000000000..00fe43814f --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/get-by-id.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import type { Hotbar } from "./hotbar"; +import hotbarsStateInjectable from "./state.injectable"; + +export type GetHotbarById = (id: string) => Hotbar | undefined; + +const getHotbarByIdInjectable = getInjectable({ + id: "get-hotbar-by-id", + instantiate: (di): GetHotbarById => { + const state = di.inject(hotbarsStateInjectable); + + return (id) => state.get(id); + }, +}); + +export default getHotbarByIdInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/hotbar.ts b/packages/core/src/features/hotbar/storage/common/hotbar.ts new file mode 100644 index 0000000000..efb2e60579 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/hotbar.ts @@ -0,0 +1,175 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { type IObservableValue, type IObservableArray, observable, runInAction, toJS } from "mobx"; +import type { CatalogEntity } from "../../../../common/catalog"; +import { getShortName } from "../../../../common/catalog/helpers"; +import type { HotbarItem, CreateHotbarData } from "./types"; +import { defaultHotbarCells } from "./types"; +import { broadcastMessage } from "../../../../common/ipc"; +import { hotbarTooManyItemsChannel } from "../../../../common/ipc/hotbar"; +import * as uuid from "uuid"; +import type { Logger } from "../../../../common/logger"; +import { tuple } from "@k8slens/utilities"; + +export interface HotbarDependencies { + readonly logger: Logger; +} + +export interface HotbarData { + readonly id: string; + readonly name: string; + readonly items: (HotbarItem | null)[]; +} + +export class Hotbar { + readonly id: string; + readonly name: IObservableValue; + readonly items: IObservableArray; + + constructor(private readonly dependencies: HotbarDependencies, data: CreateHotbarData) { + this.id = data.id ?? uuid.v4(); + this.name = observable.box(data.name); + this.items = observable.array(data.items ?? tuple.filled(defaultHotbarCells, null)); + } + + isFull() { + for (const item of this.items) { + if (!item) { + return false; + } + } + + return true; + } + + hasEntity(entityId: string) { + return this.items.findIndex(item => item?.entity.uid === entityId) >= 0; + } + + private findClosestEmptyIndex(from: number, direction = 1) { + let index = from; + + while (this.items[index] != null) { + index += direction; + } + + return index; + } + + restack(from: number, to: number) { + runInAction(() => { + const source = this.items[from]; + const moveDown = from < to; + + if ( + from < 0 || + to < 0 || + from >= this.items.length || + to >= this.items.length || + isNaN(from) || + isNaN(to) + ) { + throw new Error("Invalid 'from' or 'to' arguments"); + } + + if (from == to) { + return; + } + + this.items.splice(from, 1, null); + + if (this.items[to] == null) { + this.items.splice(to, 1, source); + } else { + // Move cells up or down to closes empty cell + this.items.splice(this.findClosestEmptyIndex(to, moveDown ? -1 : 1), 1); + this.items.splice(to, 0, source); + } + }); + } + + toggleEntity(item: CatalogEntity) { + runInAction(() => { + if (this.hasEntity(item.getId())) { + this.removeEntity(item.getId()); + } else { + this.addEntity(item); + } + }); + } + + removeEntity(uid: string) { + runInAction(() => { + const index = this.items.findIndex((item) => item?.entity.uid === uid); + + if (index < 0) { + return; + } + + this.items[index] = null; + }); + } + + addEntity(item: CatalogEntity, cellIndex?: number) { + const uid = item.getId(); + const name = item.getName(); + const shortName = getShortName(item); + + if (typeof uid !== "string") { + throw new TypeError("CatalogEntity's ID must be a string"); + } + + if (typeof name !== "string") { + throw new TypeError("CatalogEntity's NAME must be a string"); + } + + if (typeof shortName !== "string") { + throw new TypeError("CatalogEntity's SHORT_NAME must be a string"); + } + + if (this.hasEntity(item.getId())) { + return; + } + + const entity = { + uid, + name, + source: item.metadata.source, + shortName, + }; + const newItem = { entity }; + + if (cellIndex === undefined) { + // Add item to empty cell + const emptyCellIndex = this.items.indexOf(null); + + if (emptyCellIndex >= 0) { + runInAction(() => { + this.items[emptyCellIndex] = newItem; + }); + } else { + broadcastMessage(hotbarTooManyItemsChannel); + } + } else if (0 <= cellIndex && cellIndex < this.items.length) { + runInAction(() => { + this.items[cellIndex] = newItem; + }); + } else { + this.dependencies.logger.error( + "cannot pin entity to hotbar outside of index range", + { entityId: uid, hotbarId: this.id, cellIndex }, + ); + } + } + + toJSON(): HotbarData { + return { + id: this.id, + items: toJS(this.items), + name: this.name.get(), + }; + } +} diff --git a/packages/core/src/features/hotbar/storage/common/hotbars.injectable.ts b/packages/core/src/features/hotbar/storage/common/hotbars.injectable.ts new file mode 100644 index 0000000000..2a13b4fc27 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/hotbars.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import hotbarsStateInjectable from "./state.injectable"; + +const hotbarsInjectable = getInjectable({ + id: "hotbars", + instantiate: (di) => { + const state = di.inject(hotbarsStateInjectable); + + return computed(() => [...state.values()]); + }, +}); + +export default hotbarsInjectable; diff --git a/packages/core/src/common/hotbars/migrations-token.ts b/packages/core/src/features/hotbar/storage/common/migrations-token.ts similarity index 76% rename from packages/core/src/common/hotbars/migrations-token.ts rename to packages/core/src/features/hotbar/storage/common/migrations-token.ts index 5441844933..ede5cd1966 100644 --- a/packages/core/src/common/hotbars/migrations-token.ts +++ b/packages/core/src/features/hotbar/storage/common/migrations-token.ts @@ -4,7 +4,7 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { MigrationDeclaration } from "../base-store/migrations.injectable"; +import type { MigrationDeclaration } from "../../../../common/persistent-storage/migrations.injectable"; export const hotbarStoreMigrationInjectionToken = getInjectionToken({ id: "hotbar-store-migration-token", diff --git a/packages/core/src/features/hotbar/storage/common/remove-entity-from-all.injectable.ts b/packages/core/src/features/hotbar/storage/common/remove-entity-from-all.injectable.ts new file mode 100644 index 0000000000..5890a0e995 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/remove-entity-from-all.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import hotbarsInjectable from "./hotbars.injectable"; + +export type RemoveEntityFromAllHotbars = (entityId: string) => void; + +const removeEntityFromAllHotbarsInjectable = getInjectable({ + id: "remove-entity-from-all-hotbars", + instantiate: (di): RemoveEntityFromAllHotbars => { + const hotbars = di.inject(hotbarsInjectable); + + return action((entityId) => { + for (const hotbar of hotbars.get()) { + hotbar.removeEntity(entityId); + } + }); + }, +}); + +export default removeEntityFromAllHotbarsInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/remove.injectable.ts b/packages/core/src/features/hotbar/storage/common/remove.injectable.ts new file mode 100644 index 0000000000..d06c19b2e0 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/remove.injectable.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import assert from "assert"; +import { action } from "mobx"; +import activeHotbarIdInjectable from "./active-id.injectable"; +import type { Hotbar } from "./hotbar"; +import hotbarsStateInjectable from "./state.injectable"; + +export type RemoveHotbar = (hotbar: Hotbar) => void; + +const removeHotbarInjectable = getInjectable({ + id: "remove-hotbar", + instantiate: (di): RemoveHotbar => { + const state = di.inject(hotbarsStateInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + + return action((hotbar) => { + assert(state.size >= 2, "Cannot remove the last hotbar"); + + state.delete(hotbar.id); + + if (activeHotbarId.get() === hotbar.id) { + activeHotbarId.set(iter.first(state.values())?.id); + } + }); + }, +}); + +export default removeHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/set-as-active.injectable.ts b/packages/core/src/features/hotbar/storage/common/set-as-active.injectable.ts new file mode 100644 index 0000000000..226914fcae --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/set-as-active.injectable.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import activeHotbarIdInjectable from "./active-id.injectable"; +import type { Hotbar } from "./hotbar"; +import hotbarsStateInjectable from "./state.injectable"; + +export type SetAsActiveHotbar = (desc: Hotbar | number | string) => void; + +const setAsActiveHotbarInjectable = getInjectable({ + id: "set-as-active-hotbar", + instantiate: (di): SetAsActiveHotbar => { + const hotbarsState = di.inject(hotbarsStateInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + + return action((desc) => { + if (typeof desc === "number") { + const hotbar = iter.nth(hotbarsState.values(), desc); + + if (hotbar) { + activeHotbarId.set(hotbar.id); + } + } else if (typeof desc === "string") { + if (hotbarsState.has(desc)) { + activeHotbarId.set(desc); + } + } else { + if (hotbarsState.has(desc.id)) { + activeHotbarId.set(desc.id); + } + } + }); + }, +}); + +export default setAsActiveHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/state.injectable.ts b/packages/core/src/features/hotbar/storage/common/state.injectable.ts new file mode 100644 index 0000000000..c544172d9a --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/state.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { Hotbar } from "./hotbar"; + +const hotbarsStateInjectable = getInjectable({ + id: "hotbars-state", + instantiate: () => observable.map(), +}); + +export default hotbarsStateInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/storage.injectable.ts b/packages/core/src/features/hotbar/storage/common/storage.injectable.ts new file mode 100644 index 0000000000..3bdc2bd3b6 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/storage.injectable.ts @@ -0,0 +1,116 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { iter } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { action, comparer } from "mobx"; +import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import { hotbarStoreMigrationInjectionToken } from "./migrations-token"; +import { defaultHotbarCells } from "./types"; +import createPersistentStorageInjectable from "../../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../../common/persistent-storage/migrations.injectable"; +import storeMigrationVersionInjectable from "../../../../common/vars/store-migration-version.injectable"; +import activeHotbarIdInjectable from "./active-id.injectable"; +import createHotbarInjectable from "./create-hotbar.injectable"; +import type { Hotbar, HotbarData } from "./hotbar"; +import hotbarsStateInjectable from "./state.injectable"; + +export interface HotbarStoreModel { + hotbars: HotbarData[]; + activeHotbarId: string | undefined; +} + +const hotbarsPersistentStorageInjectable = getInjectable({ + id: "hotbars-persistent-storage", + instantiate: (di) => { + const state = di.inject(hotbarsStateInjectable); + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + const activeHotbarId = di.inject(activeHotbarIdInjectable); + const createHotbar = di.inject(createHotbarInjectable); + + return createPersistentStorage({ + configName: "lens-hotbar-store", + accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + syncOptions: { + equals: comparer.structural, + }, + projectVersion: di.inject(storeMigrationVersionInjectable), + migrations: di.inject(persistentStorageMigrationsInjectable, hotbarStoreMigrationInjectionToken), + fromStore: action((data) => { + if (!data.hotbars || !data.hotbars.length) { + const hotbar = createHotbar({ + name: "Default", + }); + const { + metadata: { + uid, + name, + source, + }, + } = catalogCatalogEntity; + + hotbar.items[0] = { + entity: { + uid, + name, + source, + }, + }; + state.replace([[hotbar.id, hotbar]]); + } else { + state.replace(data.hotbars.map((hotbar) => [hotbar.id, createHotbar(hotbar)])); + } + + for (const hotbar of state.values()) { + ensureExactHotbarItemLength(hotbar); + } + + if (data.activeHotbarId) { + activeHotbarId.set(data.activeHotbarId); + } + + const firstHotbarId = iter.first(state.values())?.id; + + if (!activeHotbarId.get()) { + activeHotbarId.set(firstHotbarId); + } else if (!iter.find(state.values(), hotbar => hotbar.id === activeHotbarId.get())) { + activeHotbarId.set(firstHotbarId); + } + }), + toJSON: () => ({ + hotbars: iter.chain(state.values()) + .map(hotbar => hotbar.toJSON()) + .toArray(), + activeHotbarId: activeHotbarId.get(), + }), + }); + }, +}); + +export default hotbarsPersistentStorageInjectable; + +/** + * This function ensures that there are always exactly `defaultHotbarCells` + * worth of items in the hotbar. + * @param hotbar The hotbar to modify + */ +function ensureExactHotbarItemLength(hotbar: Hotbar) { + // if there are not enough items + while (hotbar.items.length < defaultHotbarCells) { + hotbar.items.push(null); + } + + // if for some reason the hotbar was overfilled before, remove as many entries + // as needed, but prefer empty slots and items at the end first. + while (hotbar.items.length > defaultHotbarCells) { + const lastNull = hotbar.items.lastIndexOf(null); + + if (lastNull >= 0) { + hotbar.items.splice(lastNull, 1); + } else { + hotbar.items.length = defaultHotbarCells; + } + } +} diff --git a/packages/core/src/features/hotbar/storage/common/switch-to-next.injectable.ts b/packages/core/src/features/hotbar/storage/common/switch-to-next.injectable.ts new file mode 100644 index 0000000000..5648c5bfc4 --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/switch-to-next.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import activeHotbarIndexInjectable from "./active-hotbar-index.injectable"; +import setAsActiveHotbarInjectable from "./set-as-active.injectable"; +import hotbarsStateInjectable from "./state.injectable"; + +export type SwitchToNextHotbar = () => void; + +const switchToNextHotbarInjectable = getInjectable({ + id: "switch-to-next-hotbar", + instantiate: (di): SwitchToNextHotbar => { + const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable); + const activeHotbarIndex = di.inject(activeHotbarIndexInjectable); + const state = di.inject(hotbarsStateInjectable); + + return action(() => { + const index = activeHotbarIndex.get() + 1; + + if (index >= state.size) { + setAsActiveHotbar(0); + } else { + setAsActiveHotbar(index); + } + }); + }, +}); + +export default switchToNextHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/switch-to-previous.injectable.ts b/packages/core/src/features/hotbar/storage/common/switch-to-previous.injectable.ts new file mode 100644 index 0000000000..d18a12c27c --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/switch-to-previous.injectable.ts @@ -0,0 +1,32 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import activeHotbarIndexInjectable from "./active-hotbar-index.injectable"; +import setAsActiveHotbarInjectable from "./set-as-active.injectable"; +import hotbarsStateInjectable from "./state.injectable"; + +export type SwitchToPreviousHotbar = () => void; + +const switchToPreviousHotbarInjectable = getInjectable({ + id: "switch-to-previous-hotbar", + instantiate: (di): SwitchToPreviousHotbar => { + const setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable); + const activeHotbarIndex = di.inject(activeHotbarIndexInjectable); + const state = di.inject(hotbarsStateInjectable); + + return action(() => { + const index = activeHotbarIndex.get() - 1; + + if (index < 0) { + setAsActiveHotbar(state.size - 1); + } else { + setAsActiveHotbar(index); + } + }); + }, +}); + +export default switchToPreviousHotbarInjectable; diff --git a/packages/core/src/features/hotbar/storage/common/toggling.injectable.ts b/packages/core/src/features/hotbar/storage/common/toggling.injectable.ts new file mode 100644 index 0000000000..4daf9ca41e --- /dev/null +++ b/packages/core/src/features/hotbar/storage/common/toggling.injectable.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import activeHotbarInjectable from "./active.injectable"; +import type { Hotbar } from "./hotbar"; + +export type ActiveHotbarModel = Pick; + +const activeHotbarModelInjectable = getInjectable({ + id: "active-hotbar-model", + instantiate: (di): ActiveHotbarModel => { + const activeHotbar = di.inject(activeHotbarInjectable); + + return { + hasEntity: (entityId) => activeHotbar.get()?.hasEntity(entityId) ?? false, + toggleEntity: (entity) => activeHotbar.get()?.toggleEntity(entity), + addEntity: (entity) => activeHotbar.get()?.addEntity(entity), + removeEntity: (entityId) => activeHotbar.get()?.removeEntity(entityId), + }; + }, +}); + +export default activeHotbarModelInjectable; diff --git a/packages/core/src/common/hotbars/types.ts b/packages/core/src/features/hotbar/storage/common/types.ts similarity index 56% rename from packages/core/src/common/hotbars/types.ts rename to packages/core/src/features/hotbar/storage/common/types.ts index 2925c785a0..3153fdd2fa 100644 --- a/packages/core/src/common/hotbars/types.ts +++ b/packages/core/src/features/hotbar/storage/common/types.ts @@ -3,9 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import * as uuid from "uuid"; -import type { Tuple } from "@k8slens/utilities"; -import { tuple } from "@k8slens/utilities"; export interface HotbarItem { entity: { @@ -18,12 +15,10 @@ export interface HotbarItem { }; } -export type Hotbar = Required; - export interface CreateHotbarData { id?: string; name: string; - items?: Tuple; + items?: (HotbarItem | null)[]; } export interface CreateHotbarOptions { @@ -31,11 +26,3 @@ export interface CreateHotbarOptions { } export const defaultHotbarCells = 12; // Number is chosen to easy hit any item with keyboard - -export function getEmptyHotbar(name: string, id: string = uuid.v4()): Hotbar { - return { - id, - items: tuple.filled(defaultHotbarCells, null), - name, - }; -} diff --git a/packages/core/src/features/hotbar/storage/main/5.0.0-alpha.0.injectable.ts b/packages/core/src/features/hotbar/storage/main/5.0.0-alpha.0.injectable.ts new file mode 100644 index 0000000000..9450b9528f --- /dev/null +++ b/packages/core/src/features/hotbar/storage/main/5.0.0-alpha.0.injectable.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +// Cleans up a store that had the state related data stored +import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token"; +import createHotbarInjectable from "../common/create-hotbar.injectable"; + +const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({ + id: "v5.0.0-alpha.0-hotbar-store-migration", + instantiate: (di) => ({ + version: "5.0.0-alpha.0", + run(store) { + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + const createHotbar = di.inject(createHotbarInjectable); + const hotbar = createHotbar({ name: "default" }); + + hotbar.addEntity(catalogCatalogEntity); + + store.set("hotbars", [hotbar.toJSON()]); + }, + }), + injectionToken: hotbarStoreMigrationInjectionToken, +}); + +export default v500Alpha0HotbarStoreMigrationInjectable; + diff --git a/packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.2.injectable.ts b/packages/core/src/features/hotbar/storage/main/5.0.0-alpha.2.injectable.ts similarity index 76% rename from packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.2.injectable.ts rename to packages/core/src/features/hotbar/storage/main/5.0.0-alpha.2.injectable.ts index 550f46a940..adf7ff4f30 100644 --- a/packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.2.injectable.ts +++ b/packages/core/src/features/hotbar/storage/main/5.0.0-alpha.2.injectable.ts @@ -4,10 +4,10 @@ */ // Cleans up a store that had the state related data stored -import type { Hotbar } from "../../../common/hotbars/types"; import * as uuid from "uuid"; import { getInjectable } from "@ogre-tools/injectable"; -import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token"; +import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token"; +import type { HotbarData } from "../common/hotbar"; const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({ id: "v5.0.0-alpha.2-hotbar-store-migration", @@ -15,7 +15,7 @@ const v500Alpha2HotbarStoreMigrationInjectable = getInjectable({ version: "5.0.0-alpha.2", run(store) { const rawHotbars = store.get("hotbars"); - const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : []; + const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : []; store.set("hotbars", hotbars.map(({ id, ...rest }) => ({ id: id || uuid.v4(), diff --git a/packages/core/src/features/hotbar/storage/main/5.0.0-beta.10.injectable.ts b/packages/core/src/features/hotbar/storage/main/5.0.0-beta.10.injectable.ts new file mode 100644 index 0000000000..a777a81b6a --- /dev/null +++ b/packages/core/src/features/hotbar/storage/main/5.0.0-beta.10.injectable.ts @@ -0,0 +1,168 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import * as uuid from "uuid"; +import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import catalogCatalogEntityInjectable from "../../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import { isDefined, isErrnoException } from "@k8slens/utilities"; +import joinPathsInjectable from "../../../../common/path/join-paths.injectable"; +import { getInjectable } from "@ogre-tools/injectable"; +import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token"; +import readJsonSyncInjectable from "../../../../common/fs/read-json-sync.injectable"; +import loggerInjectable from "../../../../common/logger.injectable"; +import { generateNewIdFor } from "../../../../common/utils/generate-new-id-for"; +import type { ClusterModel } from "../../../../common/cluster-types"; +import { defaultHotbarCells } from "../common/types"; +import type { HotbarData } from "../common/hotbar"; +import createHotbarInjectable from "../common/create-hotbar.injectable"; + +interface Pre500WorkspaceStoreModel { + workspaces: { + id: string; + name: string; + }[]; +} + +interface Pre500ClusterModel extends ClusterModel { + workspace?: string; + workspaces?: string[]; +} + +interface Pre500ClusterStoreModel { + clusters?: Pre500ClusterModel[]; +} + +const v500Beta10HotbarStoreMigrationInjectable = getInjectable({ + id: "v5.0.0-beta.10-hotbar-store-migration", + instantiate: (di) => ({ + version: "5.0.0-beta.10", + run(store) { + const userDataPath = di.inject(directoryForUserDataInjectable); + const joinPaths = di.inject(joinPathsInjectable); + const readJsonSync = di.inject(readJsonSyncInjectable); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + const logger = di.inject(loggerInjectable); + const createHotbar = di.inject(createHotbarInjectable); + const rawHotbars = store.get("hotbars"); + const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : []; + + // Hotbars might be empty, if some of the previous migrations weren't run + if (hotbars.length === 0) { + const hotbar = createHotbar({ name: "default" }); + + hotbar.addEntity(catalogCatalogEntity); + hotbars.push(hotbar.toJSON()); + } + + try { + const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json")); + const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json")); + const workspaceHotbars = new Map(); // mapping from WorkspaceId to HotBar + + for (const { id, name } of workspaceStoreData.workspaces) { + logger.info(`Creating new hotbar for ${name}`); + workspaceHotbars.set(id, { + id: uuid.v4(), + items: [], + name: `Workspace: ${name}`, + }); + } + + { + // grab the default named hotbar or the first. + const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default")); + const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1); + + workspaceHotbars.set("default", { + name, + id, + items: items.filter(isDefined), + }); + } + + for (const cluster of clusters) { + const uid = generateNewIdFor(cluster); + + for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) { + const workspaceHotbar = workspaceHotbars.get(workspaceId); + + if (!workspaceHotbar) { + logger.info(`Cluster ${uid} has unknown workspace ID, skipping`); + continue; + } + + logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`); + + if (workspaceHotbar?.items.length < defaultHotbarCells) { + workspaceHotbar.items.push({ + entity: { + uid: generateNewIdFor(cluster), + name: cluster.preferences?.clusterName || cluster.contextName, + }, + }); + } + } + } + + for (const hotbar of workspaceHotbars.values()) { + if (hotbar.items.length === 0) { + logger.info(`Skipping ${hotbar.name} due to it being empty`); + continue; + } + + while (hotbar.items.length < defaultHotbarCells) { + hotbar.items.push(null); + } + + hotbars.push(hotbar); + } + + /** + * Finally, make sure that the catalog entity hotbar item is in place. + * Just in case something else removed it. + * + * if every hotbar has elements that all not the `catalog-entity` item + */ + if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) { + // note, we will add a new whole hotbar here called "default" if that was previously removed + const defaultHotbarIndex = hotbars.findIndex(hotbar => hotbar.name === "default"); + + if (defaultHotbarIndex >= 0) { + const defaultHotbar = createHotbar(hotbars[defaultHotbarIndex]); + + if (defaultHotbar.isFull()) { + // making a new hotbar is less destructive if the first hotbar + // called "default" is full than overriding a hotbar item + const hotbar = createHotbar({ name: "initial" }); + + hotbar.addEntity(catalogCatalogEntity); + hotbars.unshift(hotbar.toJSON()); + } else { + defaultHotbar.addEntity(catalogCatalogEntity); + hotbars[defaultHotbarIndex] = defaultHotbar.toJSON(); + } + } else { + const hotbar = createHotbar({ name: "default" }); + + hotbar.addEntity(catalogCatalogEntity); + hotbars.unshift(hotbar.toJSON()); + } + } + + } catch (error) { + // ignore files being missing + if (isErrnoException(error) && error.code !== "ENOENT") { + throw error; + } + } + + store.set("hotbars", hotbars); + }, + }), + injectionToken: hotbarStoreMigrationInjectionToken, +}); + +export default v500Beta10HotbarStoreMigrationInjectable; + diff --git a/packages/core/src/features/hotbar/storage/main/5.0.0-beta.5.injectable.ts b/packages/core/src/features/hotbar/storage/main/5.0.0-beta.5.injectable.ts new file mode 100644 index 0000000000..75f3868d7b --- /dev/null +++ b/packages/core/src/features/hotbar/storage/main/5.0.0-beta.5.injectable.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import catalogEntityRegistryInjectable from "../../../../main/catalog/entity-registry.injectable"; +import type { HotbarData } from "../common/hotbar"; +import { hotbarStoreMigrationInjectionToken } from "../common/migrations-token"; + +const v500Beta5HotbarStoreMigrationInjectable = getInjectable({ + id: "v500-beta5-hotbar-store-migration", + instantiate: (di) => ({ + version: "5.0.0-beta.5", + run(store) { + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + const rawHotbars = store.get("hotbars"); + const hotbars: HotbarData[] = Array.isArray(rawHotbars) ? rawHotbars : []; + + for (const hotbar of hotbars) { + for (let i = 0; i < hotbar.items.length; i += 1) { + const item = hotbar.items[i]; + + if (!item) { + continue; + } + + const entity = catalogEntityRegistry.findById(item.entity.uid); + + if (!entity) { + // Clear disabled item + hotbar.items[i] = null; + } else { + // Save additional data + item.entity = { + ...item.entity, + name: entity.metadata.name, + source: entity.metadata.source, + }; + } + } + } + + store.set("hotbars", hotbars); + }, + }), + injectionToken: hotbarStoreMigrationInjectionToken, +}); + +export default v500Beta5HotbarStoreMigrationInjectable; + diff --git a/packages/core/src/features/hotbar/store/main/init.injectable.ts b/packages/core/src/features/hotbar/storage/main/load-storage.injectable.ts similarity index 66% rename from packages/core/src/features/hotbar/store/main/init.injectable.ts rename to packages/core/src/features/hotbar/storage/main/load-storage.injectable.ts index cb45c396ac..d541f80249 100644 --- a/packages/core/src/features/hotbar/store/main/init.injectable.ts +++ b/packages/core/src/features/hotbar/storage/main/load-storage.injectable.ts @@ -3,21 +3,21 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; import setupSyncingOfGeneralCatalogEntitiesInjectable from "../../../../main/start-main-application/runnables/setup-syncing-of-general-catalog-entities.injectable"; +import hotbarsPersistentStorageInjectable from "../common/storage.injectable"; -const initHotbarStoreInjectable = getInjectable({ - id: "init-hotbar-store", +const loadHotbarStorageInjectable = getInjectable({ + id: "load-hotbar-storage", instantiate: (di) => ({ run: () => { - const hotbarStore = di.inject(hotbarStoreInjectable); + const storage = di.inject(hotbarsPersistentStorageInjectable); - hotbarStore.load(); + storage.loadAndStartSyncing(); }, runAfter: setupSyncingOfGeneralCatalogEntitiesInjectable, }), injectionToken: onLoadOfApplicationInjectionToken, }); -export default initHotbarStoreInjectable; +export default loadHotbarStorageInjectable; diff --git a/packages/core/src/features/hotbar/store/renderer/init.injectable.ts b/packages/core/src/features/hotbar/storage/renderer/init.injectable.ts similarity index 53% rename from packages/core/src/features/hotbar/store/renderer/init.injectable.ts rename to packages/core/src/features/hotbar/storage/renderer/init.injectable.ts index 239807dfda..ebc787bd4a 100644 --- a/packages/core/src/features/hotbar/store/renderer/init.injectable.ts +++ b/packages/core/src/features/hotbar/storage/renderer/init.injectable.ts @@ -3,21 +3,21 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import { beforeFrameStartsSecondInjectionToken } from "../../../../renderer/before-frame-starts/tokens"; -import initClusterStoreInjectable from "../../../cluster/store/renderer/init.injectable"; +import initClusterStoreInjectable from "../../../cluster/storage/renderer/init.injectable"; +import hotbarsPersistentStorageInjectable from "../common/storage.injectable"; -const initHotbarStoreInjectable = getInjectable({ - id: "init-hotbar-store", +const loadHotbarStorageInjectable = getInjectable({ + id: "load-hotbar-storage", instantiate: (di) => ({ run: () => { - const hotbarStore = di.inject(hotbarStoreInjectable); + const storage = di.inject(hotbarsPersistentStorageInjectable); - hotbarStore.load(); + storage.loadAndStartSyncing(); }, runAfter: initClusterStoreInjectable, }), injectionToken: beforeFrameStartsSecondInjectionToken, }); -export default initHotbarStoreInjectable; +export default loadHotbarStorageInjectable; diff --git a/packages/core/src/features/hotbar/storage/storage-technical.test.ts b/packages/core/src/features/hotbar/storage/storage-technical.test.ts new file mode 100644 index 0000000000..54e6875c0a --- /dev/null +++ b/packages/core/src/features/hotbar/storage/storage-technical.test.ts @@ -0,0 +1,373 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { anyObject } from "jest-mock-extended"; +import type { CatalogEntity, CatalogEntityData, CatalogEntityKindData } from "../../../common/catalog"; +import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting"; +import type { DiContainer } from "@ogre-tools/injectable"; +import catalogEntityRegistryInjectable from "../../../main/catalog/entity-registry.injectable"; +import type { IComputedValue } from "mobx"; +import { computed } from "mobx"; +import hasCategoryForEntityInjectable from "../../../common/catalog/has-category-for-entity.injectable"; +import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; +import loggerInjectable from "../../../common/logger.injectable"; +import type { Logger } from "../../../common/logger"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; +import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable"; +import type { SetAsActiveHotbar } from "./common/set-as-active.injectable"; +import setAsActiveHotbarInjectable from "./common/set-as-active.injectable"; +import hotbarsPersistentStorageInjectable from "./common/storage.injectable"; +import type { Hotbar } from "./common/hotbar"; +import hotbarsInjectable from "./common/hotbars.injectable"; +import activeHotbarInjectable from "./common/active.injectable"; +import type { AddHotbar } from "./common/add.injectable"; +import type { GetHotbarById } from "./common/get-by-id.injectable"; +import getHotbarByIdInjectable from "./common/get-by-id.injectable"; +import addHotbarInjectable from "./common/add.injectable"; +import { defaultHotbarCells } from "./common/types"; + +function getMockCatalogEntity(data: Partial & CatalogEntityKindData): CatalogEntity { + return { + getName: jest.fn(() => data.metadata?.name), + getId: jest.fn(() => data.metadata?.uid), + getSource: jest.fn(() => data.metadata?.source ?? "unknown"), + isEnabled: jest.fn(() => data.status?.enabled ?? true), + onContextMenuOpen: jest.fn(), + onSettingsOpen: jest.fn(), + metadata: {}, + spec: {}, + status: {}, + ...data, + } as CatalogEntity; +} + +describe("Hotbars technical tests", () => { + let di: DiContainer; + let testCluster: CatalogEntity; + let minikubeCluster: CatalogEntity; + let awsCluster: CatalogEntity; + let loggerMock: jest.Mocked; + let setAsActiveHotbar: SetAsActiveHotbar; + let hotbars: IComputedValue; + let activeHotbar: IComputedValue; + let addHotbar: AddHotbar; + let getHotbarById: GetHotbarById; + + beforeEach(async () => { + di = getDiForUnitTesting(); + + testCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-test-id", + name: "my-test-cluster", + source: "local", + labels: {}, + }, + }); + minikubeCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-minikube-id", + name: "my-minikube-cluster", + source: "local", + labels: {}, + }, + }); + awsCluster = getMockCatalogEntity({ + apiVersion: "v1", + kind: "Cluster", + status: { + phase: "Running", + }, + metadata: { + uid: "some-aws-id", + name: "my-aws-cluster", + source: "local", + labels: {}, + }, + }); + + di.override(hasCategoryForEntityInjectable, () => () => true); + + loggerMock = { + warn: jest.fn(), + debug: jest.fn(), + error: jest.fn(), + info: jest.fn(), + silly: jest.fn(), + }; + + di.override(loggerInjectable, () => loggerMock); + + di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); + + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); + + catalogEntityRegistry.addComputedSource("some-id", computed(() => [ + testCluster, + minikubeCluster, + awsCluster, + catalogCatalogEntity, + ])); + + setAsActiveHotbar = di.inject(setAsActiveHotbarInjectable); + hotbars = di.inject(hotbarsInjectable); + activeHotbar = di.inject(activeHotbarInjectable); + addHotbar = di.inject(addHotbarInjectable); + getHotbarById = di.inject(getHotbarByIdInjectable); + }); + + describe("given no previous data in store, running all migrations", () => { + beforeEach(() => { + di.override(storeMigrationVersionInjectable, () => "9999.0.0"); + di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing(); + }); + + describe("load", () => { + it("loads one hotbar by default", () => { + expect(hotbars.get().length).toEqual(1); + }); + }); + + describe("add", () => { + it("adds a hotbar", () => { + addHotbar({ name: "hottest" }); + expect(hotbars.get().length).toEqual(2); + }); + }); + + describe("hotbar items", () => { + it("initially creates default number of empty cells", () => { + expect(activeHotbar.get()?.items?.length).toEqual(defaultHotbarCells); + }); + + it("initially adds catalog entity as first item", () => { + expect(activeHotbar.get()?.items[0]?.entity.name).toEqual("Catalog"); + }); + + it("adds items", () => { + activeHotbar.get()?.addEntity(testCluster); + const items = activeHotbar.get()?.items.filter(Boolean); + + expect(items?.length).toEqual(2); + }); + + it("removes items", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.removeEntity("some-test-id"); + activeHotbar.get()?.removeEntity("catalog-entity"); + const items = activeHotbar.get()?.items.filter(Boolean); + + expect(items).toStrictEqual([]); + }); + + it("does nothing if removing with invalid uid", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.removeEntity("invalid uid"); + const items = activeHotbar.get()?.items.filter(Boolean); + + expect(items?.length).toEqual(2); + }); + + it("moves item to empty cell", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.addEntity(minikubeCluster); + activeHotbar.get()?.addEntity(awsCluster); + + expect(activeHotbar.get()?.items[6]).toBeNull(); + + activeHotbar.get()?.restack(1, 5); + + expect(activeHotbar.get()?.items[5]).toBeTruthy(); + expect(activeHotbar.get()?.items[5]?.entity.uid).toEqual("some-test-id"); + }); + + it("moves items down", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.addEntity(minikubeCluster); + activeHotbar.get()?.addEntity(awsCluster); + + // aws -> catalog + activeHotbar.get()?.restack(3, 0); + + const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null); + + expect(items?.slice(0, 4)).toEqual(["some-aws-id", "catalog-entity", "some-test-id", "some-minikube-id"]); + }); + + it("moves items up", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.addEntity(minikubeCluster); + activeHotbar.get()?.addEntity(awsCluster); + + // test -> aws + activeHotbar.get()?.restack(1, 3); + + const items = activeHotbar.get()?.items.map(item => item?.entity.uid || null); + + expect(items?.slice(0, 4)).toEqual(["catalog-entity", "some-minikube-id", "some-aws-id", "some-test-id"]); + }); + + it("logs an error if cellIndex is out of bounds", () => { + addHotbar({ name: "hottest", id: "hottest" }); + setAsActiveHotbar("hottest"); + + activeHotbar.get()?.addEntity(testCluster, -1); + expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject()); + + activeHotbar.get()?.addEntity(testCluster, 12); + expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject()); + + activeHotbar.get()?.addEntity(testCluster, 13); + expect(loggerMock.error).toBeCalledWith("[HOTBAR]: cannot pin entity to hotbar outside of index range", anyObject()); + }); + + it("throws an error if getId is invalid or returns not a string", () => { + expect(() => activeHotbar.get()?.addEntity({} as any)).toThrowError(TypeError); + expect(() => activeHotbar.get()?.addEntity({ getId: () => true } as any)).toThrowError(TypeError); + }); + + it("throws an error if getName is invalid or returns not a string", () => { + expect(() => activeHotbar.get()?.addEntity({ getId: () => "" } as any)).toThrowError(TypeError); + expect(() => activeHotbar.get()?.addEntity({ getId: () => "", getName: () => 4 } as any)).toThrowError(TypeError); + }); + + it("does nothing when item moved to same cell", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.restack(1, 1); + + expect(activeHotbar.get()?.items[1]?.entity.uid).toEqual("some-test-id"); + }); + + it("new items takes first empty cell", () => { + activeHotbar.get()?.addEntity(testCluster); + activeHotbar.get()?.addEntity(awsCluster); + activeHotbar.get()?.restack(0, 3); + activeHotbar.get()?.addEntity(minikubeCluster); + + expect(activeHotbar.get()?.items[0]?.entity.uid).toEqual("some-minikube-id"); + }); + + it("throws if invalid arguments provided", () => { + activeHotbar.get()?.addEntity(testCluster); + + expect(() => activeHotbar.get()?.restack(-5, 0)).toThrow(); + expect(() => activeHotbar.get()?.restack(2, -1)).toThrow(); + expect(() => activeHotbar.get()?.restack(14, 1)).toThrow(); + expect(() => activeHotbar.get()?.restack(11, 112)).toThrow(); + }); + + it("checks if entity already pinned to hotbar", () => { + activeHotbar.get()?.addEntity(testCluster); + + expect(activeHotbar.get()?.hasEntity(testCluster.getId())).toBeTruthy(); + expect(activeHotbar.get()?.hasEntity(awsCluster.getId())).toBeFalsy(); + }); + }); + }); + + describe("given data from 5.0.0-beta.3 and version being 5.0.0-beta.10", () => { + beforeEach(() => { + const writeJsonSync = di.inject(writeJsonSyncInjectable); + + writeJsonSync("/some-directory-for-user-data/lens-hotbar-store.json", { + __internal__: { + migrations: { + version: "5.0.0-beta.3", + }, + }, + hotbars: [ + { + id: "3caac17f-aec2-4723-9694-ad204465d935", + name: "myhotbar", + items: [ + { + entity: { + uid: "some-aws-id", + }, + }, + { + entity: { + uid: "55b42c3c7ba3b04193416cda405269a5", + }, + }, + { + entity: { + uid: "176fd331968660832f62283219d7eb6e", + }, + }, + { + entity: { + uid: "61c4fb45528840ebad1badc25da41d14", + name: "user1-context", + source: "local", + }, + }, + { + entity: { + uid: "27d6f99fe9e7548a6e306760bfe19969", + name: "foo2", + source: "local", + }, + }, + null, + { + entity: { + uid: "c0b20040646849bb4dcf773e43a0bf27", + name: "multinode-demo", + source: "local", + }, + }, + null, + null, + null, + null, + null, + ], + }, + ], + }); + + di.override(storeMigrationVersionInjectable, () => "5.0.0-beta.10"); + + di.inject(hotbarsPersistentStorageInjectable).loadAndStartSyncing(); + }); + + it("allows to retrieve a hotbar", () => { + const hotbar = getHotbarById("3caac17f-aec2-4723-9694-ad204465d935"); + + expect(hotbar?.id).toBe("3caac17f-aec2-4723-9694-ad204465d935"); + }); + + it("clears cells without entity", () => { + const items = hotbars.get()[0].items; + + expect(items[2]).toBeNull(); + }); + + it("adds extra data to cells with according entity", () => { + const items = hotbars.get()[0].items; + + expect(items[0]).toEqual({ + entity: { + name: "my-aws-cluster", + source: "local", + uid: "some-aws-id", + }, + }); + }); + }); +}); diff --git a/packages/core/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap b/packages/core/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap index 23530d3cb7..90b64e5302 100644 --- a/packages/core/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap +++ b/packages/core/src/features/preferences/__snapshots__/closing-preferences.test.tsx.snap @@ -709,7 +709,7 @@ exports[`preferences - closing-preferences given accessing preferences directly class="HotbarSelector" > { - const [customUrl, setCustomUrl] = React.useState(userStore.extensionRegistryUrl.customUrl || ""); +const NonInjectedExtensionInstallRegistry = observer(({ state }: Dependencies) => { + const [customUrl, setCustomUrl] = React.useState(state.extensionRegistryUrl.customUrl || ""); return (
@@ -42,14 +42,14 @@ const NonInjectedExtensionInstallRegistry = observer(({ userStore }: Dependencie - (userStore.colorTheme = value?.value ?? defaultTheme.name) + (state.colorTheme = value?.value ?? defaultTheme.name) } themeName="lens" /> @@ -53,7 +53,7 @@ const NonInjectedTheme = observer(({ export const Theme = withInjectables(NonInjectedTheme, { getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), defaultTheme: di.inject(defaultLensThemeInjectable), themes: di.injectMany(lensThemeDeclarationInjectionToken), }), diff --git a/packages/core/src/features/preferences/renderer/preference-items/application/timezone/timezone.tsx b/packages/core/src/features/preferences/renderer/preference-items/application/timezone/timezone.tsx index c3ee8f2ea1..987f378bd9 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/application/timezone/timezone.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/application/timezone/timezone.tsx @@ -5,15 +5,15 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { Select } from "../../../../../../renderer/components/select"; import moment from "moment-timezone"; import { observer } from "mobx-react"; -import currentTimezoneInjectable from "../../../../../../common/user-store/current-timezone.injectable"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import currentTimezoneInjectable from "../../../../../../common/vars/current-timezone.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; currentTimezone: string; } @@ -23,9 +23,8 @@ const timezoneOptions = moment.tz.names() label: timezone.replace("_", " "), })); - const NonInjectedTimezone = observer(({ - userStore, + state, currentTimezone, }: Dependencies) => (
@@ -33,17 +32,16 @@ const NonInjectedTimezone = observer(({ )); -export const EditorFontFamily = withInjectables( - NonInjectedEditorFontFamily, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const EditorFontFamily = withInjectables(NonInjectedEditorFontFamily, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/editor/editor-font-size/editor-font-size.tsx b/packages/core/src/features/preferences/renderer/preference-items/editor/editor-font-size/editor-font-size.tsx index 7fe83ec6aa..df144df2b7 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/editor/editor-font-size/editor-font-size.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/editor/editor-font-size/editor-font-size.tsx @@ -5,16 +5,16 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { Input, InputValidators } from "../../../../../../renderer/components/input"; import { observer } from "mobx-react"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedEditorFontSize = observer(({ userStore: { editorConfiguration }}: Dependencies) => ( +const NonInjectedEditorFontSize = observer(({ state: { editorConfiguration }}: Dependencies) => (
)); -export const EditorFontSize = withInjectables( - NonInjectedEditorFontSize, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const EditorFontSize = withInjectables(NonInjectedEditorFontSize, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/editor/line-numbers/line-numbers.tsx b/packages/core/src/features/preferences/renderer/preference-items/editor/line-numbers/line-numbers.tsx index 10cc508baa..bfd73f7415 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/editor/line-numbers/line-numbers.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/editor/line-numbers/line-numbers.tsx @@ -5,15 +5,15 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { Select } from "../../../../../../renderer/components/select"; -import { defaultEditorConfig } from "../../../../../../common/user-store/preferences-helpers"; import { capitalize } from "lodash/fp"; import { observer } from "mobx-react"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import { defaultEditorConfig } from "../../../../../user-preferences/common/preferences-helpers"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } const lineNumberOptions = ([ @@ -26,7 +26,7 @@ const lineNumberOptions = ([ label: capitalize(lineNumbers), })); -const NonInjectedLineNumbers = observer(({ userStore: { editorConfiguration }}: Dependencies) => ( +const NonInjectedLineNumbers = observer(({ state: { editorConfiguration }}: Dependencies) => (
)); -export const TabSize = withInjectables( - NonInjectedTabSize, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const TabSize = withInjectables(NonInjectedTabSize, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/kubeconfig-sync.tsx b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/kubeconfig-sync.tsx index 77642f1082..510fbd0115 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/kubeconfig-sync.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubeconfig-sync/kubeconfig-sync.tsx @@ -7,13 +7,11 @@ import { computed, makeObservable, observable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; import React from "react"; import { Notice } from "../../../../../../renderer/components/+extensions/notice"; -import type { UserStore } from "../../../../../../common/user-store"; import { iter, tuple } from "@k8slens/utilities"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { PathPicker } from "../../../../../../renderer/components/path-picker/path-picker"; import { Spinner } from "../../../../../../renderer/components/spinner"; import { RemovableItem } from "../../../removable-item/removable-item"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import isWindowsInjectable from "../../../../../../common/vars/is-windows.injectable"; import loggerInjectable from "../../../../../../common/logger.injectable"; import type { Logger } from "../../../../../../common/logger"; @@ -21,13 +19,15 @@ import type { DiscoverAllKubeconfigSyncKinds } from "./discover-all-sync-kinds.i import type { DiscoverKubeconfigSyncKind, SyncKind } from "./discover-sync-kind.injectable"; import discoverKubeconfigSyncKindInjectable from "./discover-sync-kind.injectable"; import discoverAllKubeconfigSyncKindsInjectable from "./discover-all-sync-kinds.injectable"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Entry extends SyncKind { filePath: string; } interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; isWindows: boolean; logger: Logger; discoverAllKubeconfigSyncKinds: DiscoverAllKubeconfigSyncKinds; @@ -47,7 +47,7 @@ class NonInjectedKubeconfigSync extends React.Component { async componentDidMount() { const mapEntries = await Promise.all( iter.map( - this.props.userStore.syncKubeconfigEntries, + this.props.state.syncKubeconfigEntries, ([filePath]) => this.props.discoverKubeconfigSyncKind(filePath), ), ); @@ -59,7 +59,7 @@ class NonInjectedKubeconfigSync extends React.Component { reaction( () => Array.from(this.syncs.entries(), ([filePath, kind]) => tuple.from(filePath, kind)), syncs => { - this.props.userStore.syncKubeconfigEntries.replace(syncs); + this.props.state.syncKubeconfigEntries.replace(syncs); }, ), ]); @@ -177,7 +177,7 @@ class NonInjectedKubeconfigSync extends React.Component { export const KubeconfigSync = withInjectables(NonInjectedKubeconfigSync, { getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), isWindows: di.inject(isWindowsInjectable), logger: di.inject(loggerInjectable), discoverAllKubeconfigSyncKinds: di.inject(discoverAllKubeconfigSyncKindsInjectable), diff --git a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-binary-download/kubectl-binary-download.tsx b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-binary-download/kubectl-binary-download.tsx index de096191fd..35d0ebff62 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-binary-download/kubectl-binary-download.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-binary-download/kubectl-binary-download.tsx @@ -5,34 +5,29 @@ import React from "react"; import { SubTitle } from "../../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Switch } from "../../../../../../../renderer/components/switch"; +import type { UserPreferencesState } from "../../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedKubectlBinaryDownload = observer(({ userStore }: Dependencies) => ( +const NonInjectedKubectlBinaryDownload = observer(({ state }: Dependencies) => (
userStore.downloadKubectlBinaries = !userStore.downloadKubectlBinaries} + checked={state.downloadKubectlBinaries} + onChange={() => state.downloadKubectlBinaries = !state.downloadKubectlBinaries} > Download kubectl binaries matching the Kubernetes cluster version
- )); -export const KubectlBinaryDownload = withInjectables( - NonInjectedKubectlBinaryDownload, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const KubectlBinaryDownload = withInjectables(NonInjectedKubectlBinaryDownload, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-directory-for-binaries/kubectl-directory-for-binaries.tsx b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-directory-for-binaries/kubectl-directory-for-binaries.tsx index 497ec68f8e..7e4bc8a2ab 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-directory-for-binaries/kubectl-directory-for-binaries.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-directory-for-binaries/kubectl-directory-for-binaries.tsx @@ -5,24 +5,24 @@ import React, { useState } from "react"; import { SubTitle } from "../../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Input, InputValidators } from "../../../../../../../renderer/components/input"; import directoryForBinariesInjectable from "../../../../../../../common/app-paths/directory-for-binaries/directory-for-binaries.injectable"; +import type { UserPreferencesState } from "../../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; defaultPathForGeneralBinaries: string; } const NonInjectedKubectlDirectoryForBinaries = observer( - ({ userStore, defaultPathForGeneralBinaries }: Dependencies) => { - const [downloadPath, setDownloadPath] = useState(userStore.downloadBinariesPath || ""); + ({ state, defaultPathForGeneralBinaries }: Dependencies) => { + const [downloadPath, setDownloadPath] = useState(state.downloadBinariesPath || ""); const pathValidator = downloadPath ? InputValidators.isPath : undefined; const save = () => { - userStore.downloadBinariesPath = downloadPath; + state.downloadBinariesPath = downloadPath; }; return ( @@ -35,7 +35,7 @@ const NonInjectedKubectlDirectoryForBinaries = observer( validators={pathValidator} onChange={setDownloadPath} onBlur={save} - disabled={!userStore.downloadKubectlBinaries} + disabled={!state.downloadKubectlBinaries} />
The directory to download binaries into.
@@ -43,13 +43,9 @@ const NonInjectedKubectlDirectoryForBinaries = observer( }, ); -export const KubectlDirectoryForBinaries = withInjectables( - NonInjectedKubectlDirectoryForBinaries, - - { - getProps: (di) => ({ - defaultPathForGeneralBinaries: di.inject(directoryForBinariesInjectable), - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const KubectlDirectoryForBinaries = withInjectables(NonInjectedKubectlDirectoryForBinaries, { + getProps: (di) => ({ + defaultPathForGeneralBinaries: di.inject(directoryForBinariesInjectable), + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-download-mirror/kubectl-download-mirror.tsx b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-download-mirror/kubectl-download-mirror.tsx index e880ff3308..7f01d8a933 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-download-mirror/kubectl-download-mirror.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/kubernetes/kubectl/kubectl-download-mirror/kubectl-download-mirror.tsx @@ -5,14 +5,14 @@ import React from "react"; import { SubTitle } from "../../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Select } from "../../../../../../../renderer/components/select"; -import { defaultPackageMirror, packageMirrors } from "../../../../../../../common/user-store/preferences-helpers"; +import { defaultPackageMirror, packageMirrors } from "../../../../../../user-preferences/common/preferences-helpers"; +import type { UserPreferencesState } from "../../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } const downloadMirrorOptions = Array.from(packageMirrors, ([name, mirror]) => ({ @@ -24,27 +24,23 @@ const downloadMirrorOptions = Array.from(packageMirrors, ([name, mirror]) => ({ })); -const NonInjectedKubectlDownloadMirror = observer(({ userStore }: Dependencies) => ( +const NonInjectedKubectlDownloadMirror = observer(({ state }: Dependencies) => (
-
- ); - }, + return ( +
+ + +
+ ); +}, ); -export const KubectlPathToBinary = withInjectables( - NonInjectedKubectlPathToBinary, - - { - getProps: (di) => ({ - defaultPathForKubectlBinaries: di.inject(directoryForKubectlBinariesInjectable), - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const KubectlPathToBinary = withInjectables(NonInjectedKubectlPathToBinary, { + getProps: (di) => ({ + defaultPathForKubectlBinaries: di.inject(directoryForKubectlBinariesInjectable), + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/proxy/allow-untrusted-certificates/allow-untrusted-certificates.tsx b/packages/core/src/features/preferences/renderer/preference-items/proxy/allow-untrusted-certificates/allow-untrusted-certificates.tsx index bb1abd9a57..eea78f6cda 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/proxy/allow-untrusted-certificates/allow-untrusted-certificates.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/proxy/allow-untrusted-certificates/allow-untrusted-certificates.tsx @@ -5,23 +5,21 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Switch } from "../../../../../../renderer/components/switch"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedAllowUntrustedCertificates = observer(({ userStore }: Dependencies) => ( +const NonInjectedAllowUntrustedCertificates = observer(({ state }: Dependencies) => (
- (userStore.allowUntrustedCAs = !userStore.allowUntrustedCAs) - } + checked={state.allowUntrustedCAs} + onChange={() => state.allowUntrustedCAs = !state.allowUntrustedCAs} > Allow untrusted Certificate Authorities @@ -33,12 +31,8 @@ const NonInjectedAllowUntrustedCertificates = observer(({ userStore }: Dependenc
)); -export const AllowUntrustedCertificates = withInjectables( - NonInjectedAllowUntrustedCertificates, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const AllowUntrustedCertificates = withInjectables(NonInjectedAllowUntrustedCertificates, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/proxy/http-proxy-url/http-proxy-url.tsx b/packages/core/src/features/preferences/renderer/preference-items/proxy/http-proxy-url/http-proxy-url.tsx index 7d55fd2b3b..9cc8e38d4b 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/proxy/http-proxy-url/http-proxy-url.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/proxy/http-proxy-url/http-proxy-url.tsx @@ -5,43 +5,39 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Input } from "../../../../../../renderer/components/input"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedHttpProxyUrl = observer( - ({ userStore }: Dependencies) => { - const [proxy, setProxy] = React.useState(userStore.httpsProxy || ""); +const NonInjectedHttpProxyUrl = observer(({ + state, +}: Dependencies) => { + const [proxy, setProxy] = React.useState(state.httpsProxy || ""); - return ( -
- - setProxy(v)} - onBlur={() => (userStore.httpsProxy = proxy)} - /> - - Proxy is used only for non-cluster communication. - -
- ); - }, -); + return ( +
+ + setProxy(v)} + onBlur={() => (state.httpsProxy = proxy)} + /> + + Proxy is used only for non-cluster communication. + +
+ ); +}); -export const HttpProxyUrl = withInjectables( - NonInjectedHttpProxyUrl, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const HttpProxyUrl = withInjectables(NonInjectedHttpProxyUrl, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/telemetry/automatic-error-reporting/automatic-error-reporting.tsx b/packages/core/src/features/preferences/renderer/preference-items/telemetry/automatic-error-reporting/automatic-error-reporting.tsx index 7e23c0af35..167189fffc 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/telemetry/automatic-error-reporting/automatic-error-reporting.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/telemetry/automatic-error-reporting/automatic-error-reporting.tsx @@ -5,16 +5,16 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Checkbox } from "../../../../../../renderer/components/checkbox"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedAutomaticErrorReporting = observer(({ userStore }: Dependencies) => ( +const NonInjectedAutomaticErrorReporting = observer(({ state }: Dependencies) => (
(userStore.allowErrorReporting = value)} + value={state.allowErrorReporting} + onChange={(value) => (state.allowErrorReporting = value)} />
@@ -36,12 +36,8 @@ const NonInjectedAutomaticErrorReporting = observer(({ userStore }: Dependencies
)); -export const AutomaticErrorReporting = withInjectables( - NonInjectedAutomaticErrorReporting, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const AutomaticErrorReporting = withInjectables(NonInjectedAutomaticErrorReporting, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/terminal/copy-paste-from-terminal/copy-paste-from-terminal.tsx b/packages/core/src/features/preferences/renderer/preference-items/terminal/copy-paste-from-terminal/copy-paste-from-terminal.tsx index 6fd65ccbbd..8e1f6701d8 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/terminal/copy-paste-from-terminal/copy-paste-from-terminal.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/terminal/copy-paste-from-terminal/copy-paste-from-terminal.tsx @@ -5,38 +5,31 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Switch } from "../../../../../../renderer/components/switch"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedCopyPasteFromTerminal = observer( - ({ userStore }: Dependencies) => { +const NonInjectedCopyPasteFromTerminal = observer(({ + state, +}: Dependencies) => ( +
+ + state.terminalCopyOnSelect = !state.terminalCopyOnSelect} + > + Copy on select and paste on right-click + +
+)); - return ( -
- - userStore.terminalCopyOnSelect = !userStore.terminalCopyOnSelect} - > - Copy on select and paste on right-click - -
- ); - }, -); - -export const CopyPasteFromTerminal = withInjectables( - NonInjectedCopyPasteFromTerminal, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const CopyPasteFromTerminal = withInjectables(NonInjectedCopyPasteFromTerminal, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-family/terminal-font-options.injectable.tsx b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-family/terminal-font-options.injectable.tsx index 3d6a0d2ae0..ceedb3fe02 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-family/terminal-font-options.injectable.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-family/terminal-font-options.injectable.tsx @@ -7,10 +7,10 @@ import type { IComputedValue } from "mobx"; import { action, computed } from "mobx"; import React from "react"; import type { SingleValue } from "react-select"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { defaultTerminalFontFamily } from "../../../../../../common/vars"; import type { SelectOption } from "../../../../../../renderer/components/select"; import { terminalFontInjectionToken } from "../../../../../terminal/renderer/fonts/token"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; export interface TerminalFontPreferencePresenter { readonly options: IComputedValue[]>; @@ -21,7 +21,7 @@ export interface TerminalFontPreferencePresenter { const terminalFontPreferencePresenterInjectable = getInjectable({ id: "terminal-font-preference-presenter", instantiate: (di): TerminalFontPreferencePresenter => { - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); const terminalFonts = di.injectMany(terminalFontInjectionToken); return { @@ -30,18 +30,18 @@ const terminalFontPreferencePresenterInjectable = getInjectable({ {font.name} ), value: font.name, - isSelected: userStore.terminalConfig.fontFamily === font.name, + isSelected: state.terminalConfig.fontFamily === font.name, }))), - current: computed(() => userStore.terminalConfig.fontFamily), + current: computed(() => state.terminalConfig.fontFamily), onSelection: action(selection => { - userStore.terminalConfig.fontFamily = selection?.value ?? defaultTerminalFontFamily; + state.terminalConfig.fontFamily = selection?.value ?? defaultTerminalFontFamily; }), }; }, diff --git a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-size/terminal-font-size.tsx b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-size/terminal-font-size.tsx index bf11285509..75a6a35242 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-size/terminal-font-size.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-font-size/terminal-font-size.tsx @@ -5,40 +5,32 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Input } from "../../../../../../renderer/components/input"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedTerminalFontSize = observer( - ({ userStore }: Dependencies) => { +const NonInjectedTerminalFontSize = observer(({ + state, +}: Dependencies) => ( +
+ + state.terminalConfig.fontSize = Number(value)} /> +
+)); - return ( -
- - userStore.terminalConfig.fontSize = Number(value)} - /> -
- ); - }, -); - -export const TerminalFontSize = withInjectables( - NonInjectedTerminalFontSize, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - }), - }, -); +export const TerminalFontSize = withInjectables(NonInjectedTerminalFontSize, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-shell-path/terminal-shell-path.tsx b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-shell-path/terminal-shell-path.tsx index 5fe14ab9df..bb5deaefe4 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-shell-path/terminal-shell-path.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-shell-path/terminal-shell-path.tsx @@ -5,42 +5,35 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Input } from "../../../../../../renderer/components/input"; import defaultShellInjectable from "./default-shell/default-shell.injectable"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; defaultShell: string; } -const NonInjectedTerminalShellPath = observer( - ({ userStore, defaultShell }: Dependencies) => { +const NonInjectedTerminalShellPath = observer(({ + state, + defaultShell, +}: Dependencies) => ( +
+ + state.shell = value} + /> +
+)); - return ( -
- - userStore.shell = value} - /> -
- - ); - }, -); - -export const TerminalShellPath = withInjectables( - NonInjectedTerminalShellPath, - - { - getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), - defaultShell: di.inject(defaultShellInjectable), - }), - }, -); +export const TerminalShellPath = withInjectables(NonInjectedTerminalShellPath, { + getProps: (di) => ({ + state: di.inject(userPreferencesStateInjectable), + defaultShell: di.inject(defaultShellInjectable), + }), +}); diff --git a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-theme/terminal-theme.tsx b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-theme/terminal-theme.tsx index bea6b1279e..5819171c8c 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-theme/terminal-theme.tsx +++ b/packages/core/src/features/preferences/renderer/preference-items/terminal/terminal-theme/terminal-theme.tsx @@ -5,26 +5,25 @@ import React from "react"; import { SubTitle } from "../../../../../../renderer/components/layout/sub-title"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { UserStore } from "../../../../../../common/user-store"; -import userStoreInjectable from "../../../../../../common/user-store/user-store.injectable"; import { observer } from "mobx-react"; import { Select } from "../../../../../../renderer/components/select"; import type { LensTheme } from "../../../../../../renderer/themes/lens-theme"; import { lensThemeDeclarationInjectionToken } from "../../../../../../renderer/themes/declaration"; +import type { UserPreferencesState } from "../../../../../user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../../user-preferences/common/state.injectable"; interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; themes: LensTheme[]; } const NonInjectedTerminalTheme = observer(({ - userStore, + state, themes, }: Dependencies) => { - const themeOptions = [ { - value: "", // TODO: replace with a sentinal value that isn't string (and serialize it differently) + value: "", // TODO: replace with a sentinel value that isn't string (and serialize it differently) label: "Match Lens Theme", }, ...themes.map(theme => ({ @@ -40,8 +39,8 @@ const NonInjectedTerminalTheme = observer(({ id="terminal-theme-input" themeName="lens" options={themeOptions} - value={userStore.terminalTheme} - onChange={option => userStore.terminalTheme = option?.value ?? ""} + value={state.terminalTheme} + onChange={option => state.terminalTheme = option?.value ?? ""} />
); @@ -50,7 +49,7 @@ const NonInjectedTerminalTheme = observer(({ export const TerminalTheme = withInjectables(NonInjectedTerminalTheme, { getProps: (di) => ({ - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), themes: di.injectMany(lensThemeDeclarationInjectionToken), }), }); diff --git a/packages/core/src/features/shell-sync/main/setup-shell.injectable.ts b/packages/core/src/features/shell-sync/main/setup-shell.injectable.ts index 3a763b7426..f7a8b65974 100644 --- a/packages/core/src/features/shell-sync/main/setup-shell.injectable.ts +++ b/packages/core/src/features/shell-sync/main/setup-shell.injectable.ts @@ -8,9 +8,9 @@ import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable"; import electronAppInjectable from "../../../main/electron-app/electron-app.injectable"; import computeShellEnvironmentInjectable from "./compute-shell-environment.injectable"; -import userShellSettingInjectable from "../../../common/user-store/shell-setting.injectable"; import emitShellSyncFailedInjectable from "./emit-failure.injectable"; import { unionPATHs } from "@k8slens/utilities"; +import userShellSettingInjectable from "../../user-preferences/common/shell-setting.injectable"; const setupShellInjectable = getInjectable({ id: "setup-shell", diff --git a/packages/core/src/features/status-bar/__snapshots__/status-bar-items-originating-from-extensions.test.tsx.snap b/packages/core/src/features/status-bar/__snapshots__/status-bar-items-originating-from-extensions.test.tsx.snap index f652074bf8..1b21dc2909 100644 --- a/packages/core/src/features/status-bar/__snapshots__/status-bar-items-originating-from-extensions.test.tsx.snap +++ b/packages/core/src/features/status-bar/__snapshots__/status-bar-items-originating-from-extensions.test.tsx.snap @@ -231,7 +231,7 @@ exports[`status-bar-items-originating-from-extensions when application starts wh class="HotbarSelector" > { - const userStore = di.inject(userStoreInjectable); + const userStore = di.inject(userPreferencesStateInjectable); return computed(() => userStore.httpsProxy); }, diff --git a/packages/core/src/features/user-preferences/common/is-table-column-hidden.injectable.ts b/packages/core/src/features/user-preferences/common/is-table-column-hidden.injectable.ts new file mode 100644 index 0000000000..f1e3d72318 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/is-table-column-hidden.injectable.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectable } from "@ogre-tools/injectable"; +import userPreferencesStateInjectable from "./state.injectable"; + +/** + * Checks if a column (by ID) for a table (by ID) is configured to be hidden + * @param tableId The ID of the table to be checked against + * @param columnIds The list of IDs the check if one is hidden + * @returns true if at least one column under the table is set to hidden + */ +export type IsTableColumnHidden = (tableId: string, ...columnIds: (string | undefined)[]) => boolean; + +const isTableColumnHiddenInjectable = getInjectable({ + id: "is-table-column-hidden", + instantiate: (di): IsTableColumnHidden => { + const state = di.inject(userPreferencesStateInjectable); + + return (tableId, ...columnIds) => { + if (columnIds.length === 0) { + return false; + } + + const config = state.hiddenTableColumns.get(tableId); + + if (!config) { + return false; + } + + return columnIds.some(columnId => columnId && config.has(columnId)); + }; + }, +}); + +export default isTableColumnHiddenInjectable; diff --git a/packages/core/src/common/user-store/kubeconfig-syncs.injectable.ts b/packages/core/src/features/user-preferences/common/kubeconfig-syncs.injectable.ts similarity index 68% rename from packages/core/src/common/user-store/kubeconfig-syncs.injectable.ts rename to packages/core/src/features/user-preferences/common/kubeconfig-syncs.injectable.ts index 7327b9d8e4..db34b53c1f 100644 --- a/packages/core/src/common/user-store/kubeconfig-syncs.injectable.ts +++ b/packages/core/src/features/user-preferences/common/kubeconfig-syncs.injectable.ts @@ -3,11 +3,11 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import userStoreInjectable from "./user-store.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; const kubeconfigSyncsInjectable = getInjectable({ id: "kubeconfig-syncs", - instantiate: (di) => di.inject(userStoreInjectable).syncKubeconfigEntries, + instantiate: (di) => di.inject(userPreferencesStateInjectable).syncKubeconfigEntries, }); export default kubeconfigSyncsInjectable; diff --git a/packages/core/src/common/user-store/lens-color-theme.injectable.ts b/packages/core/src/features/user-preferences/common/lens-color-theme.injectable.ts similarity index 78% rename from packages/core/src/common/user-store/lens-color-theme.injectable.ts rename to packages/core/src/features/user-preferences/common/lens-color-theme.injectable.ts index 5b48de1a37..1ed2930a37 100644 --- a/packages/core/src/common/user-store/lens-color-theme.injectable.ts +++ b/packages/core/src/features/user-preferences/common/lens-color-theme.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import userStoreInjectable from "./user-store.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; export type LensColorThemePreference = { useSystemTheme: true; @@ -16,11 +16,11 @@ export type LensColorThemePreference = { const lensColorThemePreferenceInjectable = getInjectable({ id: "lens-color-theme-preference", instantiate: (di) => { - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); return computed((): LensColorThemePreference => { // TODO: remove magic strings - if (userStore.colorTheme === "system") { + if (state.colorTheme === "system") { return { useSystemTheme: true, }; @@ -28,7 +28,7 @@ const lensColorThemePreferenceInjectable = getInjectable({ return { useSystemTheme: false, - lensThemeId: userStore.colorTheme, + lensThemeId: state.colorTheme, }; }); }, diff --git a/packages/core/src/features/user-preferences/common/migrations-token.ts b/packages/core/src/features/user-preferences/common/migrations-token.ts new file mode 100644 index 0000000000..0da426e3e6 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/migrations-token.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ + +import { getInjectionToken } from "@ogre-tools/injectable"; +import type { MigrationDeclaration } from "../../../common/persistent-storage/migrations.injectable"; + +export const userPreferencesMigrationInjectionToken = getInjectionToken({ + id: "user-preferences-migration-token", +}); diff --git a/packages/core/src/common/user-store/preference-descriptors.injectable.ts b/packages/core/src/features/user-preferences/common/preference-descriptors.injectable.ts similarity index 89% rename from packages/core/src/common/user-store/preference-descriptors.injectable.ts rename to packages/core/src/features/user-preferences/common/preference-descriptors.injectable.ts index 35815dbbea..afb84cb892 100644 --- a/packages/core/src/common/user-store/preference-descriptors.injectable.ts +++ b/packages/core/src/features/user-preferences/common/preference-descriptors.injectable.ts @@ -6,17 +6,17 @@ import { getInjectable } from "@ogre-tools/injectable"; import { merge } from "lodash"; import type { ObservableMap } from "mobx"; import { observable } from "mobx"; -import homeDirectoryPathInjectable from "../os/home-directory-path.injectable"; -import joinPathsInjectable from "../path/join-paths.injectable"; -import { defaultThemeId } from "../vars"; -import currentTimezoneInjectable from "./current-timezone.injectable"; +import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable"; +import joinPathsInjectable from "../../../common/path/join-paths.injectable"; +import { defaultThemeId } from "../../../common/vars"; +import currentTimezoneInjectable from "../../../common/vars/current-timezone.injectable"; import type { EditorConfiguration, ExtensionRegistry, KubeconfigSyncEntry, KubeconfigSyncValue, TerminalConfig } from "./preferences-helpers"; import { defaultExtensionRegistryUrlLocation, defaultEditorConfig, defaultTerminalConfig, defaultPackageMirror, getPreferenceDescriptor, packageMirrors } from "./preferences-helpers"; -export type PreferenceDescriptors = ReturnType; +export type PreferenceDescriptors = ReturnType; -const userStorePreferenceDescriptorsInjectable = getInjectable({ - id: "user-store-preference-descriptors", +const userPreferenceDescriptorsInjectable = getInjectable({ + id: "user-preference-descriptors", instantiate: (di) => { const currentTimezone = di.inject(currentTimezoneInjectable); const joinPaths = di.inject(joinPathsInjectable); @@ -140,4 +140,4 @@ const userStorePreferenceDescriptorsInjectable = getInjectable({ }, }); -export default userStorePreferenceDescriptorsInjectable; +export default userPreferenceDescriptorsInjectable; diff --git a/packages/core/src/common/user-store/preferences-helpers.ts b/packages/core/src/features/user-preferences/common/preferences-helpers.ts similarity index 94% rename from packages/core/src/common/user-store/preferences-helpers.ts rename to packages/core/src/features/user-preferences/common/preferences-helpers.ts index ecb9108bee..1a80dccd28 100644 --- a/packages/core/src/common/user-store/preferences-helpers.ts +++ b/packages/core/src/features/user-preferences/common/preferences-helpers.ts @@ -4,7 +4,7 @@ */ import type { editor } from "monaco-editor"; -import { defaultEditorFontFamily, defaultFontSize, defaultTerminalFontFamily } from "../vars"; +import { defaultFontSize, defaultTerminalFontFamily, defaultEditorFontFamily } from "../../../common/vars"; import type { PreferenceDescriptors } from "./preference-descriptors.injectable"; export interface KubeconfigSyncEntry extends KubeconfigSyncValue { @@ -95,5 +95,5 @@ export type UserStoreFlatModel = { }; export type UserPreferencesModel = { - [field in keyof PreferenceDescriptors]: PreferencesModelType; + [field in keyof PreferenceDescriptors]?: PreferencesModelType; } & { updateChannel: string }; diff --git a/packages/core/src/features/user-preferences/common/reset-theme.injectable.ts b/packages/core/src/features/user-preferences/common/reset-theme.injectable.ts new file mode 100644 index 0000000000..cdff170946 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/reset-theme.injectable.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import userPreferenceDescriptorsInjectable from "./preference-descriptors.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; + +export type ResetTheme = () => void; + +const resetThemeInjectable = getInjectable({ + id: "reset-theme", + instantiate: (di): ResetTheme => { + const state = di.inject(userPreferencesStateInjectable); + const preferenceDescriptors = di.inject(userPreferenceDescriptorsInjectable); + + return action(() => { + state.colorTheme = preferenceDescriptors.colorTheme.fromStore(undefined); + }); + }, +}); + +export default resetThemeInjectable; diff --git a/packages/core/src/common/user-store/shell-setting.injectable.ts b/packages/core/src/features/user-preferences/common/shell-setting.injectable.ts similarity index 63% rename from packages/core/src/common/user-store/shell-setting.injectable.ts rename to packages/core/src/features/user-preferences/common/shell-setting.injectable.ts index f93a4e5874..a4f7a42585 100644 --- a/packages/core/src/common/user-store/shell-setting.injectable.ts +++ b/packages/core/src/features/user-preferences/common/shell-setting.injectable.ts @@ -4,16 +4,16 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import userInfoInjectable from "./user-info.injectable"; -import userStoreInjectable from "./user-store.injectable"; +import userInfoInjectable from "../../../common/vars/user-info.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; const userShellSettingInjectable = getInjectable({ id: "user-shell-setting", instantiate: (di) => { - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); const userInfo = di.inject(userInfoInjectable); - return computed(() => userStore.shell || userInfo.shell); + return computed(() => state.shell || userInfo.shell); }, }); diff --git a/packages/core/src/features/user-preferences/common/state.injectable.ts b/packages/core/src/features/user-preferences/common/state.injectable.ts new file mode 100644 index 0000000000..53f4ac0356 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/state.injectable.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { PreferenceDescriptors } from "./preference-descriptors.injectable"; +import type { StoreType } from "./preferences-helpers"; + +export type UserPreferencesState = { + -readonly [Field in keyof PreferenceDescriptors]: StoreType; +}; + +const userPreferencesStateInjectable = getInjectable({ + id: "user-preferences-state", + instantiate: () => observable.object({} as UserPreferencesState), +}); + +export default userPreferencesStateInjectable; diff --git a/packages/core/src/features/user-preferences/common/storage.injectable.ts b/packages/core/src/features/user-preferences/common/storage.injectable.ts new file mode 100644 index 0000000000..d736c28677 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/storage.injectable.ts @@ -0,0 +1,88 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import prefixedLoggerInjectable from "../../../common/logger/prefixed-logger.injectable"; +import createPersistentStorageInjectable from "../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../common/persistent-storage/migrations.injectable"; +import { userPreferencesMigrationInjectionToken } from "./migrations-token"; +import { toJS } from "../../../common/utils"; +import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; +import selectedUpdateChannelInjectable from "../../application-update/common/selected-update-channel/selected-update-channel.injectable"; +import type { ReleaseChannel } from "../../application-update/common/update-channels"; +import userPreferencesStateInjectable from "./state.injectable"; +import userPreferenceDescriptorsInjectable from "./preference-descriptors.injectable"; +import type { UserPreferencesModel } from "./preferences-helpers"; + +export interface UserStoreModel { + preferences: UserPreferencesModel; +} + +const userPreferencesPersistentStorageInjectable = getInjectable({ + id: "user-preferences-persistent-storage", + instantiate: (di) => { + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + const logger = di.inject(prefixedLoggerInjectable, "USER-PREFERENCES"); + const descriptors = di.inject(userPreferenceDescriptorsInjectable); + const selectedUpdateChannel = di.inject(selectedUpdateChannelInjectable); + const state = di.inject(userPreferencesStateInjectable); + + return createPersistentStorage({ + configName: "lens-user-store", + projectVersion: di.inject(storeMigrationVersionInjectable), + migrations: di.inject(persistentStorageMigrationsInjectable, userPreferencesMigrationInjectionToken), + fromStore: action(({ preferences = {}}) => { + logger.debug("fromStore()", { preferences }); + + state.allowErrorReporting = descriptors.allowErrorReporting.fromStore(preferences.allowErrorReporting); + state.allowUntrustedCAs = descriptors.allowUntrustedCAs.fromStore(preferences.allowUntrustedCAs); + state.colorTheme = descriptors.colorTheme.fromStore(preferences.colorTheme); + state.downloadBinariesPath = descriptors.downloadBinariesPath.fromStore(preferences.downloadBinariesPath); + state.downloadKubectlBinaries = descriptors.downloadKubectlBinaries.fromStore(preferences.downloadKubectlBinaries); + state.downloadMirror = descriptors.downloadMirror.fromStore(preferences.downloadMirror); + state.editorConfiguration = descriptors.editorConfiguration.fromStore(preferences.editorConfiguration); + state.extensionRegistryUrl = descriptors.extensionRegistryUrl.fromStore(preferences.extensionRegistryUrl); + state.hiddenTableColumns = descriptors.hiddenTableColumns.fromStore(preferences.hiddenTableColumns); + state.httpsProxy = descriptors.httpsProxy.fromStore(preferences.httpsProxy); + state.kubectlBinariesPath = descriptors.kubectlBinariesPath.fromStore(preferences.kubectlBinariesPath); + state.localeTimezone = descriptors.localeTimezone.fromStore(preferences.localeTimezone); + state.openAtLogin = descriptors.openAtLogin.fromStore(preferences.openAtLogin); + state.shell = descriptors.shell.fromStore(preferences.shell); + state.syncKubeconfigEntries = descriptors.syncKubeconfigEntries.fromStore(preferences.syncKubeconfigEntries); + state.terminalConfig = descriptors.terminalConfig.fromStore(preferences.terminalConfig); + state.terminalCopyOnSelect = descriptors.terminalCopyOnSelect.fromStore(preferences.terminalCopyOnSelect); + state.terminalTheme = descriptors.terminalTheme.fromStore(preferences.terminalTheme); + + // TODO: Switch to action-based saving instead saving stores by reaction + selectedUpdateChannel.setValue(preferences?.updateChannel as ReleaseChannel); + }), + toJSON: () => toJS({ + preferences: { + allowErrorReporting: descriptors.allowErrorReporting.toStore(state.allowErrorReporting), + allowUntrustedCAs: descriptors.allowUntrustedCAs.toStore(state.allowUntrustedCAs), + colorTheme: descriptors.colorTheme.toStore(state.colorTheme), + downloadBinariesPath: descriptors.downloadBinariesPath.toStore(state.downloadBinariesPath), + downloadKubectlBinaries: descriptors.downloadKubectlBinaries.toStore(state.downloadKubectlBinaries), + downloadMirror: descriptors.downloadMirror.toStore(state.downloadMirror), + editorConfiguration: descriptors.editorConfiguration.toStore(state.editorConfiguration), + extensionRegistryUrl: descriptors.extensionRegistryUrl.toStore(state.extensionRegistryUrl), + hiddenTableColumns: descriptors.hiddenTableColumns.toStore(state.hiddenTableColumns), + httpsProxy: descriptors.httpsProxy.toStore(state.httpsProxy), + kubectlBinariesPath: descriptors.kubectlBinariesPath.toStore(state.kubectlBinariesPath), + localeTimezone: descriptors.localeTimezone.toStore(state.localeTimezone), + openAtLogin: descriptors.openAtLogin.toStore(state.openAtLogin), + shell: descriptors.shell.toStore(state.shell), + syncKubeconfigEntries: descriptors.syncKubeconfigEntries.toStore(state.syncKubeconfigEntries), + terminalConfig: descriptors.terminalConfig.toStore(state.terminalConfig), + terminalCopyOnSelect: descriptors.terminalCopyOnSelect.toStore(state.terminalCopyOnSelect), + terminalTheme: descriptors.terminalTheme.toStore(state.terminalTheme), + updateChannel: selectedUpdateChannel.value.get().id, + }, + }), + }); + }, +}); + +export default userPreferencesPersistentStorageInjectable; diff --git a/packages/core/src/common/user-store/terminal-config.injectable.ts b/packages/core/src/features/user-preferences/common/terminal-config.injectable.ts similarity index 60% rename from packages/core/src/common/user-store/terminal-config.injectable.ts rename to packages/core/src/features/user-preferences/common/terminal-config.injectable.ts index 6f5be75eaf..cfa9cffb3c 100644 --- a/packages/core/src/common/user-store/terminal-config.injectable.ts +++ b/packages/core/src/features/user-preferences/common/terminal-config.injectable.ts @@ -3,16 +3,15 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { computed } from "mobx"; -import { toJS } from "../utils"; -import userStoreInjectable from "./user-store.injectable"; +import { computed, toJS } from "mobx"; +import userPreferencesStateInjectable from "./state.injectable"; const terminalConfigInjectable = getInjectable({ id: "terminal-config", instantiate: (di) => { - const store = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); - return computed(() => toJS(store.terminalConfig)); + return computed(() => toJS(state.terminalConfig)); }, }); diff --git a/packages/core/src/common/user-store/terminal-copy-on-select.injectable.ts b/packages/core/src/features/user-preferences/common/terminal-copy-on-select.injectable.ts similarity index 69% rename from packages/core/src/common/user-store/terminal-copy-on-select.injectable.ts rename to packages/core/src/features/user-preferences/common/terminal-copy-on-select.injectable.ts index 543a4f73b9..494b43039a 100644 --- a/packages/core/src/common/user-store/terminal-copy-on-select.injectable.ts +++ b/packages/core/src/features/user-preferences/common/terminal-copy-on-select.injectable.ts @@ -4,14 +4,14 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import userStoreInjectable from "./user-store.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; const terminalCopyOnSelectInjectable = getInjectable({ id: "terminal-copy-on-select", instantiate: (di) => { - const store = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); - return computed(() => store.terminalCopyOnSelect); + return computed(() => state.terminalCopyOnSelect); }, }); diff --git a/packages/core/src/common/user-store/terminal-theme.injectable.ts b/packages/core/src/features/user-preferences/common/terminal-theme.injectable.ts similarity index 79% rename from packages/core/src/common/user-store/terminal-theme.injectable.ts rename to packages/core/src/features/user-preferences/common/terminal-theme.injectable.ts index a0a00c3253..f9e5c6d6f5 100644 --- a/packages/core/src/common/user-store/terminal-theme.injectable.ts +++ b/packages/core/src/features/user-preferences/common/terminal-theme.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import userStoreInjectable from "./user-store.injectable"; +import userPreferencesStateInjectable from "./state.injectable"; export type TerminalThemePreference = { matchLensTheme: true; @@ -16,11 +16,11 @@ export type TerminalThemePreference = { const terminalThemePreferenceInjectable = getInjectable({ id: "terminal-theme-preference", instantiate: (di) => { - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); return computed((): TerminalThemePreference => { // NOTE: remove use of magic strings - if (!userStore.terminalTheme) { + if (!state.terminalTheme) { return { matchLensTheme: true, }; @@ -28,7 +28,7 @@ const terminalThemePreferenceInjectable = getInjectable({ return { matchLensTheme: false, - themeId: userStore.terminalTheme, + themeId: state.terminalTheme, }; }); }, diff --git a/packages/core/src/features/user-preferences/common/toggle-table-column-visibility.injectable.ts b/packages/core/src/features/user-preferences/common/toggle-table-column-visibility.injectable.ts new file mode 100644 index 0000000000..94d1d07701 --- /dev/null +++ b/packages/core/src/features/user-preferences/common/toggle-table-column-visibility.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getOrInsertSet, toggle } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import userPreferencesStateInjectable from "./state.injectable"; + +export type ToggleTableColumnVisibility = (tableId: string, columnId: string) => void; + +const toggleTableColumnVisibilityInjectable = getInjectable({ + id: "toggle-table-column-visibility", + instantiate: (di): ToggleTableColumnVisibility => { + const state = di.inject(userPreferencesStateInjectable); + + return action((tableId, columnId) => { + toggle(getOrInsertSet(state.hiddenTableColumns, tableId), columnId); + }); + }, +}); + +export default toggleTableColumnVisibilityInjectable; diff --git a/packages/core/src/main/user-store/migrations/5.0.0-alpha.3.injectable.ts b/packages/core/src/features/user-preferences/main/5.0.0-alpha.3.injectable.ts similarity index 70% rename from packages/core/src/main/user-store/migrations/5.0.0-alpha.3.injectable.ts rename to packages/core/src/features/user-preferences/main/5.0.0-alpha.3.injectable.ts index 617f32ad5d..70127386cb 100644 --- a/packages/core/src/main/user-store/migrations/5.0.0-alpha.3.injectable.ts +++ b/packages/core/src/features/user-preferences/main/5.0.0-alpha.3.injectable.ts @@ -5,14 +5,14 @@ // Switch representation of hiddenTableColumns in store import { getInjectable } from "@ogre-tools/injectable"; -import { userStoreMigrationInjectionToken } from "../../../common/user-store/migrations-token"; +import { userPreferencesMigrationInjectionToken } from "../common/migrations-token"; interface PreV500Alpha3UserPreferencesModel { hiddenTableColumns?: Record; } -const v500Alpha3UserStoreMigrationInjectable = getInjectable({ - id: "v5.0.0-alpha.3-user-store-migration", +const v500Alpha3UserPreferencesStorageMigrationInjectable = getInjectable({ + id: "v5.0.0-alpha.3-preferences-storage-migration", instantiate: () => ({ version: "5.0.0-alpha.3", run(store) { @@ -29,8 +29,8 @@ const v500Alpha3UserStoreMigrationInjectable = getInjectable({ }); }, }), - injectionToken: userStoreMigrationInjectionToken, + injectionToken: userPreferencesMigrationInjectionToken, }); -export default v500Alpha3UserStoreMigrationInjectable; +export default v500Alpha3UserPreferencesStorageMigrationInjectable; diff --git a/packages/core/src/main/user-store/migrations/5.0.3-beta.1.injectable.ts b/packages/core/src/features/user-preferences/main/5.0.3-beta.1.injectable.ts similarity index 87% rename from packages/core/src/main/user-store/migrations/5.0.3-beta.1.injectable.ts rename to packages/core/src/features/user-preferences/main/5.0.3-beta.1.injectable.ts index 7739c67e66..d7baa496b0 100644 --- a/packages/core/src/main/user-store/migrations/5.0.3-beta.1.injectable.ts +++ b/packages/core/src/features/user-preferences/main/5.0.3-beta.1.injectable.ts @@ -3,8 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ClusterStoreModel } from "../../../common/cluster-store/cluster-store"; -import type { KubeconfigSyncEntry, UserPreferencesModel } from "../../../common/user-store"; import { isErrnoException } from "@k8slens/utilities"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; @@ -12,14 +10,16 @@ import joinPathsInjectable from "../../../common/path/join-paths.injectable"; import isLogicalChildPathInjectable from "../../../common/path/is-logical-child-path.injectable"; import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable"; import { getInjectable } from "@ogre-tools/injectable"; -import { userStoreMigrationInjectionToken } from "../../../common/user-store/migrations-token"; +import { userPreferencesMigrationInjectionToken } from "../../../features/user-preferences/common/migrations-token"; import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable"; import homeDirectoryPathInjectable from "../../../common/os/home-directory-path.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import pathExistsSyncInjectable from "../../../common/fs/path-exists-sync.injectable"; +import type { ClusterStoreModel } from "../../../features/cluster/storage/common/storage.injectable"; +import type { UserPreferencesModel, KubeconfigSyncEntry } from "../common/preferences-helpers"; -const v503Beta1UserStoreMigrationInjectable = getInjectable({ - id: "v5.0.3-beta.1-user-store-migration", +const v503Beta1UserPreferencesStorageMigrationInjectable = getInjectable({ + id: "v5.0.3-beta.1-preferences-storage-migration", instantiate: (di) => { const userDataPath = di.inject(directoryForUserDataInjectable); const kubeConfigsPath = di.inject(directoryForKubeConfigsInjectable); @@ -85,7 +85,7 @@ const v503Beta1UserStoreMigrationInjectable = getInjectable({ }, }; }, - injectionToken: userStoreMigrationInjectionToken, + injectionToken: userPreferencesMigrationInjectionToken, }); -export default v503Beta1UserStoreMigrationInjectable; +export default v503Beta1UserPreferencesStorageMigrationInjectable; diff --git a/packages/core/src/common/user-store/file-name-migration.injectable.ts b/packages/core/src/features/user-preferences/main/file-name-migration.injectable.ts similarity index 64% rename from packages/core/src/common/user-store/file-name-migration.injectable.ts rename to packages/core/src/features/user-preferences/main/file-name-migration.injectable.ts index 31d5352056..e474e7f5c6 100644 --- a/packages/core/src/common/user-store/file-name-migration.injectable.ts +++ b/packages/core/src/features/user-preferences/main/file-name-migration.injectable.ts @@ -3,28 +3,29 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import fse from "fs-extra"; -import directoryForUserDataInjectable from "../app-paths/directory-for-user-data/directory-for-user-data.injectable"; +import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; import { isErrnoException } from "@k8slens/utilities"; import { getInjectable } from "@ogre-tools/injectable"; -import joinPathsInjectable from "../path/join-paths.injectable"; +import joinPathsInjectable from "../../../common/path/join-paths.injectable"; +import fsInjectable from "../../../common/fs/fs.injectable"; export type UserStoreFileNameMigration = () => Promise; -const userStoreFileNameMigrationInjectable = getInjectable({ - id: "user-store-file-name-migration", +const userPreferencesStorageFileNameMigrationInjectable = getInjectable({ + id: "preferences-storage-file-name-migration", instantiate: (di): UserStoreFileNameMigration => { const userDataPath = di.inject(directoryForUserDataInjectable); const joinPaths = di.inject(joinPathsInjectable); const configJsonPath = joinPaths(userDataPath, "config.json"); const lensUserStoreJsonPath = joinPaths(userDataPath, "lens-user-store.json"); + const { rename, rm } = di.inject(fsInjectable); return async () => { try { - await fse.move(configJsonPath, lensUserStoreJsonPath); + await rename(configJsonPath, lensUserStoreJsonPath); } catch (error) { if (error instanceof Error && error.message === "dest already exists.") { - await fse.remove(configJsonPath); + await rm(configJsonPath); } else if (isErrnoException(error) && error.code === "ENOENT" && error.path === configJsonPath) { // (No such file or directory) return; // file already moved @@ -35,7 +36,6 @@ const userStoreFileNameMigrationInjectable = getInjectable({ } }; }, - causesSideEffects: true, }); -export default userStoreFileNameMigrationInjectable; +export default userPreferencesStorageFileNameMigrationInjectable; diff --git a/packages/core/src/features/user-preferences/main/load-storage.injectable.ts b/packages/core/src/features/user-preferences/main/load-storage.injectable.ts new file mode 100644 index 0000000000..dac1c2aebb --- /dev/null +++ b/packages/core/src/features/user-preferences/main/load-storage.injectable.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; +import initDefaultUpdateChannelInjectable from "../../../main/vars/default-update-channel/init.injectable"; +import userPreferencesPersistentStorageInjectable from "../common/storage.injectable"; +import userPreferencesStorageFileNameMigrationInjectable from "./file-name-migration.injectable"; + +const loadUserPreferencesStorageInjectable = getInjectable({ + id: "load-user-preferences-storage", + instantiate: (di) => ({ + run: async () => { + const storage = di.inject(userPreferencesPersistentStorageInjectable); + const userStoreFileNameMigration = di.inject(userPreferencesStorageFileNameMigrationInjectable); + + await userStoreFileNameMigration(); + storage.loadAndStartSyncing(); + }, + runAfter: initDefaultUpdateChannelInjectable, + }), + injectionToken: beforeApplicationIsLoadingInjectionToken, +}); + +export default loadUserPreferencesStorageInjectable; diff --git a/packages/core/src/main/user-store/sync-open-at-login-with-os.injectable.ts b/packages/core/src/features/user-preferences/main/sync-open-at-login-with-os.injectable.ts similarity index 72% rename from packages/core/src/main/user-store/sync-open-at-login-with-os.injectable.ts rename to packages/core/src/features/user-preferences/main/sync-open-at-login-with-os.injectable.ts index a06d65163e..41f3dff55c 100644 --- a/packages/core/src/main/user-store/sync-open-at-login-with-os.injectable.ts +++ b/packages/core/src/features/user-preferences/main/sync-open-at-login-with-os.injectable.ts @@ -4,18 +4,18 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import setLoginItemSettingsInjectable from "../electron-app/features/set-login-item-settings.injectable"; +import setLoginItemSettingsInjectable from "../../../main/electron-app/features/set-login-item-settings.injectable"; import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; +import userPreferencesStateInjectable from "../common/state.injectable"; const setupSyncOpenAtLoginWithOsInjectable = getInjectable({ id: "setup-sync-open-at-login-with-os", instantiate: (di) => ({ run: () => { const setLoginItemSettings = di.inject(setLoginItemSettingsInjectable); - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); - reaction(() => userStore.openAtLogin, openAtLogin => { + reaction(() => state.openAtLogin, openAtLogin => { setLoginItemSettings({ openAtLogin, openAsHidden: true, diff --git a/packages/core/src/features/user-preferences/renderer/load-storage.injectable.ts b/packages/core/src/features/user-preferences/renderer/load-storage.injectable.ts new file mode 100644 index 0000000000..43e10c1707 --- /dev/null +++ b/packages/core/src/features/user-preferences/renderer/load-storage.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeFrameStartsSecondInjectionToken } from "../../../renderer/before-frame-starts/tokens"; +import initDefaultUpdateChannelInjectable from "../../../renderer/vars/default-update-channel/init.injectable"; +import userPreferencesPersistentStorageInjectable from "../common/storage.injectable"; + +const loadUserPreferencesStorageInjectable = getInjectable({ + id: "load-user-preferences-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(userPreferencesPersistentStorageInjectable); + + return storage.loadAndStartSyncing(); + }, + runAfter: initDefaultUpdateChannelInjectable, + }), + injectionToken: beforeFrameStartsSecondInjectionToken, +}); + +export default loadUserPreferencesStorageInjectable; diff --git a/packages/core/src/features/weblinks/common/add.injectable.ts b/packages/core/src/features/weblinks/common/add.injectable.ts new file mode 100644 index 0000000000..75450d237d --- /dev/null +++ b/packages/core/src/features/weblinks/common/add.injectable.ts @@ -0,0 +1,41 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import weblinksStateInjectable from "./state.injectable"; +import type { WeblinkData } from "./storage.injectable"; +import * as uuid from "uuid"; +import { getOrInsert } from "@k8slens/utilities"; + +export interface WeblinkCreateOptions { + id?: string; + name: string; + url: string; +} + +export type AddWeblink = (data: WeblinkCreateOptions) => WeblinkData; + +const addWeblinkInjectable = getInjectable({ + id: "add-weblink", + instantiate: (di): AddWeblink => { + const state = di.inject(weblinksStateInjectable); + + return action((data) => { + const { + id = uuid.v4(), + name, + url, + } = data; + + if (state.has(id)) { + throw new Error(`There already exists a weblink with id=${id}`); + } + + return getOrInsert(state, id, { id, name, url }); + }); + }, +}); + +export default addWeblinkInjectable; diff --git a/packages/core/src/common/weblinks-store/migration-token.ts b/packages/core/src/features/weblinks/common/migration-token.ts similarity index 77% rename from packages/core/src/common/weblinks-store/migration-token.ts rename to packages/core/src/features/weblinks/common/migration-token.ts index d1cea9334b..a0d8ce58fc 100644 --- a/packages/core/src/common/weblinks-store/migration-token.ts +++ b/packages/core/src/features/weblinks/common/migration-token.ts @@ -4,7 +4,7 @@ */ import { getInjectionToken } from "@ogre-tools/injectable"; -import type { MigrationDeclaration } from "../base-store/migrations.injectable"; +import type { MigrationDeclaration } from "../../../common/persistent-storage/migrations.injectable"; export const weblinkStoreMigrationInjectionToken = getInjectionToken({ id: "weblink-store-migration-token", diff --git a/packages/core/src/features/weblinks/common/remove.injectable.ts b/packages/core/src/features/weblinks/common/remove.injectable.ts new file mode 100644 index 0000000000..0928903561 --- /dev/null +++ b/packages/core/src/features/weblinks/common/remove.injectable.ts @@ -0,0 +1,20 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action } from "mobx"; +import weblinksStateInjectable from "./state.injectable"; + +export type RemoveWeblink = (id: string) => void; + +const removeWeblinkInjectable = getInjectable({ + id: "remove-weblink", + instantiate: (di): RemoveWeblink => { + const state = di.inject(weblinksStateInjectable); + + return action((id) => state.delete(id)); + }, +}); + +export default removeWeblinkInjectable; diff --git a/packages/core/src/features/weblinks/common/state.injectable.ts b/packages/core/src/features/weblinks/common/state.injectable.ts new file mode 100644 index 0000000000..ab909050aa --- /dev/null +++ b/packages/core/src/features/weblinks/common/state.injectable.ts @@ -0,0 +1,14 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { WeblinkData } from "./storage.injectable"; + +const weblinksStateInjectable = getInjectable({ + id: "weblinks-state", + instantiate: () => observable.map(), +}); + +export default weblinksStateInjectable; diff --git a/packages/core/src/features/weblinks/common/storage.injectable.ts b/packages/core/src/features/weblinks/common/storage.injectable.ts new file mode 100644 index 0000000000..6b6c668b83 --- /dev/null +++ b/packages/core/src/features/weblinks/common/storage.injectable.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { action, comparer, toJS } from "mobx"; +import createPersistentStorageInjectable from "../../../common/persistent-storage/create.injectable"; +import persistentStorageMigrationsInjectable from "../../../common/persistent-storage/migrations.injectable"; +import storeMigrationVersionInjectable from "../../../common/vars/store-migration-version.injectable"; +import { weblinkStoreMigrationInjectionToken } from "./migration-token"; +import weblinksStateInjectable from "./state.injectable"; + +export interface WeblinkData { + id: string; + name: string; + url: string; +} + +export interface WeblinkStoreModel { + weblinks: WeblinkData[]; +} + +const weblinksPersistentStorageInjectable = getInjectable({ + id: "weblinks-persistent-storage", + instantiate: (di) => { + const state = di.inject(weblinksStateInjectable); + const createPersistentStorage = di.inject(createPersistentStorageInjectable); + + return createPersistentStorage({ + configName: "lens-weblink-store", + accessPropertiesByDotNotation: false, // To make dots safe in cluster context names + syncOptions: { + equals: comparer.structural, + }, + projectVersion: di.inject(storeMigrationVersionInjectable), + migrations: di.inject(persistentStorageMigrationsInjectable, weblinkStoreMigrationInjectionToken), + fromStore: action(({ weblinks = [] }) => { + state.replace(weblinks.map(weblink => [weblink.id, weblink])); + }), + toJSON: () => ({ + weblinks: [...toJS(state).values()], + }), + }); + }, +}); + +export default weblinksPersistentStorageInjectable; diff --git a/packages/core/src/features/weblinks/common/weblinks.injectable.ts b/packages/core/src/features/weblinks/common/weblinks.injectable.ts new file mode 100644 index 0000000000..a4dca05caf --- /dev/null +++ b/packages/core/src/features/weblinks/common/weblinks.injectable.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { computed } from "mobx"; +import weblinksStateInjectable from "./state.injectable"; + +const weblinksInjectable = getInjectable({ + id: "weblinks", + instantiate: (di) => { + const state = di.inject(weblinksStateInjectable); + + return computed(() => [...state.values()]); + }, +}); + +export default weblinksInjectable; diff --git a/packages/core/src/main/weblinks-store/migrations/5.1.4.injectable.ts b/packages/core/src/features/weblinks/main/5.1.4.injectable.ts similarity index 86% rename from packages/core/src/main/weblinks-store/migrations/5.1.4.injectable.ts rename to packages/core/src/features/weblinks/main/5.1.4.injectable.ts index 000937abba..db8eb1510b 100644 --- a/packages/core/src/main/weblinks-store/migrations/5.1.4.injectable.ts +++ b/packages/core/src/features/weblinks/main/5.1.4.injectable.ts @@ -4,10 +4,10 @@ */ import { docsUrl, forumsUrl } from "../../../common/vars"; -import type { WeblinkData } from "../../../common/weblinks-store/weblink-store"; import { getInjectable } from "@ogre-tools/injectable"; -import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token"; -import * as links from "../links"; +import { weblinkStoreMigrationInjectionToken } from "../common/migration-token"; +import * as links from "./links"; +import type { WeblinkData } from "../common/storage.injectable"; const v514WeblinkStoreMigrationInjectable = getInjectable({ id: "v5.1.4-weblink-store-migration", diff --git a/packages/core/src/main/weblinks-store/migrations/5.4.5-beta.1.injectable.ts b/packages/core/src/features/weblinks/main/5.4.5-beta.1.injectable.ts similarity index 89% rename from packages/core/src/main/weblinks-store/migrations/5.4.5-beta.1.injectable.ts rename to packages/core/src/features/weblinks/main/5.4.5-beta.1.injectable.ts index fc67e4a2f5..01f252deb1 100644 --- a/packages/core/src/main/weblinks-store/migrations/5.4.5-beta.1.injectable.ts +++ b/packages/core/src/features/weblinks/main/5.4.5-beta.1.injectable.ts @@ -3,10 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { WeblinkData } from "../../../common/weblinks-store/weblink-store"; -import * as links from "../links"; +import * as links from "../../../features/weblinks/main/links"; import { getInjectable } from "@ogre-tools/injectable"; -import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token"; +import { weblinkStoreMigrationInjectionToken } from "../../../features/weblinks/common/migration-token"; +import type { WeblinkData } from "../common/storage.injectable"; const v545Beta1WeblinkStoreMigrationInjectable = getInjectable({ id: "v5.4.5-beta.1-weblink-store-migration", diff --git a/packages/core/src/main/weblinks-store/migrations/currentVersion.injectable.ts b/packages/core/src/features/weblinks/main/currentVersion.injectable.ts similarity index 87% rename from packages/core/src/main/weblinks-store/migrations/currentVersion.injectable.ts rename to packages/core/src/features/weblinks/main/currentVersion.injectable.ts index 4b873d78c4..dfb2946164 100644 --- a/packages/core/src/main/weblinks-store/migrations/currentVersion.injectable.ts +++ b/packages/core/src/features/weblinks/main/currentVersion.injectable.ts @@ -4,11 +4,11 @@ */ import { docsUrl, forumsUrl } from "../../../common/vars"; -import type { WeblinkData } from "../../../common/weblinks-store/weblink-store"; import { getInjectable } from "@ogre-tools/injectable"; -import { weblinkStoreMigrationInjectionToken } from "../../../common/weblinks-store/migration-token"; -import { lensDocumentationWeblinkId, lensForumsWeblinkId } from "../links"; +import { weblinkStoreMigrationInjectionToken } from "../../../features/weblinks/common/migration-token"; +import { lensDocumentationWeblinkId, lensForumsWeblinkId } from "../../../features/weblinks/main/links"; import { applicationInformationToken } from "@k8slens/application"; +import type { WeblinkData } from "../common/storage.injectable"; const currentVersionWeblinkStoreMigrationInjectable = getInjectable({ id: "current-version-weblink-store-migration", diff --git a/packages/core/src/main/weblinks-store/links.ts b/packages/core/src/features/weblinks/main/links.ts similarity index 100% rename from packages/core/src/main/weblinks-store/links.ts rename to packages/core/src/features/weblinks/main/links.ts diff --git a/packages/core/src/main/start-main-application/runnables/setup-syncing-of-weblinks.injectable.ts b/packages/core/src/features/weblinks/main/load-storage.injectable.ts similarity index 55% rename from packages/core/src/main/start-main-application/runnables/setup-syncing-of-weblinks.injectable.ts rename to packages/core/src/features/weblinks/main/load-storage.injectable.ts index 09a630db37..e309381bbd 100644 --- a/packages/core/src/main/start-main-application/runnables/setup-syncing-of-weblinks.injectable.ts +++ b/packages/core/src/features/weblinks/main/load-storage.injectable.ts @@ -2,22 +2,20 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { getInjectable } from "@ogre-tools/injectable"; import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; -import syncWeblinksInjectable from "../../catalog-sources/sync-weblinks.injectable"; - -const setupSyncingOfWeblinksInjectable = getInjectable({ - id: "setup-syncing-of-weblinks", +import { getInjectable } from "@ogre-tools/injectable"; +import weblinksPersistentStorageInjectable from "../common/storage.injectable"; +const loadWeblinkStorageInjectable = getInjectable({ + id: "load-weblink-storage", instantiate: (di) => ({ run: () => { - const syncWeblinks = di.inject(syncWeblinksInjectable); + const storage = di.inject(weblinksPersistentStorageInjectable); - syncWeblinks(); + storage.loadAndStartSyncing(); }, }), - injectionToken: onLoadOfApplicationInjectionToken, }); -export default setupSyncingOfWeblinksInjectable; +export default loadWeblinkStorageInjectable; diff --git a/packages/core/src/features/weblinks/main/setup-syncing-of-weblinks.injectable.ts b/packages/core/src/features/weblinks/main/setup-syncing-of-weblinks.injectable.ts new file mode 100644 index 0000000000..1f6b440904 --- /dev/null +++ b/packages/core/src/features/weblinks/main/setup-syncing-of-weblinks.injectable.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; +import weblinkVerificationStartableStoppableInjectable from "./weblink-verification.injectable"; +import catalogEntityRegistryInjectable from "../../../main/catalog/entity-registry.injectable"; +import weblinkVerificationsInjectable from "./weblink-verifications.injectable"; +import { computed } from "mobx"; +import { iter } from "@k8slens/utilities"; + +const setupSyncingOfWeblinksInjectable = getInjectable({ + id: "setup-syncing-of-weblinks", + + instantiate: (di) => ({ + run: () => { + const weblinkVerificationStartableStoppable = di.inject(weblinkVerificationStartableStoppableInjectable); + const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); + const weblinkVerifications = di.inject(weblinkVerificationsInjectable); + + weblinkVerificationStartableStoppable.start(); + catalogEntityRegistry.addComputedSource("weblinks", computed(() => ( + iter.chain(weblinkVerifications.values()) + .map(([weblink]) => weblink) + .toArray() + ))); + }, + }), + + injectionToken: onLoadOfApplicationInjectionToken, +}); + +export default setupSyncingOfWeblinksInjectable; diff --git a/packages/core/src/features/weblinks/main/stop-validating-weblinks.injectable.ts b/packages/core/src/features/weblinks/main/stop-validating-weblinks.injectable.ts new file mode 100644 index 0000000000..221a3ee0dd --- /dev/null +++ b/packages/core/src/features/weblinks/main/stop-validating-weblinks.injectable.ts @@ -0,0 +1,23 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeQuitOfBackEndInjectionToken } from "../../../main/start-main-application/runnable-tokens/phases"; +import weblinkVerificationStartableStoppableInjectable from "./weblink-verification.injectable"; + +const stopValidatingWeblinksInjectable = getInjectable({ + id: "stop-validating-weblinks", + instantiate: (di) => ({ + run: () => { + const weblinkVerificationStartableStoppable = di.inject(weblinkVerificationStartableStoppableInjectable); + + weblinkVerificationStartableStoppable.stop(); + + return undefined; + }, + }), + injectionToken: beforeQuitOfBackEndInjectionToken, +}); + +export default stopValidatingWeblinksInjectable; diff --git a/packages/core/src/common/user-store/file-name-migration.global-override-for-injectable.ts b/packages/core/src/features/weblinks/main/validate-weblink.global-override-for-injectable.ts similarity index 53% rename from packages/core/src/common/user-store/file-name-migration.global-override-for-injectable.ts rename to packages/core/src/features/weblinks/main/validate-weblink.global-override-for-injectable.ts index 0cd1383cc6..8ec03bebaf 100644 --- a/packages/core/src/common/user-store/file-name-migration.global-override-for-injectable.ts +++ b/packages/core/src/features/weblinks/main/validate-weblink.global-override-for-injectable.ts @@ -4,6 +4,6 @@ */ import { getGlobalOverride } from "@k8slens/test-utils"; -import userStoreFileNameMigrationInjectable from "./file-name-migration.injectable"; +import validateWeblinkInjectable from "./validate-weblink.injectable"; -export default getGlobalOverride(userStoreFileNameMigrationInjectable, () => async () => {}); +export default getGlobalOverride(validateWeblinkInjectable, () => async () => "available"); diff --git a/packages/core/src/features/weblinks/main/validate-weblink.injectable.ts b/packages/core/src/features/weblinks/main/validate-weblink.injectable.ts new file mode 100644 index 0000000000..c38b483a95 --- /dev/null +++ b/packages/core/src/features/weblinks/main/validate-weblink.injectable.ts @@ -0,0 +1,42 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { chainSignal } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import fetchInjectable from "../../../common/fetch/fetch.injectable"; +import { withTimeout } from "../../../common/fetch/timeout-controller"; + +export type ValidateWeblink = (url: string, signal: AbortSignal) => Promise<"available" | "unavailable">; + +const validateWeblinkInjectable = getInjectable({ + id: "validate-weblink", + instantiate: (di): ValidateWeblink => { + const fetch = di.inject(fetchInjectable); + + return async (url, signal) => { + const timeout = withTimeout(20_000); + + chainSignal(timeout, signal); + + try { + const res = await fetch(url, { + signal: timeout.signal, + }); + + if (res.status >= 200 && res.status < 500) { + return "available"; + } + } catch { + // ignore + } finally { + timeout.abort(); + } + + return "unavailable"; + }; + }, + causesSideEffects: true, +}); + +export default validateWeblinkInjectable; diff --git a/packages/core/src/features/weblinks/main/weblink-verification.injectable.ts b/packages/core/src/features/weblinks/main/weblink-verification.injectable.ts new file mode 100644 index 0000000000..991575de46 --- /dev/null +++ b/packages/core/src/features/weblinks/main/weblink-verification.injectable.ts @@ -0,0 +1,105 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getStartableStoppable } from "@k8slens/startable-stoppable"; +import type { Disposer } from "@k8slens/utilities"; +import { delay, disposer } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { random } from "lodash"; +import { reaction, runInAction } from "mobx"; +import { WebLink } from "../../../common/catalog-entities"; +import weblinksInjectable from "../common/weblinks.injectable"; +import validateWeblinkInjectable from "./validate-weblink.injectable"; +import weblinkVerificationsInjectable from "./weblink-verifications.injectable"; + +const sixtyMinutes = 60 * 60 * 1000; + +const weblinkVerificationStartableStoppableInjectable = getInjectable({ + id: "weblink-verification-startable-stoppable", + instantiate: (di) => { + const weblinkVerifications = di.inject(weblinkVerificationsInjectable); + const validateWeblink = di.inject(validateWeblinkInjectable); + const weblinks = di.inject(weblinksInjectable); + + const startPeriodicallyCheckingWebLink = (link: WebLink): Disposer => { + const controller = new AbortController(); + const dispose = disposer(() => controller.abort()); + + void (async () => { + for (;;) { + const newStatus = await validateWeblink(link.spec.url, controller.signal); + + runInAction(() => { + link.status.phase = newStatus; + }); + + const nextCheckAfter = sixtyMinutes + random(0, 5 * 60 * 1000, false); + + await delay(nextCheckAfter, controller.signal); + + if (controller.signal.aborted) { + return; + } + } + })(); + + return dispose; + }; + + return getStartableStoppable("weblink-verification", () => disposer( + reaction( + () => weblinks.get(), + (links) => { + const seenWeblinks = new Set(); + + for (const weblinkData of links) { + seenWeblinks.add(weblinkData.id); + + if (!weblinkVerifications.has(weblinkData.id)) { + const link = new WebLink({ + metadata: { + uid: weblinkData.id, + name: weblinkData.name, + source: "local", + labels: {}, + }, + spec: { + url: weblinkData.url, + }, + status: { + phase: "available", + active: true, + }, + }); + + weblinkVerifications.set(weblinkData.id, [ + link, + startPeriodicallyCheckingWebLink(link), + ]); + } + } + + // Stop checking and remove weblinks that are no longer in the store + for (const [weblinkId, [, disposer]] of weblinkVerifications) { + if (!seenWeblinks.has(weblinkId)) { + disposer(); + weblinkVerifications.delete(weblinkId); + } + } + }, + { + fireImmediately: true, + }, + ), + () => { + // Stop the validations + for (const [, [, disposer]] of weblinkVerifications) { + disposer(); + } + }, + )); + }, +}); + +export default weblinkVerificationStartableStoppableInjectable; diff --git a/packages/core/src/features/weblinks/main/weblink-verifications.injectable.ts b/packages/core/src/features/weblinks/main/weblink-verifications.injectable.ts new file mode 100644 index 0000000000..6e3f7d8f0f --- /dev/null +++ b/packages/core/src/features/weblinks/main/weblink-verifications.injectable.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import type { Disposer } from "@k8slens/utilities"; +import { getInjectable } from "@ogre-tools/injectable"; +import { observable } from "mobx"; +import type { WebLink } from "../../../common/catalog-entities"; + +const weblinkVerificationsInjectable = getInjectable({ + id: "weblink-verifications", + instantiate: () => observable.map(), +}); + +export default weblinkVerificationsInjectable; diff --git a/packages/core/src/features/weblinks/renderer/load-storage.injectable.ts b/packages/core/src/features/weblinks/renderer/load-storage.injectable.ts new file mode 100644 index 0000000000..3cfacc3fa3 --- /dev/null +++ b/packages/core/src/features/weblinks/renderer/load-storage.injectable.ts @@ -0,0 +1,21 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import { beforeFrameStartsSecondInjectionToken } from "../../../renderer/before-frame-starts/tokens"; +import weblinksPersistentStorageInjectable from "../common/storage.injectable"; + +const loadWeblinkStorageInjectable = getInjectable({ + id: "load-weblink-storage", + instantiate: (di) => ({ + run: () => { + const storage = di.inject(weblinksPersistentStorageInjectable); + + storage.loadAndStartSyncing(); + }, + }), + injectionToken: beforeFrameStartsSecondInjectionToken, +}); + +export default loadWeblinkStorageInjectable; diff --git a/packages/core/src/features/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap b/packages/core/src/features/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap index 4016a617c3..2ff50d8b1a 100644 --- a/packages/core/src/features/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap +++ b/packages/core/src/features/welcome/__snapshots__/navigation-using-application-menu.test.ts.snap @@ -230,7 +230,7 @@ exports[`welcome - navigation using application menu renders 1`] = ` class="HotbarSelector" > false, - injectionToken: shouldBaseStoreDisableSyncInIpcListenerInjectionToken, + injectionToken: shouldPersistentStorageDisableSyncInIpcListenerInjectionToken, }); export default shouldBaseStoreDisableSyncInIpcListenerInjectable; diff --git a/packages/core/src/main/base-store/ipc-channel-prefix.injectable.ts b/packages/core/src/main/base-store/ipc-channel-prefix.injectable.ts index 635f857fc4..a0a96ce3af 100644 --- a/packages/core/src/main/base-store/ipc-channel-prefix.injectable.ts +++ b/packages/core/src/main/base-store/ipc-channel-prefix.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix"; +import { persistentStorageIpcChannelPrefixesInjectionToken } from "../../common/persistent-storage/channel-prefix"; const baseStoreIpcChannelPrefixInjectable = getInjectable({ id: "base-store-ipc-channel-prefix", @@ -11,7 +11,7 @@ const baseStoreIpcChannelPrefixInjectable = getInjectable({ local: "store-sync-main", remote: "store-sync-renderer", }), - injectionToken: baseStoreIpcChannelPrefixesInjectionToken, + injectionToken: persistentStorageIpcChannelPrefixesInjectionToken, }); export default baseStoreIpcChannelPrefixInjectable; diff --git a/packages/core/src/main/base-store/persist-state-to-config.injectable.ts b/packages/core/src/main/base-store/persist-state-to-config.injectable.ts index f6d28982ec..7869bbd6c5 100644 --- a/packages/core/src/main/base-store/persist-state-to-config.injectable.ts +++ b/packages/core/src/main/base-store/persist-state-to-config.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file"; +import { persistStateToConfigInjectionToken } from "../../common/persistent-storage/save-to-file"; import loggerInjectable from "../../common/logger.injectable"; const persistStateToConfigInjectable = getInjectable({ diff --git a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts index 42ffbedf30..6d23610e79 100644 --- a/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts +++ b/packages/core/src/main/catalog-sources/__test__/kubeconfig-sync.test.ts @@ -17,9 +17,6 @@ import type { ConfigToModels } from "../kubeconfig-sync/config-to-models.injecta import configToModelsInjectable from "../kubeconfig-sync/config-to-models.injectable"; import kubeconfigSyncManagerInjectable from "../kubeconfig-sync/manager.injectable"; import type { KubeconfigSyncManager } from "../kubeconfig-sync/manager"; -import type { KubeconfigSyncValue } from "../../../common/user-store"; -import kubeconfigSyncsInjectable from "../../../common/user-store/kubeconfig-syncs.injectable"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import type { AsyncFnMock } from "@async-fn/jest"; import type { Stat } from "../../../common/fs/stat.injectable"; @@ -36,12 +33,13 @@ import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable"; import type { KubeconfigManager } from "../../kubeconfig-manager/kubeconfig-manager"; import kubeconfigManagerInjectable from "../../kubeconfig-manager/kubeconfig-manager.injectable"; +import type { KubeconfigSyncValue } from "../../../features/user-preferences/common/preferences-helpers"; +import kubeconfigSyncsInjectable from "../../../features/user-preferences/common/kubeconfig-syncs.injectable"; describe("kubeconfig-sync.source tests", () => { let computeKubeconfigDiff: ComputeKubeconfigDiff; let configToModels: ConfigToModels; let kubeconfigSyncs: ObservableMap; - let clusters: Map; let di: DiContainer; beforeEach(async () => { @@ -58,9 +56,6 @@ describe("kubeconfig-sync.source tests", () => { ensurePath: async () => "/some-proxy-kubeconfig-file", } as Partial as KubeconfigManager)); - clusters = new Map(); - di.override(getClusterByIdInjectable, () => id => clusters.get(id)); - kubeconfigSyncs = observable.map(); di.override(kubeconfigSyncsInjectable, () => kubeconfigSyncs); diff --git a/packages/core/src/main/catalog-sources/index.ts b/packages/core/src/main/catalog-sources/index.ts deleted file mode 100644 index 98c3f08536..0000000000 --- a/packages/core/src/main/catalog-sources/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -export { syncWeblinks } from "./weblinks"; diff --git a/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts b/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts index 3b81e82382..87475931fe 100644 --- a/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts +++ b/packages/core/src/main/catalog-sources/kubeconfig-sync/compute-diff.injectable.ts @@ -9,7 +9,6 @@ import { action } from "mobx"; import { homedir } from "os"; import directoryForKubeConfigsInjectable from "../../../common/app-paths/directory-for-kube-configs/directory-for-kube-configs.injectable"; import type { CatalogEntity } from "../../../common/catalog"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import { Cluster } from "../../../common/cluster/cluster"; import { loadConfigFromString } from "../../../common/kube-helpers"; import clustersThatAreBeingDeletedInjectable from "../../cluster/are-being-deleted.injectable"; @@ -17,6 +16,7 @@ import { catalogEntityFromCluster } from "../../cluster/manager"; import configToModelsInjectable from "./config-to-models.injectable"; import kubeconfigSyncLoggerInjectable from "./logger.injectable"; import clusterConnectionInjectable from "../../cluster/cluster-connection.injectable"; +import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; export type ComputeKubeconfigDiff = (contents: string, source: ObservableMap, filePath: string) => void; diff --git a/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.injectable.ts b/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.injectable.ts index 06265764c8..9160df83da 100644 --- a/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.injectable.ts +++ b/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.injectable.ts @@ -7,7 +7,7 @@ import directoryForKubeConfigsInjectable from "../../../common/app-paths/directo import { KubeconfigSyncManager } from "./manager"; import kubeconfigSyncLoggerInjectable from "./logger.injectable"; import watchKubeconfigFileChangesInjectable from "./watch-file-changes.injectable"; -import kubeconfigSyncsInjectable from "../../../common/user-store/kubeconfig-syncs.injectable"; +import kubeconfigSyncsInjectable from "../../../features/user-preferences/common/kubeconfig-syncs.injectable"; const kubeconfigSyncManagerInjectable = getInjectable({ id: "kubeconfig-sync-manager", diff --git a/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.ts b/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.ts index 77ef96b765..c7b53d8973 100644 --- a/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.ts +++ b/packages/core/src/main/catalog-sources/kubeconfig-sync/manager.ts @@ -8,9 +8,9 @@ import { action, observable, computed, makeObservable, observe } from "mobx"; import type { CatalogEntity } from "../../../common/catalog"; import type { Disposer } from "@k8slens/utilities"; import { iter } from "@k8slens/utilities"; -import type { KubeconfigSyncValue } from "../../../common/user-store"; import type { Logger } from "../../../common/logger"; import type { WatchKubeconfigFileChanges } from "./watch-file-changes.injectable"; +import type { KubeconfigSyncValue } from "../../../features/user-preferences/common/preferences-helpers"; interface KubeconfigSyncManagerDependencies { readonly directoryForKubeConfigs: string; diff --git a/packages/core/src/main/catalog-sources/sync-weblinks.injectable.ts b/packages/core/src/main/catalog-sources/sync-weblinks.injectable.ts deleted file mode 100644 index f75de34a9d..0000000000 --- a/packages/core/src/main/catalog-sources/sync-weblinks.injectable.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import { syncWeblinks } from "./weblinks"; -import weblinkStoreInjectable from "../../common/weblinks-store/weblink-store.injectable"; -import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable"; - -const syncWeblinksInjectable = getInjectable({ - id: "sync-weblinks", - - instantiate: (di) => syncWeblinks({ - weblinkStore: di.inject(weblinkStoreInjectable), - catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), - }), -}); - -export default syncWeblinksInjectable; diff --git a/packages/core/src/main/catalog-sources/weblinks.ts b/packages/core/src/main/catalog-sources/weblinks.ts deleted file mode 100644 index 2ff2b645a1..0000000000 --- a/packages/core/src/main/catalog-sources/weblinks.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import { computed, observable, reaction } from "mobx"; -import type { WeblinkStore } from "../../common/weblinks-store/weblink-store"; -import { WebLink } from "../../common/catalog-entities"; -import type { CatalogEntityRegistry } from "../catalog"; -import got from "got"; -import type { Disposer } from "@k8slens/utilities"; -import { random } from "lodash"; - -async function validateLink(link: WebLink) { - try { - const response = await got.get(link.spec.url, { - throwHttpErrors: false, - timeout: 20_000, - }); - - if (response.statusCode >= 200 && response.statusCode < 500) { - link.status.phase = "available"; - } else { - link.status.phase = "unavailable"; - } - } catch { - link.status.phase = "unavailable"; - } -} - -interface Dependencies { - weblinkStore: WeblinkStore; - catalogEntityRegistry: CatalogEntityRegistry; -} - -export const syncWeblinks = ({ weblinkStore, catalogEntityRegistry }: Dependencies) => () => { - const webLinkEntities = observable.map(); - - function periodicallyCheckLink(link: WebLink): Disposer { - validateLink(link); - - let interval: NodeJS.Timeout; - const timeout = setTimeout(() => { - interval = setInterval(() => validateLink(link), 60 * 60 * 1000); // every 60 minutes - }, random(0, 5 * 60 * 1000, false)); // spread out over 5 minutes - - return () => { - clearTimeout(timeout); - clearInterval(interval); - }; - } - - reaction(() => weblinkStore.weblinks, (links) => { - const seenWeblinks = new Set(); - - for (const weblinkData of links) { - seenWeblinks.add(weblinkData.id); - - if (!webLinkEntities.has(weblinkData.id)) { - const link = new WebLink({ - metadata: { - uid: weblinkData.id, - name: weblinkData.name, - source: "local", - labels: {}, - }, - spec: { - url: weblinkData.url, - }, - status: { - phase: "available", - active: true, - }, - }); - - webLinkEntities.set(weblinkData.id, [ - link, - periodicallyCheckLink(link), - ]); - } - } - - // Stop checking and remove weblinks that are no longer in the store - for (const [weblinkId, [, disposer]] of webLinkEntities) { - if (!seenWeblinks.has(weblinkId)) { - disposer(); - webLinkEntities.delete(weblinkId); - } - } - }, { fireImmediately: true }); - - catalogEntityRegistry.addComputedSource("weblinks", computed(() => Array.from(webLinkEntities.values(), ([link]) => link))); -}; diff --git a/packages/core/src/main/cluster/manager.injectable.ts b/packages/core/src/main/cluster/manager.injectable.ts index e1567e51b6..21530dfe4d 100644 --- a/packages/core/src/main/cluster/manager.injectable.ts +++ b/packages/core/src/main/cluster/manager.injectable.ts @@ -3,8 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable"; import loggerInjectable from "../../common/logger.injectable"; +import addClusterInjectable from "../../features/cluster/storage/common/add.injectable"; +import clustersInjectable from "../../features/cluster/storage/common/clusters.injectable"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; import catalogEntityRegistryInjectable from "../catalog/entity-registry.injectable"; import clustersThatAreBeingDeletedInjectable from "./are-being-deleted.injectable"; import clusterConnectionInjectable from "./cluster-connection.injectable"; @@ -17,11 +19,13 @@ const clusterManagerInjectable = getInjectable({ id: "cluster-manager", instantiate: (di) => new ClusterManager({ - store: di.inject(clusterStoreInjectable), catalogEntityRegistry: di.inject(catalogEntityRegistryInjectable), clustersThatAreBeingDeleted: di.inject(clustersThatAreBeingDeletedInjectable), visibleCluster: di.inject(visibleClusterInjectable), logger: di.inject(loggerInjectable), + addCluster: di.inject(addClusterInjectable), + clusters: di.inject(clustersInjectable), + getClusterById: di.inject(getClusterByIdInjectable), updateEntityMetadata: di.inject(updateEntityMetadataInjectable), updateEntitySpec: di.inject(updateEntitySpecInjectable), getClusterConnection: (cluster) => di.inject(clusterConnectionInjectable, cluster), diff --git a/packages/core/src/main/cluster/manager.ts b/packages/core/src/main/cluster/manager.ts index bc270b79f1..f9f844bd9f 100644 --- a/packages/core/src/main/cluster/manager.ts +++ b/packages/core/src/main/cluster/manager.ts @@ -4,34 +4,37 @@ */ import "../../common/ipc/cluster"; -import type { IObservableValue, ObservableSet } from "mobx"; +import type { IComputedValue, IObservableValue, ObservableSet } from "mobx"; import { action, makeObservable, observe, reaction, toJS } from "mobx"; import type { Cluster } from "../../common/cluster/cluster"; import { isErrnoException } from "@k8slens/utilities"; import { isKubernetesCluster, KubernetesCluster, LensKubernetesClusterStatus } from "../../common/catalog-entities/kubernetes-cluster"; import { ipcMainOn } from "../../common/ipc"; import { once } from "lodash"; -import type { ClusterStore } from "../../common/cluster-store/cluster-store"; import type { ClusterId } from "../../common/cluster-types"; import type { CatalogEntityRegistry } from "../catalog"; import type { Logger } from "../../common/logger"; import type { UpdateEntityMetadata } from "./update-entity-metadata.injectable"; import type { UpdateEntitySpec } from "./update-entity-spec.injectable"; import type { ClusterConnection } from "./cluster-connection.injectable"; +import type { GetClusterById } from "../../features/cluster/storage/common/get-by-id.injectable"; +import type { AddCluster } from "../../features/cluster/storage/common/add.injectable"; const logPrefix = "[CLUSTER-MANAGER]:"; const lensSpecificClusterStatuses: Set = new Set(Object.values(LensKubernetesClusterStatus)); interface Dependencies { - readonly store: ClusterStore; readonly catalogEntityRegistry: CatalogEntityRegistry; readonly clustersThatAreBeingDeleted: ObservableSet; readonly visibleCluster: IObservableValue; readonly logger: Logger; + readonly clusters: IComputedValue; updateEntityMetadata: UpdateEntityMetadata; updateEntitySpec: UpdateEntitySpec; getClusterConnection: (cluster: Cluster) => ClusterConnection; + getClusterById: GetClusterById; + addCluster: AddCluster; } export class ClusterManager { @@ -42,15 +45,15 @@ export class ClusterManager { init = once(() => { // reacting to every cluster's state change and total amount of items reaction( - () => this.dependencies.store.clustersList.map(c => c.getState()), - () => this.updateCatalog(this.dependencies.store.clustersList), + () => this.dependencies.clusters.get().map(c => c.getState()), + () => this.updateCatalog(this.dependencies.clusters.get()), { fireImmediately: false }, ); // reacting to every cluster's preferences change and total amount of items reaction( - () => this.dependencies.store.clustersList.map(c => toJS(c.preferences)), - () => this.updateCatalog(this.dependencies.store.clustersList), + () => this.dependencies.clusters.get().map(c => toJS(c.preferences)), + () => this.updateCatalog(this.dependencies.clusters.get()), { fireImmediately: false }, ); @@ -152,7 +155,7 @@ export class ClusterManager { @action protected syncClustersFromCatalog(entities: KubernetesCluster[]) { for (const entity of entities) { - const cluster = this.dependencies.store.getById(entity.getId()); + const cluster = this.dependencies.getClusterById(entity.getId()); if (!cluster) { const model = { @@ -167,7 +170,7 @@ export class ClusterManager { * Add the bare minimum of data to ClusterStore. And especially no * preferences, as those might be configured by the entity's source */ - this.dependencies.store.addCluster(model); + this.dependencies.addCluster(model); } catch (error) { if (isErrnoException(error) && error.code === "ENOENT" && error.path === entity.spec.kubeconfigPath) { this.dependencies.logger.warn(`${logPrefix} kubeconfig file disappeared`, model); @@ -208,7 +211,9 @@ export class ClusterManager { this.dependencies.logger.info(`${logPrefix} network is offline`); await Promise.allSettled( - this.dependencies.store.clustersList + this.dependencies + .clusters + .get() .filter(cluster => !cluster.disconnected.get()) .map(async (cluster) => { cluster.online.set(false); @@ -225,7 +230,9 @@ export class ClusterManager { this.dependencies.logger.info(`${logPrefix} network is online`); await Promise.allSettled( - this.dependencies.store.clustersList + this.dependencies + .clusters + .get() .filter(cluster => !cluster.disconnected.get()) .map((cluster) => ( this.dependencies @@ -236,7 +243,7 @@ export class ClusterManager { }; stop() { - for (const cluster of this.dependencies.store.clustersList) { + for (const cluster of this.dependencies.clusters.get()) { this.dependencies .getClusterConnection(cluster) .disconnect(); diff --git a/packages/core/src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts b/packages/core/src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts index 9b8bb83e2b..5f43359d8d 100644 --- a/packages/core/src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts +++ b/packages/core/src/main/cluster/store-migrations/3.6.0-beta.1.injectable.ts @@ -18,7 +18,7 @@ interface Pre360ClusterModel extends ClusterModel { } import { getInjectable } from "@ogre-tools/injectable"; -import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token"; +import { clusterStoreMigrationInjectionToken } from "../../../features/cluster/storage/common/migration-token"; import readFileBufferSyncInjectable from "../../../common/fs/read-file-buffer-sync.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import writeFileSyncInjectable from "../../../common/fs/write-file-sync.injectable"; diff --git a/packages/core/src/main/cluster/store-migrations/5.0.0-beta.10.injectable.ts b/packages/core/src/main/cluster/store-migrations/5.0.0-beta.10.injectable.ts index 026ded3d1b..3ba3629578 100644 --- a/packages/core/src/main/cluster/store-migrations/5.0.0-beta.10.injectable.ts +++ b/packages/core/src/main/cluster/store-migrations/5.0.0-beta.10.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token"; +import { clusterStoreMigrationInjectionToken } from "../../../features/cluster/storage/common/migration-token"; import type { ClusterModel } from "../../../common/cluster-types"; import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; diff --git a/packages/core/src/main/cluster/store-migrations/5.0.0-beta.13.injectable.ts b/packages/core/src/main/cluster/store-migrations/5.0.0-beta.13.injectable.ts index 9137c81fed..6c33cfa2c4 100644 --- a/packages/core/src/main/cluster/store-migrations/5.0.0-beta.13.injectable.ts +++ b/packages/core/src/main/cluster/store-migrations/5.0.0-beta.13.injectable.ts @@ -10,7 +10,7 @@ import { isDefined } from "@k8slens/utilities"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; import { getInjectable } from "@ogre-tools/injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token"; +import { clusterStoreMigrationInjectionToken } from "../../../features/cluster/storage/common/migration-token"; import { generateNewIdFor } from "../../../common/utils/generate-new-id-for"; interface Pre500ClusterModel extends ClusterModel { diff --git a/packages/core/src/main/cluster/store-migrations/snap.injectable.ts b/packages/core/src/main/cluster/store-migrations/snap.injectable.ts index d4efc37612..f20f0a4820 100644 --- a/packages/core/src/main/cluster/store-migrations/snap.injectable.ts +++ b/packages/core/src/main/cluster/store-migrations/snap.injectable.ts @@ -6,7 +6,7 @@ // Fix embedded kubeconfig paths under snap config import { getInjectable } from "@ogre-tools/injectable"; -import { clusterStoreMigrationInjectionToken } from "../../../common/cluster-store/migration-token"; +import { clusterStoreMigrationInjectionToken } from "../../../features/cluster/storage/common/migration-token"; import loggerInjectable from "../../../common/logger.injectable"; import isSnapPackageInjectable from "../../../common/vars/is-snap-package.injectable"; import type { ClusterModel } from "../../../common/cluster-types"; diff --git a/packages/core/src/main/create-cluster/allowed-resources.injectable.ts b/packages/core/src/main/create-cluster/allowed-resources.injectable.ts index 09beae9fd2..7b520fd29a 100644 --- a/packages/core/src/main/create-cluster/allowed-resources.injectable.ts +++ b/packages/core/src/main/create-cluster/allowed-resources.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import type { KubeApiResourceDescriptor } from "../../common/rbac"; import { formatKubeApiResource } from "../../common/rbac"; diff --git a/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts b/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts index cdf120dad7..61e37642e1 100644 --- a/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts +++ b/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable.ts @@ -5,12 +5,12 @@ import { getInjectable } from "@ogre-tools/injectable"; import { setupIpcMainHandlers } from "./setup-ipc-main-handlers"; import loggerInjectable from "../../../../common/logger.injectable"; -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; import { onLoadOfApplicationInjectionToken } from "@k8slens/application"; import applicationMenuItemCompositeInjectable from "../../../../features/application-menu/main/application-menu-item-composite.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; import pushCatalogToRendererInjectable from "../../../catalog-sync-to-renderer/push-catalog-to-renderer.injectable"; import clusterFramesInjectable from "../../../../common/cluster-frames.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import clustersInjectable from "../../../../features/cluster/storage/common/clusters.injectable"; const setupIpcMainHandlersInjectable = getInjectable({ id: "setup-ipc-main-handlers", @@ -24,7 +24,7 @@ const setupIpcMainHandlersInjectable = getInjectable({ setupIpcMainHandlers({ applicationMenuItemComposite: di.inject(applicationMenuItemCompositeInjectable), pushCatalogToRenderer: di.inject(pushCatalogToRendererInjectable), - clusterStore: di.inject(clusterStoreInjectable), + clusters: di.inject(clustersInjectable), getClusterById: di.inject(getClusterByIdInjectable), clusterFrames: di.inject(clusterFramesInjectable), }); diff --git a/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts b/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts index d5b85c440a..6fdd498910 100644 --- a/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts +++ b/packages/core/src/main/electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.ts @@ -7,7 +7,6 @@ import { BrowserWindow, Menu } from "electron"; import type { ClusterFrameInfo } from "../../../../common/cluster-frames.injectable"; import { clusterSetFrameIdHandler, clusterStates } from "../../../../common/ipc/cluster"; import type { ClusterId } from "../../../../common/cluster-types"; -import type { ClusterStore } from "../../../../common/cluster-store/cluster-store"; import { broadcastMainChannel, broadcastMessage, ipcMainHandle, ipcMainOn } from "../../../../common/ipc"; import type { IComputedValue, ObservableMap } from "mobx"; import { windowActionHandleChannel, windowLocationChangedChannel, windowOpenAppMenuAsContextMenuChannel } from "../../../../common/ipc/window"; @@ -16,21 +15,23 @@ import type { ApplicationMenuItemTypes } from "../../../../features/application- import type { Composite } from "../../../../common/utils/composite/get-composite/get-composite"; import { getApplicationMenuTemplate } from "../../../../features/application-menu/main/populate-application-menu.injectable"; import type { MenuItemRoot } from "../../../../features/application-menu/main/application-menu-item-composite.injectable"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import type { Cluster } from "../../../../common/cluster/cluster"; + interface Dependencies { applicationMenuItemComposite: IComputedValue>; - clusterStore: ClusterStore; getClusterById: GetClusterById; pushCatalogToRenderer: () => void; clusterFrames: ObservableMap; + clusters: IComputedValue; } export const setupIpcMainHandlers = ({ applicationMenuItemComposite, - clusterStore, getClusterById, pushCatalogToRenderer, clusterFrames, + clusters, }: Dependencies) => { ipcMainHandle(clusterSetFrameIdHandler, (event: IpcMainInvokeEvent, clusterId: ClusterId) => { const cluster = getClusterById(clusterId); @@ -60,7 +61,7 @@ export const setupIpcMainHandlers = ({ }); ipcMainHandle(clusterStates, () => ( - clusterStore.clustersList.map(cluster => ({ + clusters.get().map(cluster => ({ id: cluster.id, state: cluster.getState(), })) diff --git a/packages/core/src/main/extension-loader/create-extension-instance.injectable.ts b/packages/core/src/main/extension-loader/create-extension-instance.injectable.ts index 62417149d4..cd20ab7ede 100644 --- a/packages/core/src/main/extension-loader/create-extension-instance.injectable.ts +++ b/packages/core/src/main/extension-loader/create-extension-instance.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { Writable } from "type-fest"; import loggerInjectable from "../../common/logger.injectable"; import { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token"; -import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import ensureHashedDirectoryForExtensionInjectable from "../../extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable"; import { lensExtensionDependencies } from "../../extensions/lens-extension"; import type { LensMainExtensionDependencies } from "../../extensions/lens-extension-set-dependencies"; import type { LensMainExtension } from "../../extensions/lens-main-extension"; @@ -17,7 +17,7 @@ const createExtensionInstanceInjectable = getInjectable({ id: "create-extension-instance", instantiate: (di) => { const deps: LensMainExtensionDependencies = { - fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), entityRegistry: di.inject(catalogEntityRegistryInjectable), navigate: di.inject(navigateForExtensionInjectable), logger: di.inject(loggerInjectable), diff --git a/packages/core/src/main/getDiForUnitTesting.ts b/packages/core/src/main/getDiForUnitTesting.ts index b374eec1ca..83b3aa6d82 100644 --- a/packages/core/src/main/getDiForUnitTesting.ts +++ b/packages/core/src/main/getDiForUnitTesting.ts @@ -10,7 +10,7 @@ import spawnInjectable from "./child-process/spawn.injectable"; import initializeExtensionsInjectable from "./start-main-application/runnables/initialize-extensions.injectable"; import setupIpcMainHandlersInjectable from "./electron-app/runnables/setup-ipc-main-handlers/setup-ipc-main-handlers.injectable"; import setupLensProxyInjectable from "./start-main-application/runnables/setup-lens-proxy.injectable"; -import setupSyncingOfWeblinksInjectable from "./start-main-application/runnables/setup-syncing-of-weblinks.injectable"; +import setupSyncingOfWeblinksInjectable from "../features/weblinks/main/setup-syncing-of-weblinks.injectable"; import setupDeepLinkingInjectable from "./electron-app/runnables/setup-deep-linking.injectable"; import setupMainWindowVisibilityAfterActivationInjectable from "./electron-app/runnables/setup-main-window-visibility-after-activation.injectable"; import setupDeviceShutdownInjectable from "./electron-app/runnables/setup-device-shutdown.injectable"; diff --git a/packages/core/src/main/helm/exec-helm/exec-env.injectable.ts b/packages/core/src/main/helm/exec-helm/exec-env.injectable.ts index 3a72d0c17c..29864427a4 100644 --- a/packages/core/src/main/helm/exec-helm/exec-env.injectable.ts +++ b/packages/core/src/main/helm/exec-helm/exec-env.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import httpsProxyConfigurationInjectable from "../../../common/user-store/https-proxy.injectable"; +import httpsProxyConfigurationInjectable from "../../../features/user-preferences/common/https-proxy.injectable"; const execHelmEnvInjectable = getInjectable({ id: "exec-helm-env", diff --git a/packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.0.injectable.ts b/packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.0.injectable.ts deleted file mode 100644 index 9143167b47..0000000000 --- a/packages/core/src/main/hotbar-store/migrations/5.0.0-alpha.0.injectable.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -// Cleans up a store that had the state related data stored -import { getEmptyHotbar } from "../../../common/hotbars/types"; -import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import { getInjectable } from "@ogre-tools/injectable"; -import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token"; - -const v500Alpha0HotbarStoreMigrationInjectable = getInjectable({ - id: "v5.0.0-alpha.0-hotbar-store-migration", - instantiate: (di) => { - const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); - - return { - version: "5.0.0-alpha.0", - run(store) { - const hotbar = getEmptyHotbar("default"); - - const { metadata: { uid, name, source }} = catalogCatalogEntity; - - hotbar.items[0] = { entity: { uid, name, source }}; - - store.set("hotbars", [hotbar]); - }, - }; - }, - injectionToken: hotbarStoreMigrationInjectionToken, -}); - -export default v500Alpha0HotbarStoreMigrationInjectable; - diff --git a/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.10.injectable.ts b/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.10.injectable.ts deleted file mode 100644 index b327085aa6..0000000000 --- a/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.10.injectable.ts +++ /dev/null @@ -1,182 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import * as uuid from "uuid"; -import type { Hotbar, HotbarItem } from "../../../common/hotbars/types"; -import { defaultHotbarCells, getEmptyHotbar } from "../../../common/hotbars/types"; -import { getLegacyGlobalDiForExtensionApi } from "../../../extensions/as-legacy-globals-for-extension-api/legacy-global-di-for-extension-api"; -import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import catalogCatalogEntityInjectable from "../../../common/catalog-entities/general-catalog-entities/implementations/catalog-catalog-entity.injectable"; -import { isDefined, isErrnoException } from "@k8slens/utilities"; -import joinPathsInjectable from "../../../common/path/join-paths.injectable"; -import { getInjectable } from "@ogre-tools/injectable"; -import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token"; -import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable"; -import loggerInjectable from "../../../common/logger.injectable"; -import { generateNewIdFor } from "../../../common/utils/generate-new-id-for"; -import type { ClusterModel } from "../../../common/cluster-types"; - -interface Pre500WorkspaceStoreModel { - workspaces: { - id: string; - name: string; - }[]; -} - -interface PartialHotbar { - id: string; - name: string; - items: (null | HotbarItem)[]; -} - -interface Pre500ClusterModel extends ClusterModel { - workspace?: string; - workspaces?: string[]; -} - -interface Pre500ClusterStoreModel { - clusters?: Pre500ClusterModel[]; -} - -const v500Beta10HotbarStoreMigrationInjectable = getInjectable({ - id: "v5.0.0-beta.10-hotbar-store-migration", - instantiate: (di) => { - const userDataPath = di.inject(directoryForUserDataInjectable); - const joinPaths = di.inject(joinPathsInjectable); - const readJsonSync = di.inject(readJsonSyncInjectable); - const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); - const logger = di.inject(loggerInjectable); - - return { - version: "5.0.0-beta.10", - run(store) { - const rawHotbars = store.get("hotbars"); - const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars.filter(h => h && typeof h === "object") : []; - - - // Hotbars might be empty, if some of the previous migrations weren't run - if (hotbars.length === 0) { - const hotbar = getEmptyHotbar("default"); - const { metadata: { uid, name, source }} = catalogCatalogEntity; - - hotbar.items[0] = { entity: { uid, name, source }}; - - hotbars.push(hotbar); - } - - try { - const workspaceStoreData: Pre500WorkspaceStoreModel = readJsonSync(joinPaths(userDataPath, "lens-workspace-store.json")); - const { clusters = [] }: Pre500ClusterStoreModel = readJsonSync(joinPaths(userDataPath, "lens-cluster-store.json")); - const workspaceHotbars = new Map(); // mapping from WorkspaceId to HotBar - - for (const { id, name } of workspaceStoreData.workspaces) { - logger.info(`Creating new hotbar for ${name}`); - workspaceHotbars.set(id, { - id: uuid.v4(), // don't use the old IDs as they aren't necessarily UUIDs - items: [], - name: `Workspace: ${name}`, - }); - } - - { - // grab the default named hotbar or the first. - const defaultHotbarIndex = Math.max(0, hotbars.findIndex(hotbar => hotbar.name === "default")); - const [{ name, id, items }] = hotbars.splice(defaultHotbarIndex, 1); - - workspaceHotbars.set("default", { - name, - id, - items: items.filter(isDefined), - }); - } - - for (const cluster of clusters) { - const uid = generateNewIdFor(cluster); - - for (const workspaceId of cluster.workspaces ?? [cluster.workspace].filter(isDefined)) { - const workspaceHotbar = workspaceHotbars.get(workspaceId); - - if (!workspaceHotbar) { - logger.info(`Cluster ${uid} has unknown workspace ID, skipping`); - continue; - } - - logger.info(`Adding cluster ${uid} to ${workspaceHotbar.name}`); - - if (workspaceHotbar?.items.length < defaultHotbarCells) { - workspaceHotbar.items.push({ - entity: { - uid: generateNewIdFor(cluster), - name: cluster.preferences?.clusterName || cluster.contextName, - }, - }); - } - } - } - - for (const hotbar of workspaceHotbars.values()) { - if (hotbar.items.length === 0) { - logger.info(`Skipping ${hotbar.name} due to it being empty`); - continue; - } - - while (hotbar.items.length < defaultHotbarCells) { - hotbar.items.push(null); - } - - hotbars.push(hotbar as Hotbar); - } - - /** - * Finally, make sure that the catalog entity hotbar item is in place. - * Just in case something else removed it. - * - * if every hotbar has elements that all not the `catalog-entity` item - */ - if (hotbars.every(hotbar => hotbar.items.every(item => item?.entity?.uid !== "catalog-entity"))) { - // note, we will add a new whole hotbar here called "default" if that was previously removed - const di = getLegacyGlobalDiForExtensionApi(); - const catalogCatalogEntity = di.inject(catalogCatalogEntityInjectable); - - const defaultHotbar = hotbars.find(hotbar => hotbar.name === "default"); - const { metadata: { uid, name, source }} = catalogCatalogEntity; - - if (defaultHotbar) { - const freeIndex = defaultHotbar.items.findIndex(i => i === null); - - if (freeIndex === -1) { - // making a new hotbar is less destructive if the first hotbar - // called "default" is full than overriding a hotbar item - const hotbar = getEmptyHotbar("initial"); - - hotbar.items[0] = { entity: { uid, name, source }}; - hotbars.unshift(hotbar); - } else { - defaultHotbar.items[freeIndex] = { entity: { uid, name, source }}; - } - } else { - const hotbar = getEmptyHotbar("default"); - - hotbar.items[0] = { entity: { uid, name, source }}; - hotbars.unshift(hotbar); - } - } - - } catch (error) { - // ignore files being missing - if (isErrnoException(error) && error.code !== "ENOENT") { - throw error; - } - } - - store.set("hotbars", hotbars); - }, - }; - }, - injectionToken: hotbarStoreMigrationInjectionToken, -}); - -export default v500Beta10HotbarStoreMigrationInjectable; - diff --git a/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.5.injectable.ts b/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.5.injectable.ts deleted file mode 100644 index eef10f7033..0000000000 --- a/packages/core/src/main/hotbar-store/migrations/5.0.0-beta.5.injectable.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { Hotbar } from "../../../common/hotbars/types"; -import catalogEntityRegistryInjectable from "../../catalog/entity-registry.injectable"; -import { getInjectable } from "@ogre-tools/injectable"; -import { hotbarStoreMigrationInjectionToken } from "../../../common/hotbars/migrations-token"; - -const v500Beta5HotbarStoreMigrationInjectable = getInjectable({ - id: "v500-beta5-hotbar-store-migration", - instantiate: (di) => { - const catalogEntityRegistry = di.inject(catalogEntityRegistryInjectable); - - return { - version: "5.0.0-beta.5", - run(store) { - const rawHotbars = store.get("hotbars"); - const hotbars: Hotbar[] = Array.isArray(rawHotbars) ? rawHotbars : []; - - for (const hotbar of hotbars) { - for (let i = 0; i < hotbar.items.length; i += 1) { - const item = hotbar.items[i]; - - if (!item) { - continue; - } - - const entity = catalogEntityRegistry.findById(item.entity.uid); - - if (!entity) { - // Clear disabled item - hotbar.items[i] = null; - } else { - // Save additional data - item.entity = { - ...item.entity, - name: entity.metadata.name, - source: entity.metadata.source, - }; - } - } - } - - store.set("hotbars", hotbars); - }, - }; - }, - injectionToken: hotbarStoreMigrationInjectionToken, -}); - -export default v500Beta5HotbarStoreMigrationInjectable; - diff --git a/packages/core/src/main/kubectl/apply-all-handler.injectable.ts b/packages/core/src/main/kubectl/apply-all-handler.injectable.ts index 00f58e5e3f..fb5f3a040f 100644 --- a/packages/core/src/main/kubectl/apply-all-handler.injectable.ts +++ b/packages/core/src/main/kubectl/apply-all-handler.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; -import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; import { kubectlApplyAllChannel } from "../../common/kube-helpers/channels"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; import resourceApplierInjectable from "../resource-applier/create-resource-applier.injectable"; import { getRequestChannelListenerInjectable } from "@k8slens/messaging"; @@ -15,14 +15,16 @@ const kubectlApplyAllChannelHandlerInjectable = getRequestChannelListenerInjecta const getClusterById = di.inject(getClusterByIdInjectable); const emitAppEvent = di.inject(emitAppEventInjectable); - return async ({ - clusterId, - extraArgs, - resources, - }) => { - emitAppEvent({ name: "cluster", action: "kubectl-apply-all" }); + return async (event) => { + const { + clusterId, + extraArgs, + resources, + } = event; const cluster = getClusterById(clusterId); + emitAppEvent({ name: "cluster", action: "kubectl-apply-all" }); + if (!cluster) { return { callWasSuccessful: false, diff --git a/packages/core/src/main/kubectl/create-kubectl.injectable.ts b/packages/core/src/main/kubectl/create-kubectl.injectable.ts index 5d7b19162c..84e5fee601 100644 --- a/packages/core/src/main/kubectl/create-kubectl.injectable.ts +++ b/packages/core/src/main/kubectl/create-kubectl.injectable.ts @@ -6,7 +6,6 @@ import { getInjectable } from "@ogre-tools/injectable"; import type { KubectlDependencies } from "./kubectl"; import { Kubectl } from "./kubectl"; import directoryForKubectlBinariesInjectable from "../../common/app-paths/directory-for-kubectl-binaries/directory-for-kubectl-binaries.injectable"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; import kubectlDownloadingNormalizedArchInjectable from "./normalized-arch.injectable"; import normalizedPlatformInjectable from "../../common/vars/normalized-platform.injectable"; import kubectlBinaryNameInjectable from "./binary-name.injectable"; @@ -20,6 +19,7 @@ import getBasenameOfPathInjectable from "../../common/path/get-basename.injectab import loggerInjectable from "../../common/logger.injectable"; import execFileInjectable from "../../common/fs/exec-file.injectable"; import unlinkInjectable from "../../common/fs/unlink.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; export type CreateKubectl = (version: string) => Kubectl; @@ -28,7 +28,7 @@ const createKubectlInjectable = getInjectable({ instantiate: (di): CreateKubectl => { const dependencies: KubectlDependencies = { - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), directoryForKubectlBinaries: di.inject(directoryForKubectlBinariesInjectable), normalizedDownloadArch: di.inject(kubectlDownloadingNormalizedArchInjectable), normalizedDownloadPlatform: di.inject(normalizedPlatformInjectable), diff --git a/packages/core/src/main/kubectl/delete-all-handler.injectable.ts b/packages/core/src/main/kubectl/delete-all-handler.injectable.ts index 5d207be482..b7e21b4d8e 100644 --- a/packages/core/src/main/kubectl/delete-all-handler.injectable.ts +++ b/packages/core/src/main/kubectl/delete-all-handler.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import emitAppEventInjectable from "../../common/app-event-bus/emit-event.injectable"; -import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; import { kubectlDeleteAllChannel } from "../../common/kube-helpers/channels"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; import resourceApplierInjectable from "../resource-applier/create-resource-applier.injectable"; import { getRequestChannelListenerInjectable } from "@k8slens/messaging"; @@ -15,15 +15,16 @@ const kubectlDeleteAllChannelHandlerInjectable = getRequestChannelListenerInject const emitAppEvent = di.inject(emitAppEventInjectable); const getClusterById = di.inject(getClusterByIdInjectable); - return async ({ - clusterId, - extraArgs, - resources, - }) => { - emitAppEvent({ name: "cluster", action: "kubectl-delete-all" }); - + return async (event) => { + const { + clusterId, + extraArgs, + resources, + } = event; const cluster = getClusterById(clusterId); + emitAppEvent({ name: "cluster", action: "kubectl-delete-all" }); + if (!cluster) { return { callWasSuccessful: false, diff --git a/packages/core/src/main/kubectl/kubectl.ts b/packages/core/src/main/kubectl/kubectl.ts index ca0fac16e2..631e08b833 100644 --- a/packages/core/src/main/kubectl/kubectl.ts +++ b/packages/core/src/main/kubectl/kubectl.ts @@ -7,7 +7,6 @@ import fs from "fs"; import { ensureDir, pathExists } from "fs-extra"; import * as lockFile from "proper-lockfile"; import { SemVer, coerce } from "semver"; -import { defaultPackageMirror, packageMirrors } from "../../common/user-store/preferences-helpers"; import got from "got/dist/source"; import { promisify } from "util"; import stream from "stream"; @@ -20,6 +19,7 @@ import type { Logger } from "../../common/logger"; import type { ExecFile } from "../../common/fs/exec-file.injectable"; import { hasTypedProperty, isObject, isString, json } from "@k8slens/utilities"; import type { Unlink } from "../../common/fs/unlink.injectable"; +import { packageMirrors, defaultPackageMirror } from "../../features/user-preferences/common/preferences-helpers"; const initScriptVersionString = "# lens-initscript v3"; @@ -30,7 +30,7 @@ export interface KubectlDependencies { readonly kubectlBinaryName: string; readonly bundledKubectlBinaryPath: string; readonly baseBundeledBinariesDirectory: string; - readonly userStore: { + readonly state: { readonly kubectlBinariesPath?: string; readonly downloadBinariesPath?: string; readonly downloadKubectlBinaries: boolean; @@ -91,12 +91,12 @@ export class Kubectl { } public getPathFromPreferences() { - return this.dependencies.userStore.kubectlBinariesPath || this.getBundledPath(); + return this.dependencies.state.kubectlBinariesPath || this.getBundledPath(); } protected getDownloadDir() { - if (this.dependencies.userStore.downloadBinariesPath) { - return this.dependencies.joinPaths(this.dependencies.userStore.downloadBinariesPath, "kubectl"); + if (this.dependencies.state.downloadBinariesPath) { + return this.dependencies.joinPaths(this.dependencies.state.downloadBinariesPath, "kubectl"); } return this.dependencies.directoryForKubectlBinaries; @@ -107,7 +107,7 @@ export class Kubectl { return this.getBundledPath(); } - if (this.dependencies.userStore.downloadKubectlBinaries === false) { + if (this.dependencies.state.downloadKubectlBinaries === false) { return this.getPathFromPreferences(); } @@ -231,7 +231,7 @@ export class Kubectl { } public async ensureKubectl(): Promise { - if (this.dependencies.userStore.downloadKubectlBinaries === false) { + if (this.dependencies.state.downloadKubectlBinaries === false) { return true; } @@ -303,7 +303,7 @@ export class Kubectl { protected async writeInitScripts() { const binariesDir = this.dependencies.baseBundeledBinariesDirectory; - const kubectlPath = this.dependencies.userStore.downloadKubectlBinaries + const kubectlPath = this.dependencies.state.downloadKubectlBinaries ? this.dirname : this.dependencies.getDirnameOfPath(this.getPathFromPreferences()); @@ -370,7 +370,7 @@ export class Kubectl { protected getDownloadMirror(): string { // MacOS packages are only available from default - const { url } = packageMirrors.get(this.dependencies.userStore.downloadMirror) + const { url } = packageMirrors.get(this.dependencies.state.downloadMirror) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion ?? packageMirrors.get(defaultPackageMirror)!; diff --git a/packages/core/src/main/lens-proxy/get-cluster-for-request.injectable.ts b/packages/core/src/main/lens-proxy/get-cluster-for-request.injectable.ts index 7d529540d1..8be8d1d4e4 100644 --- a/packages/core/src/main/lens-proxy/get-cluster-for-request.injectable.ts +++ b/packages/core/src/main/lens-proxy/get-cluster-for-request.injectable.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; import { getClusterIdFromHost } from "../../common/utils"; import { apiKubePrefix } from "../../common/vars"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; import type { GetClusterForRequest } from "./lens-proxy"; const getClusterForRequestInjectable = getInjectable({ diff --git a/packages/core/src/main/protocol-handler/__test__/router.test.ts b/packages/core/src/main/protocol-handler/__test__/router.test.ts index 5af2e588d8..e3eddb6b34 100644 --- a/packages/core/src/main/protocol-handler/__test__/router.test.ts +++ b/packages/core/src/main/protocol-handler/__test__/router.test.ts @@ -6,12 +6,10 @@ import * as uuid from "uuid"; import { ProtocolHandlerExtension, ProtocolHandlerInternal, ProtocolHandlerInvalid } from "../../../common/protocol-handler"; -import { delay, noop } from "@k8slens/utilities"; -import type { ExtensionsStore, IsEnabledExtensionDescriptor } from "../../../extensions/extensions-store/extensions-store"; +import { noop } from "@k8slens/utilities"; import type { LensProtocolRouterMain } from "../lens-protocol-router-main/lens-protocol-router-main"; import { getDiForUnitTesting } from "../../getDiForUnitTesting"; import lensProtocolRouterMainInjectable from "../lens-protocol-router-main/lens-protocol-router-main.injectable"; -import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import getConfigurationFileModelInjectable from "../../../common/get-configuration-file-model/get-configuration-file-model.injectable"; import { LensExtension } from "../../../extensions/lens-extension"; import type { ObservableMap } from "mobx"; @@ -23,6 +21,8 @@ import pathExistsInjectable from "../../../common/fs/path-exists.injectable"; import readJsonSyncInjectable from "../../../common/fs/read-json-sync.injectable"; import writeJsonSyncInjectable from "../../../common/fs/write-json-sync.injectable"; import type { LensExtensionId } from "@k8slens/legacy-extensions"; +import type { LensExtensionState } from "../../../features/extensions/enabled/common/state.injectable"; +import enabledExtensionsStateInjectable from "../../../features/extensions/enabled/common/state.injectable"; function throwIfDefined(val: any): void { if (val != null) { @@ -33,7 +33,7 @@ function throwIfDefined(val: any): void { describe("protocol router tests", () => { let extensionInstances: ObservableMap; let lpr: LensProtocolRouterMain; - let enabledExtensions: Set; + let enabledExtensions: ObservableMap; let broadcastMessageMock: jest.Mock; beforeEach(async () => { @@ -44,11 +44,7 @@ describe("protocol router tests", () => { di.override(readJsonSyncInjectable, () => () => { throw new Error("tried call readJsonSync without override"); }); di.override(writeJsonSyncInjectable, () => () => { throw new Error("tried call writeJsonSync without override"); }); - enabledExtensions = new Set(); - - di.override(extensionsStoreInjectable, () => ({ - isEnabled: ({ id, isBundled }: IsEnabledExtensionDescriptor) => isBundled || enabledExtensions.has(id), - } as unknown as ExtensionsStore)); + enabledExtensions = di.inject(enabledExtensionsStateInjectable); di.permitSideEffects(getConfigurationFileModelInjectable); @@ -95,7 +91,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@mirantis/minikube", enabled: true }); lpr.addInternalHandler("/", noop); @@ -105,14 +101,14 @@ describe("protocol router tests", () => { expect(throwIfDefined(error)).not.toThrow(); } + expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched"); + try { expect(await lpr.route("lens://extension/@mirantis/minikube")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } - await delay(50); - expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerInternal, "lens://app", "matched"); expect(broadcastMessageMock).toHaveBeenCalledWith(ProtocolHandlerExtension, "lens://extension/@mirantis/minikube", "matched"); }); @@ -175,7 +171,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@foobar/icecream", enabled: true }); try { expect(await lpr.route("lens://extension/@foobar/icecream/page/foob")).toBeUndefined(); @@ -183,7 +179,6 @@ describe("protocol router tests", () => { expect(throwIfDefined(error)).not.toThrow(); } - await delay(50); expect(called).toBe("foob"); expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/@foobar/icecream/page/foob", "matched"); }); @@ -214,7 +209,7 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "@foobar/icecream", enabled: true }); } { @@ -240,19 +235,15 @@ describe("protocol router tests", () => { }); extensionInstances.set(extId, ext); - enabledExtensions.add(extId); + enabledExtensions.set(extId, { name: "icecream", enabled: true }); } - enabledExtensions.add("@foobar/icecream"); - enabledExtensions.add("icecream"); - try { expect(await lpr.route("lens://extension/icecream/page")).toBeUndefined(); } catch (error) { expect(throwIfDefined(error)).not.toThrow(); } - await delay(50); expect(called).toBe(1); expect(broadcastMessageMock).toBeCalledWith(ProtocolHandlerExtension, "lens://extension/icecream/page", "matched"); diff --git a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts index bce91119c2..92fbdd8737 100644 --- a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts +++ b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.injectable.ts @@ -5,22 +5,21 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterMain } from "./lens-protocol-router-main"; -import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import showApplicationWindowInjectable from "../../start-main-application/lens-window/show-application-window.injectable"; import broadcastMessageInjectable from "../../../common/ipc/broadcast-message.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import isExtensionEnabledInjectable from "../../../features/extensions/enabled/common/is-enabled.injectable"; const lensProtocolRouterMainInjectable = getInjectable({ id: "lens-protocol-router-main", - instantiate: (di) => - new LensProtocolRouterMain({ - extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(extensionsStoreInjectable), - showApplicationWindow: di.inject(showApplicationWindowInjectable), - broadcastMessage: di.inject(broadcastMessageInjectable), - logger: di.inject(loggerInjectable), - }), + instantiate: (di) => new LensProtocolRouterMain({ + extensionLoader: di.inject(extensionLoaderInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), + showApplicationWindow: di.inject(showApplicationWindowInjectable), + broadcastMessage: di.inject(broadcastMessageInjectable), + logger: di.inject(loggerInjectable), + }), }); export default lensProtocolRouterMainInjectable; diff --git a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts index fb0ad71892..8185ca4e68 100644 --- a/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts +++ b/packages/core/src/main/protocol-handler/lens-protocol-router-main/lens-protocol-router-main.ts @@ -119,7 +119,13 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { const rawUrl = url.toString(); // for sending to renderer const attempt = super._routeToInternal(url); - this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt))); + const sendRoutingToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerInternal, rawUrl, attempt); + + if (this.rendererLoaded) { + sendRoutingToRenderer(); + } else { + this.disposers.push(when(() => this.rendererLoaded, sendRoutingToRenderer)); + } return attempt; } @@ -136,7 +142,13 @@ export class LensProtocolRouterMain extends proto.LensProtocolRouter { */ const attempt = await super._routeToExtension(new URLParse(url.toString(), true)); - this.disposers.push(when(() => this.rendererLoaded, () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt))); + const sendRoutingToRenderer = () => this.dependencies.broadcastMessage(proto.ProtocolHandlerExtension, rawUrl, attempt); + + if (this.rendererLoaded) { + sendRoutingToRenderer(); + } else { + this.disposers.push(when(() => this.rendererLoaded, sendRoutingToRenderer)); + } return attempt; } diff --git a/packages/core/src/main/shell-session/local-shell-session/local-shell-session.ts b/packages/core/src/main/shell-session/local-shell-session/local-shell-session.ts index 567fc1dca7..b066702266 100644 --- a/packages/core/src/main/shell-session/local-shell-session/local-shell-session.ts +++ b/packages/core/src/main/shell-session/local-shell-session/local-shell-session.ts @@ -3,17 +3,17 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { UserStore } from "../../../common/user-store"; import type { ShellSessionArgs, ShellSessionDependencies } from "../shell-session"; import { ShellSession } from "../shell-session"; import type { ModifyTerminalShellEnv } from "../shell-env-modifier/modify-terminal-shell-env.injectable"; import type { JoinPaths } from "../../../common/path/join-paths.injectable"; import type { GetDirnameOfPath } from "../../../common/path/get-dirname.injectable"; import type { GetBasenameOfPath } from "../../../common/path/get-basename.injectable"; +import type { UserPreferencesState } from "../../../features/user-preferences/common/state.injectable"; export interface LocalShellSessionDependencies extends ShellSessionDependencies { readonly directoryForBinaries: string; - readonly userStore: UserStore; + readonly state: UserPreferencesState; modifyTerminalShellEnv: ModifyTerminalShellEnv; joinPaths: JoinPaths; getDirnameOfPath: GetDirnameOfPath; @@ -50,8 +50,8 @@ export class LocalShellSession extends ShellSession { } protected async getShellArgs(shell: string): Promise { - const pathFromPreferences = this.dependencies.userStore.kubectlBinariesPath || this.kubectl.getBundledPath(); - const kubectlPathDir = this.dependencies.userStore.downloadKubectlBinaries + const pathFromPreferences = this.dependencies.state.kubectlBinariesPath || this.kubectl.getBundledPath(); + const kubectlPathDir = this.dependencies.state.downloadKubectlBinaries ? this.dependencies.directoryContainingKubectl : this.dependencies.getDirnameOfPath(pathFromPreferences); diff --git a/packages/core/src/main/shell-session/local-shell-session/open.injectable.ts b/packages/core/src/main/shell-session/local-shell-session/open.injectable.ts index 4d5d54984a..c7d396b41a 100644 --- a/packages/core/src/main/shell-session/local-shell-session/open.injectable.ts +++ b/packages/core/src/main/shell-session/local-shell-session/open.injectable.ts @@ -12,19 +12,19 @@ import isMacInjectable from "../../../common/vars/is-mac.injectable"; import type { Cluster } from "../../../common/cluster/cluster"; import isWindowsInjectable from "../../../common/vars/is-windows.injectable"; import loggerInjectable from "../../../common/logger.injectable"; -import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import type WebSocket from "ws"; import getDirnameOfPathInjectable from "../../../common/path/get-dirname.injectable"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable"; import computeShellEnvironmentInjectable from "../../../features/shell-sync/main/compute-shell-environment.injectable"; import spawnPtyInjectable from "../spawn-pty.injectable"; -import userShellSettingInjectable from "../../../common/user-store/shell-setting.injectable"; import appNameInjectable from "../../../common/vars/app-name.injectable"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; import statInjectable from "../../../common/fs/stat.injectable"; import kubeconfigManagerInjectable from "../../kubeconfig-manager/kubeconfig-manager.injectable"; +import userPreferencesStateInjectable from "../../../features/user-preferences/common/state.injectable"; +import userShellSettingInjectable from "../../../features/user-preferences/common/shell-setting.injectable"; export interface OpenLocalShellSessionArgs { websocket: WebSocket; @@ -44,7 +44,7 @@ const openLocalShellSessionInjectable = getInjectable({ isMac: di.inject(isMacInjectable), isWindows: di.inject(isWindowsInjectable), logger: di.inject(loggerInjectable), - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), userShellSetting: di.inject(userShellSettingInjectable), appName: di.inject(appNameInjectable), buildVersion: di.inject(buildVersionInjectable), diff --git a/packages/core/src/main/shell-session/node-shell-session/open.injectable.ts b/packages/core/src/main/shell-session/node-shell-session/open.injectable.ts index 55ec26d362..d2ce8ffb17 100644 --- a/packages/core/src/main/shell-session/node-shell-session/open.injectable.ts +++ b/packages/core/src/main/shell-session/node-shell-session/open.injectable.ts @@ -14,7 +14,6 @@ import loggerInjectable from "../../../common/logger.injectable"; import createKubeJsonApiForClusterInjectable from "../../../common/k8s-api/create-kube-json-api-for-cluster.injectable"; import computeShellEnvironmentInjectable from "../../../features/shell-sync/main/compute-shell-environment.injectable"; import spawnPtyInjectable from "../spawn-pty.injectable"; -import userShellSettingInjectable from "../../../common/user-store/shell-setting.injectable"; import appNameInjectable from "../../../common/vars/app-name.injectable"; import buildVersionInjectable from "../../vars/build-version/build-version.injectable"; import emitAppEventInjectable from "../../../common/app-event-bus/emit-event.injectable"; @@ -22,6 +21,7 @@ import statInjectable from "../../../common/fs/stat.injectable"; import createKubeApiInjectable from "../../../common/k8s-api/create-kube-api.injectable"; import loadProxyKubeconfigInjectable from "../../cluster/load-proxy-kubeconfig.injectable"; import kubeconfigManagerInjectable from "../../kubeconfig-manager/kubeconfig-manager.injectable"; +import userShellSettingInjectable from "../../../features/user-preferences/common/shell-setting.injectable"; export interface NodeShellSessionArgs { websocket: WebSocket; diff --git a/packages/core/src/main/stores/init-user-store.injectable.ts b/packages/core/src/main/stores/init-user-store.injectable.ts deleted file mode 100644 index b7d1dac33d..0000000000 --- a/packages/core/src/main/stores/init-user-store.injectable.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import userStoreFileNameMigrationInjectable from "../../common/user-store/file-name-migration.injectable"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import { beforeApplicationIsLoadingInjectionToken } from "@k8slens/application"; -import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable"; - -const initUserStoreInjectable = getInjectable({ - id: "init-user-store", - instantiate: (di) => ({ - run: async () => { - const userStore = di.inject(userStoreInjectable); - const userStoreFileNameMigration = di.inject(userStoreFileNameMigrationInjectable); - - await userStoreFileNameMigration(); - userStore.load(); - }, - runAfter: initDefaultUpdateChannelInjectable, - }), - injectionToken: beforeApplicationIsLoadingInjectionToken, -}); - -export default initUserStoreInjectable; diff --git a/packages/core/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts b/packages/core/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts index b856c15ffd..5a8c691c82 100644 --- a/packages/core/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts +++ b/packages/core/src/renderer/api/catalog/entity/get-active-cluster-entity.injectable.ts @@ -4,16 +4,24 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import clusterStoreInjectable from "../../../../common/cluster-store/cluster-store.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import catalogEntityRegistryInjectable from "./registry.injectable"; const activeEntityInternalClusterInjectable = getInjectable({ id: "active-entity-internal-cluster", instantiate: (di) => { - const store = di.inject(clusterStoreInjectable); + const getClusterById = di.inject(getClusterByIdInjectable); const entityRegistry = di.inject(catalogEntityRegistryInjectable); - return computed(() => store.getById(entityRegistry.activeEntity?.getId())); + return computed(() => { + const entityId = entityRegistry.activeEntity?.getId(); + + if (entityId) { + return getClusterById(entityId); + } + + return undefined; + }); }, }); diff --git a/packages/core/src/renderer/base-store/disable-sync-in-ipc-listener.injectable.ts b/packages/core/src/renderer/base-store/disable-sync-in-ipc-listener.injectable.ts index 82670d8e1b..a1ae454992 100644 --- a/packages/core/src/renderer/base-store/disable-sync-in-ipc-listener.injectable.ts +++ b/packages/core/src/renderer/base-store/disable-sync-in-ipc-listener.injectable.ts @@ -3,12 +3,12 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { shouldBaseStoreDisableSyncInIpcListenerInjectionToken } from "../../common/base-store/disable-sync"; +import { shouldPersistentStorageDisableSyncInIpcListenerInjectionToken } from "../../common/persistent-storage/disable-sync"; const shouldBaseStoreDisableSyncInIpcListenerInjectable = getInjectable({ id: "should-base-store-disable-sync-in-ipc-listener", instantiate: () => true, - injectionToken: shouldBaseStoreDisableSyncInIpcListenerInjectionToken, + injectionToken: shouldPersistentStorageDisableSyncInIpcListenerInjectionToken, }); export default shouldBaseStoreDisableSyncInIpcListenerInjectable; diff --git a/packages/core/src/renderer/base-store/ipc-channel-prefix.injectable.ts b/packages/core/src/renderer/base-store/ipc-channel-prefix.injectable.ts index bb2d407720..8a27bc456e 100644 --- a/packages/core/src/renderer/base-store/ipc-channel-prefix.injectable.ts +++ b/packages/core/src/renderer/base-store/ipc-channel-prefix.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { baseStoreIpcChannelPrefixesInjectionToken } from "../../common/base-store/channel-prefix"; +import { persistentStorageIpcChannelPrefixesInjectionToken } from "../../common/persistent-storage/channel-prefix"; const baseStoreIpcChannelPrefixInjectable = getInjectable({ id: "base-store-ipc-channel-prefix", @@ -11,7 +11,7 @@ const baseStoreIpcChannelPrefixInjectable = getInjectable({ local: "store-sync-renderer", remote: "store-sync-main", }), - injectionToken: baseStoreIpcChannelPrefixesInjectionToken, + injectionToken: persistentStorageIpcChannelPrefixesInjectionToken, }); export default baseStoreIpcChannelPrefixInjectable; diff --git a/packages/core/src/renderer/base-store/persist-state-to-config.injectable.ts b/packages/core/src/renderer/base-store/persist-state-to-config.injectable.ts index 0cf594062a..78d87c7fb1 100644 --- a/packages/core/src/renderer/base-store/persist-state-to-config.injectable.ts +++ b/packages/core/src/renderer/base-store/persist-state-to-config.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { persistStateToConfigInjectionToken } from "../../common/base-store/save-to-file"; +import { persistStateToConfigInjectionToken } from "../../common/persistent-storage/save-to-file"; import { noop } from "@k8slens/utilities"; const persistStateToConfigInjectable = getInjectable({ diff --git a/packages/core/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts b/packages/core/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts index 0775b501b5..d175ce2899 100644 --- a/packages/core/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts +++ b/packages/core/src/renderer/before-frame-starts/runnables/setup-kubernetes-cluster-context-menu-open.injectable.ts @@ -4,9 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import catalogCategoryRegistryInjectable from "../../../common/catalog/category-registry.injectable"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import loadKubeconfigInjectable from "../../../common/cluster/load-kubeconfig.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; import openDeleteClusterDialogInjectable from "../../components/delete-cluster-dialog/open.injectable"; import { beforeFrameStartsSecondInjectionToken } from "../tokens"; diff --git a/packages/core/src/renderer/cluster-frame-context/hosted-cluster.injectable.ts b/packages/core/src/renderer/cluster-frame-context/hosted-cluster.injectable.ts index a466984952..934a660188 100644 --- a/packages/core/src/renderer/cluster-frame-context/hosted-cluster.injectable.ts +++ b/packages/core/src/renderer/cluster-frame-context/hosted-cluster.injectable.ts @@ -4,16 +4,20 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import hostedClusterIdInjectable from "./hosted-cluster-id.injectable"; -import clusterStoreInjectable from "../../common/cluster-store/cluster-store.injectable"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; const hostedClusterInjectable = getInjectable({ id: "hosted-cluster", instantiate: (di) => { const hostedClusterId = di.inject(hostedClusterIdInjectable); - const store = di.inject(clusterStoreInjectable); + const getClusterById = di.inject(getClusterByIdInjectable); - return store.getById(hostedClusterId); + if (!hostedClusterId) { + return undefined; + } + + return getClusterById(hostedClusterId); }, }); diff --git a/packages/core/src/renderer/cluster-frame-context/should-show-resource.injectable.ts b/packages/core/src/renderer/cluster-frame-context/should-show-resource.injectable.ts index 6258796a0b..39b6070c3b 100644 --- a/packages/core/src/renderer/cluster-frame-context/should-show-resource.injectable.ts +++ b/packages/core/src/renderer/cluster-frame-context/should-show-resource.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable, lifecycleEnum } from "@ogre-tools/injectable"; import { computed } from "mobx"; import hostedClusterInjectable from "./hosted-cluster.injectable"; -import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import type { KubeApiResourceDescriptor } from "../../common/rbac"; import { formatKubeApiResource } from "../../common/rbac"; diff --git a/packages/core/src/renderer/components/+catalog/__tests__/custom-columns.test.ts b/packages/core/src/renderer/components/+catalog/__tests__/custom-columns.test.ts index 8b5ed60a36..381f9541ac 100644 --- a/packages/core/src/renderer/components/+catalog/__tests__/custom-columns.test.ts +++ b/packages/core/src/renderer/components/+catalog/__tests__/custom-columns.test.ts @@ -11,7 +11,6 @@ import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; import type { AdditionalCategoryColumnRegistration, CategoryColumnRegistration } from "../custom-category-columns"; import type { CategoryColumns, GetCategoryColumnsParams } from "../columns/get.injectable"; import getCategoryColumnsInjectable from "../columns/get.injectable"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import extensionInjectable from "../../../../extensions/extension-loader/extension/extension.injectable"; import currentlyInClusterFrameInjectable from "../../../routes/currently-in-cluster-frame.injectable"; @@ -46,7 +45,6 @@ describe("Custom Category Columns", () => { beforeEach(() => { di = getDiForUnitTesting(); - di.override(hotbarStoreInjectable, () => ({})); di.override(currentlyInClusterFrameInjectable, () => false); getCategoryColumns = di.inject(getCategoryColumnsInjectable); diff --git a/packages/core/src/renderer/components/+catalog/catalog.tsx b/packages/core/src/renderer/components/+catalog/catalog.tsx index 1d385d78c4..0d11d070ab 100644 --- a/packages/core/src/renderer/components/+catalog/catalog.tsx +++ b/packages/core/src/renderer/components/+catalog/catalog.tsx @@ -28,8 +28,6 @@ import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entit import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable"; import type { NavigateToCatalog } from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; import navigateToCatalogInjectable from "../../../common/front-end-routing/routes/catalog/navigate-to-catalog.injectable"; -import type { HotbarStore } from "../../../common/hotbars/store"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { Logger } from "../../../common/logger"; import loggerInjectable from "../../../common/logger.injectable"; import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable"; @@ -51,6 +49,8 @@ import type { OnCatalogEntityListClick } from "./entity-details/on-catalog-click import onCatalogEntityListClickInjectable from "./entity-details/on-catalog-click.injectable"; import type { ShowEntityDetails } from "./entity-details/show.injectable"; import showEntityDetailsInjectable from "./entity-details/show.injectable"; +import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; +import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable"; interface Dependencies { catalogPreviousActiveTabStorage: StorageLayer; @@ -65,13 +65,13 @@ interface Dependencies { kind: IComputedValue; }; navigateToCatalog: NavigateToCatalog; - hotbarStore: HotbarStore; catalogCategoryRegistry: CatalogCategoryRegistry; visitEntityContextMenu: VisitEntityContextMenu; navigate: Navigate; normalizeMenuItem: NormalizeCatalogEntityContextMenu; showErrorNotification: ShowNotification; logger: Logger; + activeHotbar: IComputedValue; } @observer @@ -156,11 +156,11 @@ class NonInjectedCatalog extends React.Component { } addToHotbar(entity: CatalogEntity): void { - this.props.hotbarStore.addToHotbar(entity); + this.props.activeHotbar.get()?.addEntity(entity); } removeFromHotbar(entity: CatalogEntity): void { - this.props.hotbarStore.removeFromHotbar(entity.getId()); + this.props.activeHotbar.get()?.removeEntity(entity.getId()); } onTabChange = action((tabId: string | null) => { @@ -323,7 +323,7 @@ export const Catalog = withInjectables(NonInjectedCatalog, { routeParameters: di.inject(catalogRouteParametersInjectable), navigateToCatalog: di.inject(navigateToCatalogInjectable), emitEvent: di.inject(emitAppEventInjectable), - hotbarStore: di.inject(hotbarStoreInjectable), + activeHotbar: di.inject(activeHotbarInjectable), catalogCategoryRegistry: di.inject(catalogCategoryRegistryInjectable), visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable), navigate: di.inject(navigateInjectable), diff --git a/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.ts b/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.ts new file mode 100644 index 0000000000..433d263b79 --- /dev/null +++ b/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import styles from "../catalog.module.scss"; +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +import renderNamedCategoryColumnCellInjectable from "./render-named-category-column-cell.injectable"; + +const namedCategoryColumnInjectable = getInjectable({ + id: "name-category-column", + instantiate: (di): RegisteredAdditionalCategoryColumn => ({ + id: "name", + priority: 0, + renderCell: di.inject(renderNamedCategoryColumnCellInjectable), + titleProps: { + title: "Name", + className: styles.entityName, + id: "name", + sortBy: "name", + }, + searchFilter: (entity) => entity.getName(), + sortCallback: (entity) => `name=${entity.getName()}`, + }), +}); + +export default namedCategoryColumnInjectable; diff --git a/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.tsx b/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.tsx deleted file mode 100644 index 0f7544ffc1..0000000000 --- a/packages/core/src/renderer/components/+catalog/columns/named-category.injectable.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import styles from "../catalog.module.scss"; -import type { CatalogEntity } from "../../../../common/catalog"; -import { prevDefault } from "@k8slens/utilities"; -import { Avatar } from "../../avatar"; -import { Icon } from "../../icon"; -import React from "react"; -import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; -import type { HotbarStore } from "../../../../common/hotbars/store"; - -const renderEntityName = (hotbarStore: HotbarStore) => (entity: CatalogEntity) => { - const isItemInHotbar = hotbarStore.isAddedToActive(entity); - const onClick = prevDefault( - isItemInHotbar - ? () => hotbarStore.removeFromHotbar(entity.getId()) - : () => hotbarStore.addToHotbar(entity), - ); - - return ( - <> - - {entity.spec.icon?.material && } - - {entity.getName()} - - - ); -}; - -const namedCategoryColumnInjectable = getInjectable({ - id: "name-category-column", - instantiate: (di): RegisteredAdditionalCategoryColumn => ({ - id: "name", - priority: 0, - renderCell: renderEntityName(di.inject(hotbarStoreInjectable)), - titleProps: { - title: "Name", - className: styles.entityName, - id: "name", - sortBy: "name", - }, - searchFilter: (entity) => entity.getName(), - sortCallback: (entity) => `name=${entity.getName()}`, - }), -}); - -export default namedCategoryColumnInjectable; diff --git a/packages/core/src/renderer/components/+catalog/columns/render-named-category-column-cell.injectable.tsx b/packages/core/src/renderer/components/+catalog/columns/render-named-category-column-cell.injectable.tsx new file mode 100644 index 0000000000..21fd683ffb --- /dev/null +++ b/packages/core/src/renderer/components/+catalog/columns/render-named-category-column-cell.injectable.tsx @@ -0,0 +1,59 @@ +/** + * Copyright (c) OpenLens Authors. All rights reserved. + * Licensed under MIT License. See LICENSE in root directory for more information. + */ +import { getInjectable } from "@ogre-tools/injectable"; +import styles from "../catalog.module.scss"; +import React from "react"; +import activeHotbarInjectable from "../../../../features/hotbar/storage/common/active.injectable"; +import { Avatar } from "../../avatar"; +import type { RegisteredAdditionalCategoryColumn } from "../custom-category-columns"; +import { Icon } from "../../icon"; +import { prevDefault } from "@k8slens/utilities"; + +const renderNamedCategoryColumnCellInjectable = getInjectable({ + id: "render-named-category-column-cell", + instantiate: (di): RegisteredAdditionalCategoryColumn["renderCell"] => { + const activeHotbar = di.inject(activeHotbarInjectable); + + return (entity) => { + const hotbar = activeHotbar.get(); + + if (!hotbar) { + return null; + } + + const isItemInHotbar = hotbar.hasEntity(entity.getId()); + const onClick = prevDefault(( + isItemInHotbar + ? () => hotbar.removeEntity(entity.getId()) + : () => hotbar.addEntity(entity) + )); + + return ( + <> + + {entity.spec.icon?.material && } + + {entity.getName()} + + + ); + }; + }, +}); + +export default renderNamedCategoryColumnCellInjectable; diff --git a/packages/core/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx b/packages/core/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx index bae8895ff7..bab9b79eb5 100644 --- a/packages/core/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx +++ b/packages/core/src/renderer/components/+catalog/hotbar-toggle-menu-item.tsx @@ -9,11 +9,12 @@ import { MenuItem } from "../menu"; import type { CatalogEntity } from "../../api/catalog-entity"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; -import type { HotbarStore } from "../../../common/hotbars/store"; +import type { IComputedValue } from "mobx"; +import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; +import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable"; interface Dependencies { - hotbarStore: HotbarStore; + activeHotbar: IComputedValue; } interface HotbarToggleMenuItemProps { @@ -25,19 +26,19 @@ interface HotbarToggleMenuItemProps { function NonInjectedHotbarToggleMenuItem({ addContent, entity, - hotbarStore, + activeHotbar, removeContent, }: Dependencies & HotbarToggleMenuItemProps) { - const [itemInHotbar, setItemInHotbar] = useState(hotbarStore.isAddedToActive(entity)); + const [itemInHotbar, setItemInHotbar] = useState(activeHotbar.get()?.hasEntity(entity.getId()) ?? false); return ( { if (itemInHotbar) { - hotbarStore.removeFromHotbar(entity.getId()); + activeHotbar.get()?.removeEntity(entity.getId()); setItemInHotbar(false); } else { - hotbarStore.addToHotbar(entity); + activeHotbar.get()?.addEntity(entity); setItemInHotbar(true); } }} @@ -47,14 +48,10 @@ function NonInjectedHotbarToggleMenuItem({ ); } -export const HotbarToggleMenuItem = withInjectables( - NonInjectedHotbarToggleMenuItem, - - { - getProps: (di, props) => ({ - hotbarStore: di.inject(hotbarStoreInjectable), - ...props, - }), - }, -); +export const HotbarToggleMenuItem = withInjectables(NonInjectedHotbarToggleMenuItem, { + getProps: (di, props) => ({ + ...props, + activeHotbar: di.inject(activeHotbarInjectable), + }), +}); diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx index 059f8c15eb..64d4028f24 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/general-settings.injectable.tsx @@ -6,8 +6,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; import type { KubernetesCluster } from "../../../../common/catalog-entities"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterIconSetting } from "../../cluster-settings/icon-settings"; import { ClusterKubeconfig } from "../../cluster-settings/kubeconfig"; import { ClusterNameSetting } from "../../cluster-settings/name-setting"; diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx index 1be5f7e78a..0a860300a3 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/metrics-settings.injectable.tsx @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterMetricsSetting } from "../../cluster-settings/metrics-setting"; import { ClusterPrometheusSetting } from "../../cluster-settings/prometheus-setting"; import { ShowMetricsSetting } from "../../cluster-settings/show-metrics"; diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx index a7c86265b8..c84827f2a7 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/namespace-settings.injectable.tsx @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterAccessibleNamespaces } from "../../cluster-settings/accessible-namespaces"; import type { EntitySettingViewProps } from "../extension-registrator.injectable"; import { entitySettingInjectionToken } from "../token"; diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx index ff899095d6..1a1424b5b3 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/node-shell-settings.injectable.tsx @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterNodeShellSetting } from "../../cluster-settings/node-shell-setting"; import type { EntitySettingViewProps } from "../extension-registrator.injectable"; import { entitySettingInjectionToken } from "../token"; diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx index 4479a04c2a..90b30ce7d2 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/proxy-settings.injectable.tsx @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterProxySetting } from "../../cluster-settings/proxy-setting"; import type { EntitySettingViewProps } from "../extension-registrator.injectable"; import { entitySettingInjectionToken } from "../token"; diff --git a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx index e778c97728..6448c935bc 100644 --- a/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx +++ b/packages/core/src/renderer/components/+entity-settings/internal-kubernetes-cluster/terminal-settings.injectable.tsx @@ -5,8 +5,8 @@ import { getInjectable } from "@ogre-tools/injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import React from "react"; -import type { GetClusterById } from "../../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterLocalTerminalSetting } from "../../cluster-settings/local-terminal-settings"; import type { EntitySettingViewProps } from "../extension-registrator.injectable"; import { entitySettingInjectionToken } from "../token"; diff --git a/packages/core/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.tsx b/packages/core/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.tsx index 6a03466abd..2707f99a3b 100644 --- a/packages/core/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.tsx +++ b/packages/core/src/renderer/components/+extensions/get-base-registry-url/get-base-registry-url.injectable.tsx @@ -7,15 +7,15 @@ import { getInjectable } from "@ogre-tools/injectable"; import React from "react"; import execFileInjectable from "../../../../common/fs/exec-file.injectable"; import loggerInjectable from "../../../../common/logger.injectable"; -import { defaultExtensionRegistryUrl } from "../../../../common/user-store/preferences-helpers"; -import userStoreInjectable from "../../../../common/user-store/user-store.injectable"; +import { defaultExtensionRegistryUrl } from "../../../../features/user-preferences/common/preferences-helpers"; +import userPreferencesStateInjectable from "../../../../features/user-preferences/common/state.injectable"; import showErrorNotificationInjectable from "../../notifications/show-error-notification.injectable"; const getBaseRegistryUrlInjectable = getInjectable({ id: "get-base-registry-url", instantiate: (di) => { - const { extensionRegistryUrl } = di.inject(userStoreInjectable); + const { extensionRegistryUrl } = di.inject(userPreferencesStateInjectable); const showErrorNotification = di.inject(showErrorNotificationInjectable); const logger = di.inject(loggerInjectable); const execFile = di.inject(execFileInjectable); diff --git a/packages/core/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts b/packages/core/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts index d6b23b582f..a90e3d63d9 100644 --- a/packages/core/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts +++ b/packages/core/src/renderer/components/+workloads-overview/workloads/workloads.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import { shouldShowResourceInjectionToken } from "../../../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { byOrderNumber } from "../../../../common/utils/composable-responsibilities/orderable/orderable"; import { workloadInjectionToken } from "./workload-injection-token"; diff --git a/packages/core/src/renderer/components/avatar/avatar.tsx b/packages/core/src/renderer/components/avatar/avatar.tsx index 84892b081c..991b7e761b 100644 --- a/packages/core/src/renderer/components/avatar/avatar.tsx +++ b/packages/core/src/renderer/components/avatar/avatar.tsx @@ -8,9 +8,9 @@ import styles from "./avatar.module.scss"; import type { ImgHTMLAttributes, MouseEventHandler } from "react"; import React from "react"; import randomColor from "randomcolor"; -import GraphemeSplitter from "grapheme-splitter"; import type { SingleOrMany } from "@k8slens/utilities"; -import { cssNames, isDefined, iter } from "@k8slens/utilities"; +import { cssNames } from "@k8slens/utilities"; +import { computeDefaultShortName } from "../../../common/catalog/helpers"; export interface AvatarProps { title: string; @@ -28,40 +28,6 @@ export interface AvatarProps { "data-testid"?: string; } -function getNameParts(name: string): string[] { - const byWhitespace = name.split(/\s+/); - - if (byWhitespace.length > 1) { - return byWhitespace; - } - - const byDashes = name.split(/[-_]+/); - - if (byDashes.length > 1) { - return byDashes; - } - - return name.split(/@+/); -} - -function getLabelFromTitle(title: string) { - if (!title) { - return "??"; - } - - const [rawFirst, rawSecond, rawThird] = getNameParts(title); - const splitter = new GraphemeSplitter(); - const first = splitter.iterateGraphemes(rawFirst); - const second = rawSecond ? splitter.iterateGraphemes(rawSecond): first; - const third = rawThird ? splitter.iterateGraphemes(rawThird) : iter.newEmpty(); - - return [ - ...iter.take(first, 1), - ...iter.take(second, 1), - ...iter.take(third, 1), - ].filter(isDefined).join(""); -} - export const Avatar = ({ title, variant = "rounded", @@ -104,6 +70,6 @@ export const Avatar = ({ alt={title} /> ) - : children || getLabelFromTitle(title)} + : children || computeDefaultShortName(title)} ); diff --git a/packages/core/src/renderer/components/catalog-entities/weblink-add-command.tsx b/packages/core/src/renderer/components/catalog-entities/weblink-add-command.tsx index 0209ff93fa..221af86f38 100644 --- a/packages/core/src/renderer/components/catalog-entities/weblink-add-command.tsx +++ b/packages/core/src/renderer/components/catalog-entities/weblink-add-command.tsx @@ -7,15 +7,15 @@ import React from "react"; import { observer } from "mobx-react"; import { Input } from "../input"; import { isUrl } from "../input/input_validators"; -import type { WeblinkStore } from "../../../common/weblinks-store/weblink-store"; import { computed, makeObservable, observable } from "mobx"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import weblinkStoreInjectable from "../../../common/weblinks-store/weblink-store.injectable"; +import type { AddWeblink } from "../../../features/weblinks/common/add.injectable"; +import addWeblinkInjectable from "../../../features/weblinks/common/add.injectable"; interface Dependencies { closeCommandOverlay: () => void; - weblinkStore: WeblinkStore; + addWeblink: AddWeblink; } @@ -42,7 +42,7 @@ class NonInjectedWeblinkAddCommand extends React.Component { } onSubmit(name: string) { - this.props.weblinkStore.add({ + this.props.addWeblink({ name: name || this.url, url: this.url, }); @@ -97,6 +97,6 @@ export const WeblinkAddCommand = withInjectables(NonInjectedWeblin getProps: (di, props) => ({ ...props, closeCommandOverlay: di.inject(commandOverlayInjectable).close, - weblinkStore: di.inject(weblinkStoreInjectable), + addWeblink: di.inject(addWeblinkInjectable), }), }); diff --git a/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts b/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts index 3f40455527..079bf22b2d 100644 --- a/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts +++ b/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.injectable.ts @@ -3,8 +3,8 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; import { ClusterFrameHandler } from "./cluster-frame-handler"; import emitClusterVisibilityInjectable from "./emit-cluster-visibility.injectable"; diff --git a/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.ts b/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.ts index 7420fa680a..eca0e28e11 100644 --- a/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.ts +++ b/packages/core/src/renderer/components/cluster-manager/cluster-frame-handler.ts @@ -9,8 +9,8 @@ import type { Disposer } from "@k8slens/utilities"; import { onceDefined } from "@k8slens/utilities"; import assert from "assert"; import type { Logger } from "../../../common/logger"; -import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable"; import { getClusterFrameUrl } from "../../../common/utils"; +import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable"; export interface LensView { isLoaded: boolean; diff --git a/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx b/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx index 6749fdd9d3..1e5062b313 100644 --- a/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx @@ -18,10 +18,10 @@ import clusterViewRouteParametersInjectable from "./cluster-view-route-parameter import clusterFrameHandlerInjectable from "./cluster-frame-handler.injectable"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; -import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import type { RequestClusterActivation } from "../../../features/cluster/activation/common/request-token"; import requestClusterActivationInjectable from "../../../features/cluster/activation/renderer/request-activation.injectable"; +import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable"; +import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; interface Dependencies { clusterId: IComputedValue; diff --git a/packages/core/src/renderer/components/delete-cluster-dialog/view.tsx b/packages/core/src/renderer/components/delete-cluster-dialog/view.tsx index b1548354fd..69666efede 100644 --- a/packages/core/src/renderer/components/delete-cluster-dialog/view.tsx +++ b/packages/core/src/renderer/components/delete-cluster-dialog/view.tsx @@ -15,9 +15,7 @@ import { Dialog } from "../dialog"; import { Icon } from "../icon"; import { Select } from "../select"; import { Checkbox } from "../checkbox"; -import type { HotbarStore } from "../../../common/hotbars/store"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import type { DeleteClusterDialogState } from "./state.injectable"; import deleteClusterDialogStateInjectable from "./state.injectable"; import type { RequestSetClusterAsDeleting } from "../../../features/cluster/delete-dialog/renderer/request-set-as-deleting.injectable"; @@ -32,16 +30,18 @@ import showErrorNotificationInjectable from "../notifications/show-error-notific import { isCurrentContext } from "./is-current-context"; import type { IsInLocalKubeconfig } from "./is-in-local-kubeconfig.injectable"; import isInLocalKubeconfigInjectable from "./is-in-local-kubeconfig.injectable"; +import type { RemoveEntityFromAllHotbars } from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable"; +import removeEntityFromAllHotbarsInjectable from "../../../features/hotbar/storage/common/remove-entity-from-all.injectable"; interface Dependencies { state: IObservableValue; - hotbarStore: HotbarStore; requestSetClusterAsDeleting: RequestSetClusterAsDeleting; requestDeleteCluster: RequestDeleteCluster; requestClearClusterAsDeleting: RequestClearClusterAsDeleting; showErrorNotification: ShowNotification; saveKubeconfig: SaveKubeconfig; isInLocalKubeconfig: IsInLocalKubeconfig; + removeEntityFromAllHotbars: RemoveEntityFromAllHotbars; } @observer @@ -65,7 +65,7 @@ class NonInjectedDeleteClusterDialog extends React.Component { try { await this.props.saveKubeconfig(config, cluster.kubeConfigPath.get()); - this.props.hotbarStore.removeAllHotbarItems(cluster.id); + this.props.removeEntityFromAllHotbars(cluster.id); await this.props.requestDeleteCluster(cluster.id); } catch(error) { this.props.showErrorNotification(`Cannot remove cluster, failed to process config file. ${error}`); @@ -267,7 +267,6 @@ class NonInjectedDeleteClusterDialog extends React.Component { export const DeleteClusterDialog = withInjectables(NonInjectedDeleteClusterDialog, { getProps: (di) => ({ - hotbarStore: di.inject(hotbarStoreInjectable), state: di.inject(deleteClusterDialogStateInjectable), requestSetClusterAsDeleting: di.inject(requestSetClusterAsDeletingInjectable), requestClearClusterAsDeleting: di.inject(requestClearClusterAsDeletingInjectable), @@ -275,5 +274,6 @@ export const DeleteClusterDialog = withInjectables(NonInjectedDele saveKubeconfig: di.inject(saveKubeconfigInjectable), showErrorNotification: di.inject(showErrorNotificationInjectable), isInLocalKubeconfig: di.inject(isInLocalKubeconfigInjectable), + removeEntityFromAllHotbars: di.inject(removeEntityFromAllHotbarsInjectable), }), }); diff --git a/packages/core/src/renderer/components/dock/logs/list.tsx b/packages/core/src/renderer/components/dock/logs/list.tsx index 6b3eded365..d0e437e720 100644 --- a/packages/core/src/renderer/components/dock/logs/list.tsx +++ b/packages/core/src/renderer/components/dock/logs/list.tsx @@ -15,7 +15,6 @@ import { disposeOnUnmount, observer } from "mobx-react"; import moment from "moment-timezone"; import type { Align, ListOnScrollProps } from "react-window"; import { SearchStore } from "../../../search-store/search-store"; -import type { UserStore } from "../../../../common/user-store"; import { array, cssNames } from "@k8slens/utilities"; import type { VirtualListRef } from "../../virtual-list"; import { VirtualList } from "../../virtual-list"; @@ -23,8 +22,9 @@ import { ToBottom } from "./to-bottom"; import type { LogTabViewModel } from "../logs/logs-view-model"; import { Spinner } from "../../spinner"; import { withInjectables } from "@ogre-tools/injectable-react"; -import userStoreInjectable from "../../../../common/user-store/user-store.injectable"; import autoBindReact from "auto-bind/react"; +import type { UserPreferencesState } from "../../../../features/user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../../features/user-preferences/common/state.injectable"; export interface LogListProps { model: LogTabViewModel; @@ -37,7 +37,7 @@ export interface LogListRef { } interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } @observer @@ -129,7 +129,7 @@ class NonForwardedLogList extends React.Component (`${logTimestamp && moment.tz(logTimestamp, this.props.userStore.localeTimezone).format()}${log}`)); + .map(([logTimestamp, log]) => (`${logTimestamp && moment.tz(logTimestamp, this.props.state.localeTimezone).format()}${log}`)); } /** @@ -283,7 +283,7 @@ class NonForwardedLogList extends React.Component }>(NonForwardedLogList, { getProps: (di, props) => ({ ...props, - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), }), }); diff --git a/packages/core/src/renderer/components/dock/terminal/create-terminal.injectable.ts b/packages/core/src/renderer/components/dock/terminal/create-terminal.injectable.ts index 2ff82e2c17..88e7514855 100644 --- a/packages/core/src/renderer/components/dock/terminal/create-terminal.injectable.ts +++ b/packages/core/src/renderer/components/dock/terminal/create-terminal.injectable.ts @@ -8,13 +8,12 @@ import { Terminal } from "./terminal"; import type { TabId } from "../dock/store"; import type { TerminalApi } from "../../../api/terminal-api"; import terminalSpawningPoolInjectable from "./terminal-spawning-pool.injectable"; -import terminalConfigInjectable from "../../../../common/user-store/terminal-config.injectable"; -import terminalCopyOnSelectInjectable - from "../../../../common/user-store/terminal-copy-on-select.injectable"; import isMacInjectable from "../../../../common/vars/is-mac.injectable"; import openLinkInBrowserInjectable from "../../../../common/utils/open-link-in-browser.injectable"; import xtermColorThemeInjectable from "../../../themes/terminal-colors.injectable"; import loggerInjectable from "../../../../common/logger.injectable"; +import terminalConfigInjectable from "../../../../features/user-preferences/common/terminal-config.injectable"; +import terminalCopyOnSelectInjectable from "../../../../features/user-preferences/common/terminal-copy-on-select.injectable"; export type CreateTerminal = (tabId: TabId, api: TerminalApi) => Terminal; diff --git a/packages/core/src/renderer/components/dock/terminal/terminal.ts b/packages/core/src/renderer/components/dock/terminal/terminal.ts index 9e307aa7dc..a0c3a5384a 100644 --- a/packages/core/src/renderer/components/dock/terminal/terminal.ts +++ b/packages/core/src/renderer/components/dock/terminal/terminal.ts @@ -14,11 +14,11 @@ import { disposer } from "@k8slens/utilities"; import { once } from "lodash"; import { clipboard } from "electron"; import type { Logger } from "../../../../common/logger"; -import type { TerminalConfig } from "../../../../common/user-store/preferences-helpers"; import assert from "assert"; import { TerminalChannels } from "../../../../common/terminal/channels"; import { LinkProvider } from "xterm-link-provider"; import type { OpenLinkInBrowser } from "../../../../common/utils/open-link-in-browser.injectable"; +import type { TerminalConfig } from "../../../../features/user-preferences/common/preferences-helpers"; export interface TerminalDependencies { readonly spawningPool: HTMLElement; diff --git a/packages/core/src/renderer/components/hotbar/__tests__/__snapshots__/hotbar-remove-command.test.tsx.snap b/packages/core/src/renderer/components/hotbar/__tests__/__snapshots__/hotbar-remove-command.test.tsx.snap new file mode 100644 index 0000000000..89eacf6e48 --- /dev/null +++ b/packages/core/src/renderer/components/hotbar/__tests__/__snapshots__/hotbar-remove-command.test.tsx.snap @@ -0,0 +1,97 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders w/o errors 1`] = ` +
+
+ + + option , selected. + + + 2 results available. Use Up and Down to choose options, press Enter to select the currently focused option, press Escape to exit the menu, press Tab to select the option and exit the menu. + + + +
+
+
+ Remove hotbar +
+
+ +
+
+
+
+
+
+
+ 1: default +
+
+ 2: non-default +
+
+
+
+
+`; diff --git a/packages/core/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx b/packages/core/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx index bb370562f3..98b007206c 100644 --- a/packages/core/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx +++ b/packages/core/src/renderer/components/hotbar/__tests__/hotbar-remove-command.test.tsx @@ -5,75 +5,56 @@ import "@testing-library/jest-dom/extend-expect"; import { HotbarRemoveCommand } from "../hotbar-remove-command"; +import type { RenderResult } from "@testing-library/react"; import { fireEvent } from "@testing-library/react"; import React from "react"; -import type { DiContainer } from "@ogre-tools/injectable"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; -import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; import { ConfirmDialog } from "../../confirm-dialog"; import directoryForUserDataInjectable from "../../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable"; -import type { HotbarStore } from "../../../../common/hotbars/store"; -import storesAndApisCanBeCreatedInjectable from "../../../stores-apis-can-be-created.injectable"; - -const mockHotbars: Partial> = { - "1": { - id: "1", - name: "Default", - items: [], - }, -}; +import hotbarsStateInjectable from "../../../../features/hotbar/storage/common/state.injectable"; +import type { CreateHotbar } from "../../../../features/hotbar/storage/common/create-hotbar.injectable"; +import createHotbarInjectable from "../../../../features/hotbar/storage/common/create-hotbar.injectable"; +import type { IComputedValue } from "mobx"; +import type { Hotbar } from "../../../../features/hotbar/storage/common/hotbar"; +import hotbarsInjectable from "../../../../features/hotbar/storage/common/hotbars.injectable"; describe("", () => { - let di: DiContainer; - let render: DiRender; + let result: RenderResult; + let createHotbar: CreateHotbar; + let hotbars: IComputedValue; beforeEach(() => { - di = getDiForUnitTesting(); + const di = getDiForUnitTesting(); + const render = renderFor(di); - di.override(storesAndApisCanBeCreatedInjectable, () => true); di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data"); - render = renderFor(di); - }); + createHotbar = di.inject(createHotbarInjectable); + hotbars = di.inject(hotbarsInjectable); - it("renders w/o errors", () => { - di.override(hotbarStoreInjectable, () => ({ - hotbars: [mockHotbars["1"]], - getById: (id: string) => mockHotbars[id], - remove: () => { - }, - hotbarIndex: () => 0, - getDisplayLabel: () => "1: Default", - }) as unknown as HotbarStore); + const hotbarsState = di.inject(hotbarsStateInjectable); + const defaultHotbar = createHotbar({ name: "default" }); + const nonDefaultHotbar = createHotbar({ name: "non-default" }); - const { container } = render(); + hotbarsState.set(defaultHotbar.id, defaultHotbar); + hotbarsState.set(nonDefaultHotbar.id, nonDefaultHotbar); - expect(container).toBeInstanceOf(HTMLElement); - }); - - it("calls remove if you click on the entry", () => { - const removeMock = jest.fn(); - - di.override(hotbarStoreInjectable, () => ({ - hotbars: [mockHotbars["1"]], - getById: (id: string) => mockHotbars[id], - remove: removeMock, - hotbarIndex: () => 0, - getDisplayLabel: () => "1: Default", - }) as unknown as HotbarStore); - - const { getByText } = render( + result = render(( <> - , - ); + + )); + }); - fireEvent.click(getByText("1: Default")); - fireEvent.click(getByText("Remove Hotbar")); + it("renders w/o errors", () => { + expect(result.container).toMatchSnapshot(); + }); - expect(removeMock).toHaveBeenCalled(); + it("calls remove if you click on the entry", () => { + fireEvent.click(result.getByText("1: default")); + fireEvent.click(result.getByText("Remove Hotbar")); + expect(hotbars.get().length).toBe(1); }); }); diff --git a/packages/core/src/renderer/components/hotbar/hotbar-add-command.tsx b/packages/core/src/renderer/components/hotbar/hotbar-add-command.tsx index 3fd261b213..cb97f638e9 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-add-command.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-add-command.tsx @@ -7,15 +7,15 @@ import React from "react"; import { observer } from "mobx-react"; import type { InputValidator } from "../input"; import { Input } from "../input"; -import type { CreateHotbarData, CreateHotbarOptions } from "../../../common/hotbars/types"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; import uniqueHotbarNameInjectable from "../input/validators/unique-hotbar-name.injectable"; -import addHotbarInjectable from "../../../common/hotbars/add-hotbar.injectable"; +import type { AddHotbar } from "../../../features/hotbar/storage/common/add.injectable"; +import addHotbarInjectable from "../../../features/hotbar/storage/common/add.injectable"; interface Dependencies { closeCommandOverlay: () => void; - addHotbar: (data: CreateHotbarData, opts: CreateHotbarOptions) => void; + addHotbar: AddHotbar; uniqueHotbarName: InputValidator; } diff --git a/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx b/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx index e83848cdfd..b9ba43b711 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx @@ -5,7 +5,7 @@ import "./hotbar-menu.scss"; -import React from "react"; +import React, { useState } from "react"; import { observer } from "mobx-react"; import { HotbarEntityIcon } from "./hotbar-entity-icon"; import type { IClassName } from "@k8slens/utilities"; @@ -16,57 +16,46 @@ import { DragDropContext, Draggable, Droppable, type DropResult } from "react-be import { HotbarSelector } from "./hotbar-selector"; import { HotbarCell } from "./hotbar-cell"; import { HotbarIcon } from "./hotbar-icon"; -import type { HotbarItem } from "../../../common/hotbars/types"; -import { defaultHotbarCells } from "../../../common/hotbars/types"; -import { action, makeObservable, observable } from "mobx"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; +import type { HotbarItem } from "../../../features/hotbar/storage/common/types"; +import { defaultHotbarCells } from "../../../features/hotbar/storage/common/types"; +import type { IComputedValue } from "mobx"; import { withInjectables } from "@ogre-tools/injectable-react"; -import type { HotbarStore } from "../../../common/hotbars/store"; import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; +import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; +import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable"; export interface HotbarMenuProps { className?: IClassName; } interface Dependencies { - hotbarStore: HotbarStore; + activeHotbar: IComputedValue; entityRegistry: CatalogEntityRegistry; } -@observer -class NonInjectedHotbarMenu extends React.Component { - @observable draggingOver = false; +const NonInjectedHotbarMenu = observer((props: Dependencies & HotbarMenuProps) => { + const { + activeHotbar, + entityRegistry, + className, + } = props; - constructor(props: Dependencies & HotbarMenuProps) { - super(props); - makeObservable(this); - } + const [draggingOver, setDraggingOver] = useState(false); + const hotbar = activeHotbar.get(); - get hotbar() { - return this.props.hotbarStore.getActive(); - } - - getEntity(item: HotbarItem | null) { - const hotbar = this.props.hotbarStore.getActive(); - - if (!hotbar || !item) { - return null; + const getEntity = (item: HotbarItem | null) => { + if (!item) { + return undefined; } - return this.props.entityRegistry.getById(item.entity.uid) ?? null; - } + return entityRegistry.getById(item.entity.uid); + }; + const onDragStart = () => setDraggingOver(true); + const onDragEnd = (result: DropResult) => { + setDraggingOver(false); - @action - onDragStart() { - this.draggingOver = true; - } - - @action - onDragEnd(result: DropResult) { const { source, destination } = result; - this.draggingOver = false; - if (!destination) { // Dropped outside of the list return; } @@ -74,135 +63,115 @@ class NonInjectedHotbarMenu extends React.Component { - const hotbar = this.props.hotbarStore; - - hotbar.removeFromHotbar(uid); + hotbar?.restack(from, to); }; - - addItem = (entity: CatalogEntity, index = -1) => { - const hotbar = this.props.hotbarStore; - - hotbar.addToHotbar(entity, index); + const removeItem = (entityId: string) => { + hotbar?.removeEntity(entityId); }; - - getMoveAwayDirection(entityId: string | undefined | null, cellIndex: number) { - if (!entityId) { + const addItem = (entity: CatalogEntity) => { + hotbar?.addEntity(entity); + }; + const getMoveAwayDirection = (entityId: string | undefined | null, cellIndex: number) => { + if (!entityId || !hotbar) { return "animateDown"; } - const draggableItemIndex = this.hotbar.items.findIndex(item => item?.entity.uid == entityId); + const draggableItemIndex = hotbar.items.findIndex(item => item?.entity.uid == entityId); return draggableItemIndex > cellIndex ? "animateDown" : "animateUp"; - } + }; - renderGrid() { - return this.hotbar.items.map((item, index) => { - const entity = this.getEntity(item); - - return ( - - {(provided, snapshot) => ( - - {item && ( - - {(provided, snapshot) => { - const style = { - zIndex: defaultHotbarCells - index, - position: "absolute", - ...provided.draggableProps.style, - } as React.CSSProperties; - - return ( -
- {entity ? ( - this.props.entityRegistry.onRun(entity)} - className={cssNames({ isDragging: snapshot.isDragging })} - remove={this.removeItem} - add={this.addItem} - size={40} - /> - ) : ( - this.removeItem(item.entity.uid), - }, - ]} - disabled - size={40} - /> - )} -
- ); - }} -
- )} - {provided.placeholder} -
- )} -
- ); - }); - } - - render() { - const { className, hotbarStore } = this.props; - const hotbar = hotbarStore.getActive(); + const renderGrid = () => hotbar?.items.map((item, index) => { + const entity = getEntity(item); return ( -
-
- this.onDragStart()} - onDragEnd={(result) => this.onDragEnd(result)}> - {this.renderGrid()} - -
- -
+ + {(provided, snapshot) => ( + + {item && ( + + {(provided, snapshot) => ( +
+ {entity ? ( + entityRegistry.onRun(entity)} + className={cssNames({ isDragging: snapshot.isDragging })} + remove={removeItem} + add={addItem} + size={40} /> + ) : ( + removeItem(item.entity.uid), + }, + ]} + disabled + size={40} /> + )} +
+ )} +
+ )} + {provided.placeholder} +
+ )} +
); - } -} + }); + + return ( +
+
+ onDragStart()} + onDragEnd={(result) => onDragEnd(result)}> + {renderGrid()} + +
+ +
+ ); +}); export const HotbarMenu = withInjectables(NonInjectedHotbarMenu, { getProps: (di, props) => ({ ...props, - hotbarStore: di.inject(hotbarStoreInjectable), entityRegistry: di.inject(catalogEntityRegistryInjectable), + activeHotbar: di.inject(activeHotbarInjectable), }), }); diff --git a/packages/core/src/renderer/components/hotbar/hotbar-remove-command.tsx b/packages/core/src/renderer/components/hotbar/hotbar-remove-command.tsx index 0199b7f739..4f2b1a4e17 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-remove-command.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-remove-command.tsx @@ -6,23 +6,32 @@ import React from "react"; import { observer } from "mobx-react"; import { Select } from "../select"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; import commandOverlayInjectable from "../command-palette/command-overlay.injectable"; -import type { HotbarStore } from "../../../common/hotbars/store"; import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable"; import openConfirmDialogInjectable from "../confirm-dialog/open.injectable"; +import type { IComputedValue } from "mobx"; +import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; +import type { ComputeHotbarDisplayLabel } from "../../../features/hotbar/storage/common/compute-display-label.injectable"; +import computeHotbarDisplayLabelInjectable from "../../../features/hotbar/storage/common/compute-display-label.injectable"; +import hotbarsInjectable from "../../../features/hotbar/storage/common/hotbars.injectable"; +import type { RemoveHotbar } from "../../../features/hotbar/storage/common/remove.injectable"; +import removeHotbarInjectable from "../../../features/hotbar/storage/common/remove.injectable"; interface Dependencies { closeCommandOverlay: () => void; openConfirmDialog: OpenConfirmDialog; - hotbarStore: HotbarStore; + hotbars: IComputedValue; + computeHotbarDisplayLabel: ComputeHotbarDisplayLabel; + removeHotbar: RemoveHotbar; } const NonInjectedHotbarRemoveCommand = observer(({ closeCommandOverlay, - hotbarStore, openConfirmDialog, + hotbars, + computeHotbarDisplayLabel, + removeHotbar, }: Dependencies) => ( ); } } else { - hotbarStore.setActiveHotbar(option.value); + setAsActiveHotbar(option.value); commandOverlay.close(); } }} components={{ DropdownIndicator: null, IndicatorSeparator: null }} menuIsOpen={true} options={[ - ...hotbarStore.hotbars.map(hotbar => ({ + ...hotbars.get().map(hotbar => ({ value: hotbar, - label: hotbarStore.getDisplayLabel(hotbar), + label: computeHotbarDisplayLabel(hotbar), })), { value: hotbarAddAction, label: "Add hotbar ...", }, - ...ignoreIf(hotbarStore.hotbars.length > 1, [ + ...ignoreIf(hotbars.get().length > 1, [ { value: hotbarRemoveAction, label: "Remove hotbar ...", @@ -85,8 +94,10 @@ const NonInjectedHotbarSwitchCommand = observer(({ export const HotbarSwitchCommand = withInjectables(NonInjectedHotbarSwitchCommand, { getProps: (di, props) => ({ - hotbarStore: di.inject(hotbarStoreInjectable), - commandOverlay: di.inject(commandOverlayInjectable), ...props, + commandOverlay: di.inject(commandOverlayInjectable), + computeHotbarDisplayLabel: di.inject(computeHotbarDisplayLabelInjectable), + hotbars: di.inject(hotbarsInjectable), + setAsActiveHotbar: di.inject(setAsActiveHotbarInjectable), }), }); diff --git a/packages/core/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts b/packages/core/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts index e4d68e9f29..48c1fb6fc1 100644 --- a/packages/core/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts +++ b/packages/core/src/renderer/components/input/validators/unique-hotbar-name.injectable.ts @@ -4,19 +4,19 @@ */ import { getInjectable } from "@ogre-tools/injectable"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; +import findHotbarByNameInjectable from "../../../../features/hotbar/storage/common/find-by-name.injectable"; import { inputValidator } from "../input_validators"; const uniqueHotbarNameInjectable = getInjectable({ id: "unique-hotbar-name", instantiate: di => { - const store = di.inject(hotbarStoreInjectable); + const findHotbarByName = di.inject(findHotbarByNameInjectable); return inputValidator({ condition: ({ required }) => required, message: () => "Hotbar with this name already exists", - validate: value => !store.findByName(value), + validate: value => !findHotbarByName(value), }); }, }); diff --git a/packages/core/src/renderer/components/item-object-list/content.tsx b/packages/core/src/renderer/components/item-object-list/content.tsx index 17b864ed6e..2b11a97570 100644 --- a/packages/core/src/renderer/components/item-object-list/content.tsx +++ b/packages/core/src/renderer/components/item-object-list/content.tsx @@ -25,15 +25,17 @@ import type { LensTheme } from "../../themes/lens-theme"; import { MenuActions } from "../menu/menu-actions"; import { MenuItem } from "../menu"; import { Checkbox } from "../checkbox"; -import type { UserStore } from "../../../common/user-store"; import type { ItemListStore } from "./list-layout"; import { withInjectables } from "@ogre-tools/injectable-react"; -import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import pageFiltersStoreInjectable from "./page-filters/store.injectable"; import type { OpenConfirmDialog } from "../confirm-dialog/open.injectable"; import openConfirmDialogInjectable from "../confirm-dialog/open.injectable"; import activeThemeInjectable from "../../themes/active.injectable"; import autoBindReact from "auto-bind/react"; +import type { ToggleTableColumnVisibility } from "../../../features/user-preferences/common/toggle-table-column-visibility.injectable"; +import toggleTableColumnVisibilityInjectable from "../../../features/user-preferences/common/toggle-table-column-visibility.injectable"; +import type { IsTableColumnHidden } from "../../../features/user-preferences/common/is-table-column-hidden.injectable"; +import isTableColumnHiddenInjectable from "../../../features/user-preferences/common/is-table-column-hidden.injectable"; export interface ItemListLayoutContentProps { getFilters: () => Filter[]; @@ -74,9 +76,10 @@ export interface ItemListLayoutContentProps; - userStore: UserStore; pageFiltersStore: PageFiltersStore; openConfirmDialog: OpenConfirmDialog; + toggleTableColumnVisibility: ToggleTableColumnVisibility; + isTableColumnHidden: IsTableColumnHidden; } @observer @@ -342,7 +345,7 @@ class NonInjectedItemListLayoutContent< showColumn({ id: columnId, showWithColumn }: TableCellProps): boolean { const { tableId, isConfigurable } = this.props; - return !isConfigurable || !tableId || !this.props.userStore.isTableColumnHidden(tableId, columnId, showWithColumn); + return !isConfigurable || !tableId || !this.props.isTableColumnHidden(tableId, columnId, showWithColumn); } renderColumnVisibilityMenu(tableId: string) { @@ -365,7 +368,7 @@ class NonInjectedItemListLayoutContent< `} value={this.showColumn(cellProps)} - onChange={() => this.props.userStore.toggleTableColumnVisibility(tableId, cellProps.id)} + onChange={() => this.props.toggleTableColumnVisibility(tableId, cellProps.id)} /> )) @@ -379,8 +382,9 @@ export const ItemListLayoutContent = withInjectables ({ ...props, activeTheme: di.inject(activeThemeInjectable), - userStore: di.inject(userStoreInjectable), pageFiltersStore: di.inject(pageFiltersStoreInjectable), openConfirmDialog: di.inject(openConfirmDialogInjectable), + toggleTableColumnVisibility: di.inject(toggleTableColumnVisibilityInjectable), + isTableColumnHidden: di.inject(isTableColumnHiddenInjectable), }), }) as (props: ItemListLayoutContentProps) => React.ReactElement; diff --git a/packages/core/src/renderer/components/layout/__tests__/__snapshots__/sidebar-cluster.test.tsx.snap b/packages/core/src/renderer/components/layout/__tests__/__snapshots__/sidebar-cluster.test.tsx.snap new file mode 100644 index 0000000000..26aab5675c --- /dev/null +++ b/packages/core/src/renderer/components/layout/__tests__/__snapshots__/sidebar-cluster.test.tsx.snap @@ -0,0 +1,36 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders w/o errors 1`] = ` +
+ +
+`; diff --git a/packages/core/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx b/packages/core/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx index f9d447dd5e..69bfe5458c 100644 --- a/packages/core/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx +++ b/packages/core/src/renderer/components/layout/__tests__/sidebar-cluster.test.tsx @@ -5,56 +5,47 @@ import React from "react"; import "@testing-library/jest-dom/extend-expect"; +import type { RenderResult } from "@testing-library/react"; import { fireEvent } from "@testing-library/react"; import { SidebarCluster } from "../sidebar-cluster"; import { KubernetesCluster } from "../../../../common/catalog-entities"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; -import type { DiRender } from "../../test-utils/renderFor"; import { renderFor } from "../../test-utils/renderFor"; -import hotbarStoreInjectable from "../../../../common/hotbars/store.injectable"; -import type { HotbarStore } from "../../../../common/hotbars/store"; - -const clusterEntity = new KubernetesCluster({ - metadata: { - uid: "test-uid", - name: "test-cluster", - source: "local", - labels: {}, - }, - spec: { - kubeconfigPath: "", - kubeconfigContext: "", - }, - status: { - phase: "connected", - }, -}); describe("", () => { - let render: DiRender; + let result: RenderResult; beforeEach(() => { const di = getDiForUnitTesting(); + const render = renderFor(di); - di.override(hotbarStoreInjectable, () => ({ - isAddedToActive: () => {}, - }) as unknown as HotbarStore); + const clusterEntity = new KubernetesCluster({ + metadata: { + uid: "test-uid", + name: "test-cluster", + source: "local", + labels: {}, + }, + spec: { + kubeconfigPath: "", + kubeconfigContext: "", + }, + status: { + phase: "connected", + }, + }); - render = renderFor(di); + result = render(); }); it("renders w/o errors", () => { - const { container } = render(); - - expect(container).toBeInstanceOf(HTMLElement); + expect(result.container).toMatchSnapshot(); }); it("renders cluster avatar and name", () => { - const { getByText, getAllByText } = render(); + expect(result.getByText("tc")).toBeInTheDocument(); - expect(getByText("tc")).toBeInTheDocument(); - - const v = getAllByText("test-cluster"); + const v = result.getAllByText("test-cluster"); expect(v.length).toBeGreaterThan(0); @@ -64,11 +55,8 @@ describe("", () => { }); it("renders cluster menu", () => { - const { getByTestId, getByText } = render(); - const link = getByTestId("sidebar-cluster-dropdown"); - - fireEvent.click(link); - expect(getByText("Add to Hotbar")).toBeInTheDocument(); + fireEvent.click(result.getByTestId("sidebar-cluster-dropdown")); + expect(result.getByText("Add to Hotbar")).toBeInTheDocument(); }); }); diff --git a/packages/core/src/renderer/components/layout/sidebar-cluster.tsx b/packages/core/src/renderer/components/layout/sidebar-cluster.tsx index ba5ae7e387..7ae6ee9405 100644 --- a/packages/core/src/renderer/components/layout/sidebar-cluster.tsx +++ b/packages/core/src/renderer/components/layout/sidebar-cluster.tsx @@ -14,8 +14,6 @@ import { Icon } from "../icon"; import { Menu, MenuItem } from "../menu"; import { Tooltip } from "../tooltip"; import { withInjectables } from "@ogre-tools/injectable-react"; -import hotbarStoreInjectable from "../../../common/hotbars/store.injectable"; -import type { HotbarStore } from "../../../common/hotbars/store"; import { observer } from "mobx-react"; import type { VisitEntityContextMenu } from "../../../common/catalog/visit-entity-context-menu.injectable"; import visitEntityContextMenuInjectable from "../../../common/catalog/visit-entity-context-menu.injectable"; @@ -23,6 +21,8 @@ import type { Navigate } from "../../navigation/navigate.injectable"; import type { NormalizeCatalogEntityContextMenu } from "../../catalog/normalize-menu-item.injectable"; import navigateInjectable from "../../navigation/navigate.injectable"; import normalizeCatalogEntityContextMenuInjectable from "../../catalog/normalize-menu-item.injectable"; +import type { ActiveHotbarModel } from "../../../features/hotbar/storage/common/toggling.injectable"; +import activeHotbarInjectable from "../../../features/hotbar/storage/common/toggling.injectable"; export interface SidebarClusterProps { clusterEntity: CatalogEntity | null | undefined; @@ -31,16 +31,16 @@ export interface SidebarClusterProps { interface Dependencies { navigate: Navigate; normalizeMenuItem: NormalizeCatalogEntityContextMenu; - hotbarStore: HotbarStore; visitEntityContextMenu: VisitEntityContextMenu; + entityInActiveHotbar: ActiveHotbarModel; } const NonInjectedSidebarCluster = observer(({ clusterEntity, - hotbarStore, visitEntityContextMenu: onContextMenuOpen, navigate, normalizeMenuItem, + entityInActiveHotbar, }: Dependencies & SidebarClusterProps) => { const [menuItems] = useState(observable.array()); const [opened, setOpened] = useState(false ); @@ -61,13 +61,10 @@ const NonInjectedSidebarCluster = observer(({ } const onMenuOpen = () => { - const isAddedToActive = hotbarStore.isAddedToActive(clusterEntity); - const title = isAddedToActive + const title = entityInActiveHotbar.hasEntity(clusterEntity.getId()) ? "Remove from Hotbar" : "Add to Hotbar"; - const onClick = isAddedToActive - ? () => hotbarStore.removeFromHotbar(clusterEntity.getId()) - : () => hotbarStore.addToHotbar(clusterEntity); + const onClick = () => entityInActiveHotbar.toggleEntity(clusterEntity); menuItems.replace([{ title, onClick }]); onContextMenuOpen(clusterEntity, { @@ -148,9 +145,9 @@ const NonInjectedSidebarCluster = observer(({ export const SidebarCluster = withInjectables(NonInjectedSidebarCluster, { getProps: (di, props) => ({ ...props, - hotbarStore: di.inject(hotbarStoreInjectable), visitEntityContextMenu: di.inject(visitEntityContextMenuInjectable), navigate: di.inject(navigateInjectable), normalizeMenuItem: di.inject(normalizeCatalogEntityContextMenuInjectable), + entityInActiveHotbar: di.inject(activeHotbarInjectable), }), }); diff --git a/packages/core/src/renderer/components/locale-date/locale-date.tsx b/packages/core/src/renderer/components/locale-date/locale-date.tsx index 28b5f6b05a..4ec558e617 100644 --- a/packages/core/src/renderer/components/locale-date/locale-date.tsx +++ b/packages/core/src/renderer/components/locale-date/locale-date.tsx @@ -6,27 +6,27 @@ import React from "react"; import { observer } from "mobx-react"; import moment from "moment-timezone"; -import type { UserStore } from "../../../common/user-store"; import { withInjectables } from "@ogre-tools/injectable-react"; -import userStoreInjectable from "../../../common/user-store/user-store.injectable"; +import type { UserPreferencesState } from "../../../features/user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../features/user-preferences/common/state.injectable"; export interface LocaleDateProps { date: string; } interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; } -const NonInjectedLocaleDate = observer(({ date, userStore }: LocaleDateProps & Dependencies) => ( +const NonInjectedLocaleDate = observer(({ date, state }: LocaleDateProps & Dependencies) => ( <> - {`${moment.tz(date, userStore.localeTimezone).format()}`} + {`${moment.tz(date, state.localeTimezone).format()}`} )); export const LocaleDate = withInjectables(NonInjectedLocaleDate, { getProps: (di, props) => ({ ...props, - userStore: di.inject(userStoreInjectable), + state: di.inject(userPreferencesStateInjectable), }), }); diff --git a/packages/core/src/renderer/components/monaco-editor/monaco-editor.tsx b/packages/core/src/renderer/components/monaco-editor/monaco-editor.tsx index d41ab4157b..7e36639c41 100644 --- a/packages/core/src/renderer/components/monaco-editor/monaco-editor.tsx +++ b/packages/core/src/renderer/components/monaco-editor/monaco-editor.tsx @@ -13,15 +13,15 @@ import type { MonacoTheme } from "./monaco-themes"; import { type MonacoValidator, monacoValidators } from "./monaco-validators"; import { debounce, merge } from "lodash"; import { cssNames, disposer } from "@k8slens/utilities"; -import type { UserStore } from "../../../common/user-store"; import type { LensTheme } from "../../themes/lens-theme"; import { withInjectables } from "@ogre-tools/injectable-react"; -import userStoreInjectable from "../../../common/user-store/user-store.injectable"; import activeThemeInjectable from "../../themes/active.injectable"; import getEditorHeightFromLinesCountInjectable from "./get-editor-height-from-lines-number.injectable"; import type { Logger } from "../../../common/logger"; import loggerInjectable from "../../../common/logger.injectable"; import autoBindReact from "auto-bind/react"; +import type { UserPreferencesState } from "../../../features/user-preferences/common/state.injectable"; +import userPreferencesStateInjectable from "../../../features/user-preferences/common/state.injectable"; export type MonacoEditorId = string; @@ -45,7 +45,7 @@ export interface MonacoEditorProps { } interface Dependencies { - userStore: UserStore; + state: UserPreferencesState; activeTheme: IComputedValue; getEditorHeightFromLinesCount: (linesCount: number) => number; logger: Logger; @@ -116,7 +116,7 @@ class NonInjectedMonacoEditor extends React.Component( - React.forwardRef((props, ref) => ), - { - getProps: (di, props) => ({ - ...props, - userStore: di.inject(userStoreInjectable), - activeTheme: di.inject(activeThemeInjectable), - getEditorHeightFromLinesCount: di.inject(getEditorHeightFromLinesCountInjectable), - logger: di.inject(loggerInjectable), - }), - }, -); +const ForwardedRefMonacoEditor = React.forwardRef(( + (props, ref) => +)); + +export const MonacoEditor = withInjectables(ForwardedRefMonacoEditor, { + getProps: (di, props) => ({ + ...props, + state: di.inject(userPreferencesStateInjectable), + activeTheme: di.inject(activeThemeInjectable), + getEditorHeightFromLinesCount: di.inject(getEditorHeightFromLinesCountInjectable), + logger: di.inject(loggerInjectable), + }), +}); diff --git a/packages/core/src/renderer/components/test-utils/get-extension-fake.ts b/packages/core/src/renderer/components/test-utils/get-extension-fake.ts index 675b7e93e2..9da4adb019 100644 --- a/packages/core/src/renderer/components/test-utils/get-extension-fake.ts +++ b/packages/core/src/renderer/components/test-utils/get-extension-fake.ts @@ -3,7 +3,6 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import type { Writable } from "type-fest"; -import fileSystemProvisionerStoreInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; import { lensExtensionDependencies } from "../../../extensions/lens-extension"; import { LensMainExtension } from "../../../extensions/lens-main-extension"; import navigateForExtensionInjectable from "../../../main/start-main-application/lens-window/navigate-for-extension.injectable"; @@ -16,6 +15,7 @@ import catalogEntityRegistryForMainInjectable from "../../../main/catalog/entity import catalogEntityRegistryForRendererInjectable from "../../api/catalog/entity/registry.injectable"; import type { DiContainer } from "@ogre-tools/injectable"; import loggerInjectable from "../../../common/logger.injectable"; +import ensureHashedDirectoryForExtensionInjectable from "../../../extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable"; export class TestExtensionMain extends LensMainExtension {} export class TestExtensionRenderer extends LensRendererExtension {} @@ -47,7 +47,7 @@ export const getMainExtensionFakeWith = (di: DiContainer) => ({ id, name, mainOp Object.assign(instance, mainOptions); (instance as Writable)[lensExtensionDependencies] = { - fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), entityRegistry: di.inject(catalogEntityRegistryForMainInjectable), navigate: di.inject(navigateForExtensionInjectable), logger: di.inject(loggerInjectable), @@ -78,7 +78,7 @@ export const getRendererExtensionFakeWith = (di: DiContainer) => ({ id, name, re (instance as Writable)[lensExtensionDependencies] = { categoryRegistry: di.inject(catalogCategoryRegistryInjectable), entityRegistry: di.inject(catalogEntityRegistryForRendererInjectable), - fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), getExtensionPageParameters: di.inject(getExtensionPageParametersInjectable), navigateToRoute: di.inject(navigateToRouteInjectable), routes: di.inject(routesInjectable), diff --git a/packages/core/src/renderer/extension-loader/create-extension-instance.injectable.ts b/packages/core/src/renderer/extension-loader/create-extension-instance.injectable.ts index 0f92440338..ebc424641a 100644 --- a/packages/core/src/renderer/extension-loader/create-extension-instance.injectable.ts +++ b/packages/core/src/renderer/extension-loader/create-extension-instance.injectable.ts @@ -7,7 +7,7 @@ import type { Writable } from "type-fest"; import catalogCategoryRegistryInjectable from "../../common/catalog/category-registry.injectable"; import loggerInjectable from "../../common/logger.injectable"; import { createExtensionInstanceInjectionToken } from "../../extensions/extension-loader/create-extension-instance.token"; -import fileSystemProvisionerStoreInjectable from "../../extensions/extension-loader/file-system-provisioner-store/file-system-provisioner-store.injectable"; +import ensureHashedDirectoryForExtensionInjectable from "../../extensions/extension-loader/file-system-provisioner-store/ensure-hashed-directory-for-extension.injectable"; import { lensExtensionDependencies } from "../../extensions/lens-extension"; import type { LensRendererExtensionDependencies } from "../../extensions/lens-extension-set-dependencies"; import type { LensRendererExtension } from "../../extensions/lens-renderer-extension"; @@ -22,7 +22,7 @@ const createExtensionInstanceInjectable = getInjectable({ const deps: LensRendererExtensionDependencies = { categoryRegistry: di.inject(catalogCategoryRegistryInjectable), entityRegistry: di.inject(catalogEntityRegistryInjectable), - fileSystemProvisionerStore: di.inject(fileSystemProvisionerStoreInjectable), + ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable), getExtensionPageParameters: di.inject(getExtensionPageParametersInjectable), navigateToRoute: di.inject(navigateToRouteInjectable), routes: di.inject(routesInjectable), diff --git a/packages/core/src/renderer/initializers/add-sync-entries.injectable.tsx b/packages/core/src/renderer/initializers/add-sync-entries.injectable.tsx index 2f9f3ec74e..5d42d0e496 100644 --- a/packages/core/src/renderer/initializers/add-sync-entries.injectable.tsx +++ b/packages/core/src/renderer/initializers/add-sync-entries.injectable.tsx @@ -3,24 +3,24 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; import React from "react"; import navigateToKubernetesPreferencesInjectable from "../../features/preferences/common/navigate-to-kubernetes-preferences.injectable"; import { runInAction } from "mobx"; import showSuccessNotificationInjectable from "../components/notifications/show-success-notification.injectable"; +import userPreferencesStateInjectable from "../../features/user-preferences/common/state.injectable"; const addSyncEntriesInjectable = getInjectable({ id: "add-sync-entries", instantiate: (di) => { - const userStore = di.inject(userStoreInjectable); + const state = di.inject(userPreferencesStateInjectable); const navigateToKubernetesPreferences = di.inject(navigateToKubernetesPreferencesInjectable); const showSuccessNotification = di.inject(showSuccessNotificationInjectable); return async (paths: string[]) => { runInAction(() => { for (const path of paths) { - userStore.syncKubeconfigEntries.set(path, {}); + state.syncKubeconfigEntries.set(path, {}); } }); diff --git a/packages/core/src/renderer/initializers/workload-events.tsx b/packages/core/src/renderer/initializers/workload-events.tsx index 73251382c8..945746cb2f 100644 --- a/packages/core/src/renderer/initializers/workload-events.tsx +++ b/packages/core/src/renderer/initializers/workload-events.tsx @@ -7,7 +7,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import { observer } from "mobx-react"; import React from "react"; -import { shouldShowResourceInjectionToken } from "../../common/cluster-store/allowed-resources-injection-token"; +import { shouldShowResourceInjectionToken } from "../../features/cluster/showing-kube-resources/common/allowed-resources-injection-token"; import { Events } from "../components/+events/events"; export interface WorkloadEventsProps {} diff --git a/packages/core/src/renderer/ipc/list-namespaces-forbidden-handler.injectable.tsx b/packages/core/src/renderer/ipc/list-namespaces-forbidden-handler.injectable.tsx index 4acf8182b1..f8c850e7f1 100644 --- a/packages/core/src/renderer/ipc/list-namespaces-forbidden-handler.injectable.tsx +++ b/packages/core/src/renderer/ipc/list-namespaces-forbidden-handler.injectable.tsx @@ -10,8 +10,8 @@ import type { IpcRendererEvent } from "electron"; import React from "react"; import notificationsStoreInjectable from "../components/notifications/notifications-store.injectable"; import { getMillisecondsFromUnixEpoch } from "../../common/utils/date/get-current-date-time"; -import getClusterByIdInjectable from "../../common/cluster-store/get-by-id.injectable"; import showSuccessNotificationInjectable from "../components/notifications/show-success-notification.injectable"; +import getClusterByIdInjectable from "../../features/cluster/storage/common/get-by-id.injectable"; const intervalBetweenNotifications = 1000 * 60; // 60s diff --git a/packages/core/src/renderer/ipc/register-ipc-listeners.injectable.ts b/packages/core/src/renderer/ipc/register-ipc-listeners.injectable.ts index 0c2d407d59..0a80abb9e6 100644 --- a/packages/core/src/renderer/ipc/register-ipc-listeners.injectable.ts +++ b/packages/core/src/renderer/ipc/register-ipc-listeners.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import { defaultHotbarCells } from "../../common/hotbars/types"; +import { defaultHotbarCells } from "../../features/hotbar/storage/common/types"; import { clusterListNamespaceForbiddenChannel } from "../../common/ipc/cluster"; import { hotbarTooManyItemsChannel } from "../../common/ipc/hotbar"; import showErrorNotificationInjectable from "../components/notifications/show-error-notification.injectable"; diff --git a/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts b/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts index 1d378df375..c4497dfaeb 100644 --- a/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts +++ b/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.injectable.ts @@ -12,11 +12,9 @@ import navigateToExtensionsInjectable from "../../../common/front-end-routing/ro import navigateToEntitySettingsInjectable from "../../../common/front-end-routing/routes/entity-settings/navigate-to-entity-settings.injectable"; import navigateToClusterViewInjectable from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable"; import catalogEntityRegistryInjectable from "../../api/catalog/entity/registry.injectable"; - -// TODO: Importing from features is not OK. Make protocol-router to comply with Open Closed Principle to allow moving implementation under a feature import navigateToPreferencesInjectable from "../../../features/preferences/common/navigate-to-preferences.injectable"; -import getClusterByIdInjectable from "../../../common/cluster-store/get-by-id.injectable"; import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable"; +import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; const bindProtocolAddRouteHandlersInjectable = getInjectable({ id: "bind-protocol-add-route-handlers", diff --git a/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx b/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx index 4bcf207340..bf80d6c4d4 100644 --- a/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx +++ b/packages/core/src/renderer/protocol-handler/bind-protocol-add-route-handlers/bind-protocol-add-route-handlers.tsx @@ -17,7 +17,7 @@ import type { NavigateToEntitySettings } from "../../../common/front-end-routing import type { NavigateToClusterView } from "../../../common/front-end-routing/routes/cluster-view/navigate-to-cluster-view.injectable"; import assert from "assert"; import type { AttemptInstallByInfo } from "../../components/+extensions/attempt-install-by-info.injectable"; -import type { GetClusterById } from "../../../common/cluster-store/get-by-id.injectable"; +import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable"; interface Dependencies { attemptInstallByInfo: AttemptInstallByInfo; diff --git a/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts b/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts index 35938b32fd..aa9525b31a 100644 --- a/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts +++ b/packages/core/src/renderer/protocol-handler/lens-protocol-router-renderer/lens-protocol-router-renderer.injectable.ts @@ -5,17 +5,17 @@ import { getInjectable } from "@ogre-tools/injectable"; import extensionLoaderInjectable from "../../../extensions/extension-loader/extension-loader.injectable"; import { LensProtocolRouterRenderer } from "./lens-protocol-router-renderer"; -import extensionsStoreInjectable from "../../../extensions/extensions-store/extensions-store.injectable"; import loggerInjectable from "../../../common/logger.injectable"; import showErrorNotificationInjectable from "../../components/notifications/show-error-notification.injectable"; import showShortInfoNotificationInjectable from "../../components/notifications/show-short-info.injectable"; +import isExtensionEnabledInjectable from "../../../features/extensions/enabled/common/is-enabled.injectable"; const lensProtocolRouterRendererInjectable = getInjectable({ id: "lens-protocol-router-renderer", instantiate: (di) => new LensProtocolRouterRenderer({ extensionLoader: di.inject(extensionLoaderInjectable), - extensionsStore: di.inject(extensionsStoreInjectable), + isExtensionEnabled: di.inject(isExtensionEnabledInjectable), logger: di.inject(loggerInjectable), showErrorNotification: di.inject(showErrorNotificationInjectable), showShortInfoNotification: di.inject(showShortInfoNotificationInjectable), diff --git a/packages/core/src/renderer/stores/init-user-store.injectable.ts b/packages/core/src/renderer/stores/init-user-store.injectable.ts deleted file mode 100644 index af192d9b2e..0000000000 --- a/packages/core/src/renderer/stores/init-user-store.injectable.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ -import { getInjectable } from "@ogre-tools/injectable"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; -import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; -import initDefaultUpdateChannelInjectable from "../vars/default-update-channel/init.injectable"; - -const initUserStoreInjectable = getInjectable({ - id: "init-user-store", - instantiate: (di) => ({ - run: () => { - const userStore = di.inject(userStoreInjectable); - - return userStore.load(); - }, - runAfter: initDefaultUpdateChannelInjectable, - }), - injectionToken: beforeFrameStartsSecondInjectionToken, -}); - -export default initUserStoreInjectable; diff --git a/packages/core/src/renderer/themes/active.injectable.ts b/packages/core/src/renderer/themes/active.injectable.ts index e22aee237b..324bbf8117 100644 --- a/packages/core/src/renderer/themes/active.injectable.ts +++ b/packages/core/src/renderer/themes/active.injectable.ts @@ -5,7 +5,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import assert from "assert"; import { computed } from "mobx"; -import lensColorThemePreferenceInjectable from "../../common/user-store/lens-color-theme.injectable"; +import lensColorThemePreferenceInjectable from "../../features/user-preferences/common/lens-color-theme.injectable"; import { lensThemeDeclarationInjectionToken } from "./declaration"; import defaultLensThemeInjectable from "./default-theme.injectable"; import systemThemeConfigurationInjectable from "./system-theme.injectable"; diff --git a/packages/core/src/renderer/themes/apply-lens-theme.injectable.ts b/packages/core/src/renderer/themes/apply-lens-theme.injectable.ts index 94afd5c531..977c5eb77c 100644 --- a/packages/core/src/renderer/themes/apply-lens-theme.injectable.ts +++ b/packages/core/src/renderer/themes/apply-lens-theme.injectable.ts @@ -4,9 +4,9 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import loggerInjectable from "../../common/logger.injectable"; -import userStoreInjectable from "../../common/user-store/user-store.injectable"; import { object } from "@k8slens/utilities"; import type { LensTheme } from "./lens-theme"; +import resetThemeInjectable from "../../features/user-preferences/common/reset-theme.injectable"; export type ApplyLensTheme = (theme: LensTheme) => void; @@ -14,7 +14,7 @@ const applyLensThemeInjectable = getInjectable({ id: "apply-lens-theme", instantiate: (di): ApplyLensTheme => { const logger = di.inject(loggerInjectable); - const userStore = di.inject(userStoreInjectable); + const resetTheme = di.inject(resetThemeInjectable); return (theme) => { try { @@ -28,7 +28,7 @@ const applyLensThemeInjectable = getInjectable({ document.body.classList.toggle("theme-light", theme.type === "light"); } catch (error) { logger.error("[THEME]: Failed to apply active theme", error); - userStore.resetTheme(); + resetTheme(); } }; }, diff --git a/packages/core/src/renderer/themes/setup-apply-active-theme.injectable.ts b/packages/core/src/renderer/themes/setup-apply-active-theme.injectable.ts index c71dd5baa7..f658093f8e 100644 --- a/packages/core/src/renderer/themes/setup-apply-active-theme.injectable.ts +++ b/packages/core/src/renderer/themes/setup-apply-active-theme.injectable.ts @@ -6,7 +6,7 @@ import { getInjectable } from "@ogre-tools/injectable"; import { reaction } from "mobx"; import initializeSystemThemeTypeInjectable from "../../features/theme/system-type/renderer/initialize.injectable"; import { beforeFrameStartsSecondInjectionToken } from "../before-frame-starts/tokens"; -import initUserStoreInjectable from "../stores/init-user-store.injectable"; +import initUserStoreInjectable from "../../features/user-preferences/renderer/load-storage.injectable"; import activeThemeInjectable from "./active.injectable"; import applyLensThemeInjectable from "./apply-lens-theme.injectable"; diff --git a/packages/core/src/renderer/themes/terminal-colors.injectable.ts b/packages/core/src/renderer/themes/terminal-colors.injectable.ts index c7010b08b6..7b83d7b16c 100644 --- a/packages/core/src/renderer/themes/terminal-colors.injectable.ts +++ b/packages/core/src/renderer/themes/terminal-colors.injectable.ts @@ -4,7 +4,7 @@ */ import { getInjectable } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import terminalThemePreferenceInjectable from "../../common/user-store/terminal-theme.injectable"; +import terminalThemePreferenceInjectable from "../../features/user-preferences/common/terminal-theme.injectable"; import activeThemeInjectable from "./active.injectable"; import lensThemesInjectable from "./themes.injectable"; diff --git a/packages/core/src/test-utils/override-fs-with-fakes.ts b/packages/core/src/test-utils/override-fs-with-fakes.ts index 87f8dd583a..5eaeb90ba7 100644 --- a/packages/core/src/test-utils/override-fs-with-fakes.ts +++ b/packages/core/src/test-utils/override-fs-with-fakes.ts @@ -63,6 +63,7 @@ export const getOverrideFsWithFakes = () => { createReadStream: root.createReadStream as any, stat: root.promises.stat as any, unlink: root.promises.unlink, + rename: root.promises.rename, })); }; }; diff --git a/packages/core/src/test-utils/use-fake-time.ts b/packages/core/src/test-utils/use-fake-time.ts index e455984861..11d5fe0918 100644 --- a/packages/core/src/test-utils/use-fake-time.ts +++ b/packages/core/src/test-utils/use-fake-time.ts @@ -19,7 +19,11 @@ export const advanceFakeTime = (milliseconds: number) => { export const testUsingFakeTime = (dateTime = "2015-10-21T07:28:00Z") => { usingFakeTime = true; - jest.useFakeTimers(); + jest.useFakeTimers({ + doNotFake: [ + "nextTick", + ], + }); jest.setSystemTime(new Date(dateTime)); }; diff --git a/packages/open-lens/package.json b/packages/open-lens/package.json index 577c1a2a6b..fb4fbc6616 100644 --- a/packages/open-lens/package.json +++ b/packages/open-lens/package.json @@ -257,7 +257,7 @@ "esbuild-loader": "^2.20.0", "fork-ts-checker-webpack-plugin": "^7.3.0", "html-webpack-plugin": "^5.5.0", - "jest": "^28.1.3", + "jest": "^29.5.0", "jest-environment-jsdom": "^28.1.3", "jsonfile": "^6.1.0", "mini-css-extract-plugin": "^2.7.1", diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/channel.no-coverage.ts b/packages/technical-features/messaging/agnostic/src/features/actual/channel.no-coverage.ts deleted file mode 100644 index 62a2ea1490..0000000000 --- a/packages/technical-features/messaging/agnostic/src/features/actual/channel.no-coverage.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface Channel { - id: string; - _messageTemplate?: MessageTemplate; - _returnTemplate?: ReturnTemplate; -} diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/index.ts b/packages/technical-features/messaging/agnostic/src/features/actual/index.ts index e8209f26f0..f85542c954 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/index.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/index.ts @@ -6,8 +6,6 @@ export { getMessageChannel } from "./message/get-message-channel"; export { requestFromChannelInjectionToken } from "./request/request-from-channel-injection-token"; -export type { Channel } from "./channel.no-coverage"; - export { sendMessageToChannelInjectionToken } from "./message/message-to-channel-injection-token"; export type { SendMessageToChannel } from "./message/message-to-channel-injection-token"; diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts index c28d6cbc5a..9040939f06 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/listening-of-channels/listening-of-channels.injectable.ts @@ -6,17 +6,24 @@ import { getStartableStoppable, StartableStoppable } from "@k8slens/startable-st import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { IComputedValue, reaction } from "mobx"; -import { messageChannelListenerInjectionToken } from "../message/message-channel-listener-injection-token"; -import { requestChannelListenerInjectionToken } from "../request/request-channel-listener-injection-token"; +import { + MessageChannel, + messageChannelListenerInjectionToken, +} from "../message/message-channel-listener-injection-token"; +import { + RequestChannel, + requestChannelListenerInjectionToken, +} from "../request/request-channel-listener-injection-token"; import { enlistRequestChannelListenerInjectionToken } from "../request/enlist-request-channel-listener-injection-token"; -import type { Channel } from "../channel.no-coverage"; export type ListeningOfChannels = StartableStoppable; export const listeningOfChannelsInjectionToken = getInjectionToken({ id: "listening-of-channels-injection-token", }); -const listening = }>( +const listening = < + T extends { id: string; channel: MessageChannel | RequestChannel }, +>( channelListeners: IComputedValue, enlistChannelListener: (listener: T) => () => void, getId: (listener: T) => string, diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts index 9ec6f8b93a..1bb114ca09 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/message/enlist-message-channel-listener-injection-token.ts @@ -1,3 +1,4 @@ +import type { Disposer } from "@k8slens/utilities"; import { getInjectionToken } from "@ogre-tools/injectable"; import type { @@ -5,9 +6,9 @@ import type { MessageChannelListener, } from "./message-channel-listener-injection-token"; -export type EnlistMessageChannelListener = ( - listener: MessageChannelListener>, -) => () => void; +export type EnlistMessageChannelListener = ( + listener: MessageChannelListener>, +) => Disposer; export const enlistMessageChannelListenerInjectionToken = getInjectionToken({ diff --git a/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts b/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts index cdb3ac97d5..7f2a04a78d 100644 --- a/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts +++ b/packages/technical-features/messaging/agnostic/src/features/actual/request/enlist-request-channel-listener-injection-token.ts @@ -1,3 +1,4 @@ +import type { Disposer } from "@k8slens/utilities/index"; import { getInjectionToken } from "@ogre-tools/injectable"; import type { @@ -5,9 +6,9 @@ import type { RequestChannelListener, } from "./request-channel-listener-injection-token"; -export type EnlistRequestChannelListener = ( - listener: RequestChannelListener>, -) => () => void; +export type EnlistRequestChannelListener = ( + listener: RequestChannelListener>, +) => Disposer; export const enlistRequestChannelListenerInjectionToken = getInjectionToken({ diff --git a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts index 5a33ed39d9..a8368c489b 100644 --- a/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/electron/main/src/channel-listeners/enlist-message-channel-listener.injectable.ts @@ -10,7 +10,7 @@ const enlistMessageChannelListenerInjectable = getInjectable({ const ipcMain = di.inject(ipcMainInjectable); return ({ channel, handler }) => { - const nativeOnCallback = (nativeEvent: IpcMainEvent, message: unknown) => { + const nativeOnCallback = (nativeEvent: IpcMainEvent, message: any) => { handler(message, { frameId: nativeEvent.frameId, processId: nativeEvent.processId }); }; diff --git a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts index 6948e51073..fb1840f33f 100644 --- a/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts +++ b/packages/technical-features/messaging/electron/renderer/src/listening-of-messages/enlist-message-channel-listener.injectable.ts @@ -10,7 +10,7 @@ const enlistMessageChannelListenerInjectable = getInjectable({ const ipcRenderer = di.inject(ipcRendererInjectable); return ({ channel, handler }) => { - const nativeCallback = (event: IpcRendererEvent, message: unknown) => { + const nativeCallback = (_: IpcRendererEvent, message: any) => { handler(message); }; diff --git a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts index ef8eed6461..8feb31dcd2 100644 --- a/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts +++ b/packages/technical-features/messaging/message-bridge-fake/src/get-message-bridge-fake/get-message-bridge-fake.ts @@ -1,5 +1,11 @@ import type { DiContainer } from "@ogre-tools/injectable"; -import type { Channel, MessageChannelHandler, RequestChannelHandler } from "@k8slens/messaging"; +import type { + MessageChannel, + MessageChannelHandler, + MessageChannelListener, + RequestChannel, + RequestChannelHandler, +} from "@k8slens/messaging"; import { enlistMessageChannelListenerInjectionToken, @@ -20,6 +26,8 @@ export type MessageBridgeFake = { setAsync: (value: boolean) => void; }; +type MessageHandlers = Set>>; + const overrideMessaging = ({ di, messageListenersByDi, @@ -28,13 +36,13 @@ const overrideMessaging = ({ }: { di: DiContainer; - messageListenersByDi: Map>>>; + messageListenersByDi: Map>; messagePropagationBuffer: Set<{ resolve: () => Promise }>; getAsyncModeStatus: () => boolean; }) => { - const messageHandlersByChannel = new Map>>(); + const messageHandlersByChannel = new Map(); messageListenersByDi.set(di, messageHandlersByChannel); @@ -64,30 +72,36 @@ const overrideMessaging = ({ }); }); - di.override(enlistMessageChannelListenerInjectionToken, () => (listener) => { - if (!messageHandlersByChannel.has(listener.channel.id)) { - messageHandlersByChannel.set(listener.channel.id, new Set()); - } + di.override( + enlistMessageChannelListenerInjectionToken, + () => + (listener: MessageChannelListener>) => { + if (!messageHandlersByChannel.has(listener.channel.id)) { + messageHandlersByChannel.set(listener.channel.id, new Set()); + } - const handlerSet = messageHandlersByChannel.get(listener.channel.id); + const handlerSet = messageHandlersByChannel.get(listener.channel.id); - handlerSet?.add(listener.handler); + handlerSet?.add(listener.handler); - return () => { - handlerSet?.delete(listener.handler); - }; - }); + return () => { + handlerSet?.delete(listener.handler); + }; + }, + ); }; +type RequestHandlers = Set>>; + const overrideRequesting = ({ di, requestListenersByDi, }: { di: DiContainer; - requestListenersByDi: Map>>>; + requestListenersByDi: Map>; }) => { - const requestHandlersByChannel = new Map>>(); + const requestHandlersByChannel = new Map(); requestListenersByDi.set(di, requestHandlersByChannel); @@ -141,15 +155,8 @@ const overrideRequesting = ({ }; export const getMessageBridgeFake = (): MessageBridgeFake => { - const messageListenersByDi = new Map< - DiContainer, - Map>> - >(); - - const requestListenersByDi = new Map< - DiContainer, - Map>> - >(); + const messageListenersByDi = new Map>(); + const requestListenersByDi = new Map>(); const messagePropagationBuffer = new Set void>>(); diff --git a/packages/utility-features/utilities/src/abort-controller.ts b/packages/utility-features/utilities/src/abort-controller.ts index 784b495a9e..d434a24c6f 100644 --- a/packages/utility-features/utilities/src/abort-controller.ts +++ b/packages/utility-features/utilities/src/abort-controller.ts @@ -22,3 +22,11 @@ export function setTimeoutFor(controller: AbortController, timeout: number): voi controller.signal.addEventListener("abort", () => clearTimeout(handle)); } + +export function chainSignal(target: AbortController, signal: AbortSignal) { + if (signal.aborted) { + target.abort(); + } else { + signal.addEventListener("abort", (event) => target.abort(event)); + } +} diff --git a/packages/utility-features/utilities/src/collection-functions.ts b/packages/utility-features/utilities/src/collection-functions.ts index e78e215e8b..0c2d5cf119 100644 --- a/packages/utility-features/utilities/src/collection-functions.ts +++ b/packages/utility-features/utilities/src/collection-functions.ts @@ -74,7 +74,11 @@ export function getOrInsertWith(map: Map | WeakMap(map: Map, key: K, asyncBuilder: () => Promise): Promise { if (!map.has(key)) { - map.set(key, await asyncBuilder()); + const newValue = await asyncBuilder(); + + runInAction(() => { + map.set(key, newValue); + }); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/utility-features/utilities/src/delay.ts b/packages/utility-features/utilities/src/delay.ts index d86395026b..83be44be71 100644 --- a/packages/utility-features/utilities/src/delay.ts +++ b/packages/utility-features/utilities/src/delay.ts @@ -10,11 +10,11 @@ * @param timeout The number of milliseconds before resolving * @param failFast An abort controller instance to cause the delay to short-circuit */ -export function delay(timeout = 1000, failFast?: AbortController): Promise { +export function delay(timeout = 1000, failFast?: AbortSignal): Promise { return new Promise(resolve => { const timeoutId = setTimeout(resolve, timeout); - failFast?.signal.addEventListener("abort", () => { + failFast?.addEventListener("abort", () => { clearTimeout(timeoutId); resolve(); }); diff --git a/packages/utility-features/utilities/src/iter.ts b/packages/utility-features/utilities/src/iter.ts index abbf4cc92d..b20f48a67a 100644 --- a/packages/utility-features/utilities/src/iter.ts +++ b/packages/utility-features/utilities/src/iter.ts @@ -10,10 +10,14 @@ interface Iterator extends Iterable { filterMap(fn: (val: T) => Falsy | U): Iterator; find(fn: (val: T) => unknown): T | undefined; collect(fn: (values: Iterable) => U): U; + toArray(): T[]; + toMap(): T extends [infer K, infer V] ? Map : never; + toSet(): Set; map(fn: (val: T) => U): Iterator; flatMap(fn: (val: T) => U[]): Iterator; concat(src2: IterableIterator): Iterator; join(sep?: string): string; + take(count: number): Iterator; } function chain(src: IterableIterator): Iterator { @@ -25,7 +29,11 @@ function chain(src: IterableIterator): Iterator { find: (fn) => find(src, fn), join: (sep) => join(src, sep), collect: (fn) => fn(src), + toArray: () => [...src], + toMap: () => new Map(src as IterableIterator<[any, any]>) as any, + toSet: () => new Set(src), concat: (src2) => chain(concat(src, src2)), + take: (count) => chain(take(src, count)), [Symbol.iterator]: () => src, }; }