diff --git a/src/Umbraco.Web.UI.Client/.eslintrc.json b/src/Umbraco.Web.UI.Client/.eslintrc.json index 4cd0e150db..5034e34797 100644 --- a/src/Umbraco.Web.UI.Client/.eslintrc.json +++ b/src/Umbraco.Web.UI.Client/.eslintrc.json @@ -33,7 +33,8 @@ "no-var": "error", "import/no-unresolved": "error", "import/order": "warn", - "local-rules/bad-type-import": "error" + "local-rules/bad-type-import": "error", + "local-rules/no-direct-api-import": "warn" }, "settings": { "import/parsers": { diff --git a/src/Umbraco.Web.UI.Client/.vscode/launch.json b/src/Umbraco.Web.UI.Client/.vscode/launch.json index 094ba1adaf..828c6a84f0 100644 --- a/src/Umbraco.Web.UI.Client/.vscode/launch.json +++ b/src/Umbraco.Web.UI.Client/.vscode/launch.json @@ -4,6 +4,12 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ + { + "command": "npx eslint", + "name": "Debug eslint", + "request": "launch", + "type": "node-terminal" + }, { "type": "chrome", "request": "launch", diff --git a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs index eeddac50c8..a4d6a82658 100644 --- a/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs +++ b/src/Umbraco.Web.UI.Client/eslint-local-rules.cjs @@ -4,8 +4,8 @@ * A eslint rule that ensures the use of the `import type` operator from the `src/core/models/index.ts` file. */ // eslint-disable-next-line no-undef -/** @type {import('eslint').Rule.RuleModule} */ module.exports = { + /** @type {import('eslint').Rule.RuleModule} */ 'bad-type-import': { meta: { type: 'problem', @@ -20,7 +20,7 @@ module.exports = { create: function (context) { return { ImportDeclaration: function (node) { - if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value.endsWith('/generated-schema') || node.source.value === 'router-slot/model')) { + if (node.source.parent.importKind !== 'type' && (node.source.value.endsWith('/models') || node.source.value === 'router-slot/model')) { const sourceCode = context.getSourceCode(); const nodeSource = sourceCode.getText(node); context.report({ @@ -32,5 +32,37 @@ module.exports = { }, }; } + }, + + /** @type {import('eslint').Rule.RuleModule} */ + 'no-direct-api-import': { + meta: { + type: 'suggestion', + docs: { + description: 'Ensures that any API resources from the `@umbraco-cms/backend-api` module are not used directly. Instead you should use the `tryExecuteAndNotify` function from the `@umbraco-cms/resources` module.', + category: 'Best Practices', + recommended: true + }, + fixable: 'code', + schema: [], + }, + create: function (context) { + return { + // If methods called on *Resource classes are not already wrapped with `await tryExecuteAndNotify()`, then we should suggest to wrap them. + CallExpression: function (node) { + if (node.callee.type === 'MemberExpression' && node.callee.object.type === 'Identifier' && node.callee.object.name.endsWith('Resource') && node.callee.property.type === 'Identifier' && node.callee.property.name !== 'constructor') { + const hasTryExecuteAndNotify = node.parent && node.parent.callee && (node.parent.callee.name === 'tryExecute' || node.parent.callee.name === 'tryExecuteAndNotify'); + if (!hasTryExecuteAndNotify) { + context.report({ + node, + message: 'Wrap this call with `tryExecuteAndNotify()`. Make sure to `await` the result.', + fix: fixer => [fixer.insertTextBefore(node, 'tryExecuteAndNotify(this, '), fixer.insertTextAfter(node, ')')], + }); + } + } + } + }; + + }, } }; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index fb45b1e96f..ba00451a4e 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -42,7 +42,7 @@ "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.47.0", + "@typescript-eslint/parser": "^5.48.0", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.0.7", "@web/test-runner": "^0.15.0", @@ -4081,24 +4081,6 @@ "node": ">=8" } }, - "node_modules/@storybook/builder-webpack4/node_modules/schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - }, - "engines": { - "node": ">= 8.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/@storybook/builder-webpack4/node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -6425,53 +6407,6 @@ } } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6488,14 +6423,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", "debug": "^4.3.4" }, "engines": { @@ -6515,13 +6450,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -6558,7 +6493,7 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "node_modules/@typescript-eslint/types": { "version": "5.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", @@ -6571,7 +6506,7 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "node_modules/@typescript-eslint/typescript-estree": { "version": "5.48.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", @@ -6598,78 +6533,6 @@ } } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6711,80 +6574,6 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/utils/node_modules/semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -6801,12 +6590,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.47.0", + "@typescript-eslint/types": "5.48.0", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -11384,9 +11173,9 @@ } }, "node_modules/css-loader/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -15519,9 +15308,9 @@ } }, "node_modules/html-webpack-plugin/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -17101,9 +16890,9 @@ "dev": true }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "bin": { "json5": "lib/cli.js" @@ -25756,9 +25545,9 @@ } }, "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -28065,9 +27854,9 @@ "dev": true }, "node_modules/webpack/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "dependencies": { "minimist": "^1.2.0" @@ -31457,17 +31246,6 @@ "p-limit": "^2.2.0" } }, - "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" - } - }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -33295,32 +33073,6 @@ "tsutils": "^3.21.0" }, "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -33333,25 +33085,25 @@ } }, "@typescript-eslint/parser": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.47.0.tgz", - "integrity": "sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.48.0.tgz", + "integrity": "sha512-1mxNA8qfgxX8kBvRDIHEzrRGrKHQfQlbW6iHyfHYS0Q4X1af+S6mkLNtgCOsGVl8+/LUPrqdHMssAemkrQ01qg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.47.0", - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/typescript-estree": "5.47.0", + "@typescript-eslint/scope-manager": "5.48.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/typescript-estree": "5.48.0", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.47.0.tgz", - "integrity": "sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", + "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0" + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0" } }, "@typescript-eslint/type-utils": { @@ -33364,64 +33116,22 @@ "@typescript-eslint/utils": "5.48.0", "debug": "^4.3.4", "tsutils": "^3.21.0" - }, - "dependencies": { - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/types": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.47.0.tgz", - "integrity": "sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", + "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.47.0.tgz", - "integrity": "sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", + "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", - "@typescript-eslint/visitor-keys": "5.47.0", + "@typescript-eslint/types": "5.48.0", + "@typescript-eslint/visitor-keys": "5.48.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -33456,47 +33166,6 @@ "semver": "^7.3.7" }, "dependencies": { - "@typescript-eslint/scope-manager": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.48.0.tgz", - "integrity": "sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0" - } - }, - "@typescript-eslint/types": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.48.0.tgz", - "integrity": "sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.0.tgz", - "integrity": "sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "@typescript-eslint/visitor-keys": "5.48.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", - "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.48.0", - "eslint-visitor-keys": "^3.3.0" - } - }, "semver": { "version": "7.3.8", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", @@ -33509,12 +33178,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "5.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.47.0.tgz", - "integrity": "sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==", + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.48.0.tgz", + "integrity": "sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==", "dev": true, "requires": { - "@typescript-eslint/types": "5.47.0", + "@typescript-eslint/types": "5.48.0", "eslint-visitor-keys": "^3.3.0" } }, @@ -37299,9 +36968,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -40431,9 +40100,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -41581,9 +41250,9 @@ "dev": true }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "jsonfile": { @@ -48223,9 +47892,9 @@ }, "dependencies": { "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -49797,9 +49466,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 035c66d012..2ca695f048 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -87,7 +87,7 @@ "@types/mocha": "^10.0.0", "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.48.0", - "@typescript-eslint/parser": "^5.47.0", + "@typescript-eslint/parser": "^5.48.0", "@web/dev-server-esbuild": "^0.3.3", "@web/dev-server-import-maps": "^0.0.7", "@web/test-runner": "^0.15.0", diff --git a/src/Umbraco.Web.UI.Client/src/app.ts b/src/Umbraco.Web.UI.Client/src/app.ts index 1980d5f434..deac04fcb6 100644 --- a/src/Umbraco.Web.UI.Client/src/app.ts +++ b/src/Umbraco.Web.UI.Client/src/app.ts @@ -1,28 +1,29 @@ import './core/css/custom-properties.css'; + +// TODO: remove these imports when they are part of UUI +import '@umbraco-ui/uui-color-swatch'; +import '@umbraco-ui/uui-color-swatches'; import '@umbraco-ui/uui-modal'; import '@umbraco-ui/uui-modal-container'; import '@umbraco-ui/uui-modal-dialog'; import '@umbraco-ui/uui-modal-sidebar'; -import '@umbraco-ui/uui-color-swatch'; -import '@umbraco-ui/uui-color-swatches'; -import 'router-slot'; import 'element-internals-polyfill'; +import 'router-slot'; +import './auth'; -// TODO: remove these imports when they are part of UUI import type { Guard, IRoute } from 'router-slot/model'; import { UUIIconRegistryEssential } from '@umbraco-ui/uui'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; -import { UmbIconStore } from '@umbraco-cms/stores/icon/icon.store'; +import { UmbLitElement } from './core/element/lit-element.element'; +import { tryExecuteAndNotify } from './core/resources/tryExecuteAndNotify.method'; import { OpenAPI, RuntimeLevel, ServerResource } from '@umbraco-cms/backend-api'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; - -import './auth'; +import { UmbIconStore } from '@umbraco-cms/stores/icon/icon.store'; @customElement('umb-app') -export class UmbApp extends UmbContextProviderMixin(LitElement) { +export class UmbApp extends UmbLitElement { static styles = css` :host { overflow: hidden; @@ -95,14 +96,8 @@ export class UmbApp extends UmbContextProviderMixin(LitElement) { } private async _setInitStatus() { - try { - const serverStatus = await ServerResource.getServerStatus(); - if (serverStatus.serverStatus) { - this._runtimeLevel = serverStatus.serverStatus; - } - } catch (error) { - console.log(error); - } + const { data } = await tryExecuteAndNotify(this, ServerResource.getServerStatus()); + this._runtimeLevel = data?.serverStatus ?? RuntimeLevel.UNKNOWN; } private _redirect() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts index 166c1749c8..9a48b508cb 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-indexers.ts @@ -1,20 +1,19 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { UUIButtonState } from '@umbraco-ui/uui-button'; import { UmbModalService } from '../../../../../core/modal'; -import { UmbNotificationService } from '../../../../../core/notification'; -import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import './section-view-examine-searchers'; -import { ApiError, Index, IndexerResource, ProblemDetails } from '@umbraco-cms/backend-api'; +import { Index, IndexerResource } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; @customElement('umb-dashboard-examine-index') -export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(LitElement) { +export class UmbDashboardExamineIndexElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -93,30 +92,24 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit @state() private _loading = true; - private _notificationService?: UmbNotificationService; private _modalService?: UmbModalService; constructor() { super(); - this.consumeAllContexts(['umbNotificationService', 'umbModalService'], (instances) => { - this._notificationService = instances['umbNotificationService']; + this.consumeAllContexts(['umbModalService'], (instances) => { this._modalService = instances['umbModalService']; }); } private async _getIndexData() { - try { - this._indexData = await IndexerResource.getIndexerByIndexName({ indexName: this.indexName }); - if (!this._indexData?.isHealthy) { - this._buttonState = 'waiting'; - } - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch index' }; - this._notificationService?.peek('danger', { data }); - } + const { data } = await tryExecuteAndNotify( + this, + IndexerResource.getIndexerByIndexName({ indexName: this.indexName }) + ); + this._indexData = data; + if (!this._indexData?.isHealthy) { + this._buttonState = 'waiting'; } this._loading = false; } @@ -144,18 +137,17 @@ export class UmbDashboardExamineIndexElement extends UmbContextConsumerMixin(Lit } private async _rebuild() { this._buttonState = 'waiting'; - try { - await IndexerResource.postIndexerByIndexNameRebuild({ indexName: this.indexName }); - this._buttonState = 'success'; - await this._getIndexData(); - } catch (e) { + const { error } = await tryExecuteAndNotify( + this, + IndexerResource.postIndexerByIndexNameRebuild({ indexName: this.indexName }) + ); + if (error) { this._buttonState = 'failed'; - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.message ?? 'Rebuild error' }; - this._notificationService?.peek('danger', { data }); - } + return; } + + this._buttonState = 'success'; + await this._getIndexData(); } render() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-overview.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-overview.ts index dd17f71c5d..8b204624a5 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-overview.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-overview.ts @@ -1,15 +1,13 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { UmbNotificationService } from '../../../../../core/notification'; -import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default'; - -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { ApiError, ProblemDetails, Searcher, Index, IndexerResource, SearcherResource } from '@umbraco-cms/backend-api'; +import { Index, IndexerResource, Searcher, SearcherResource } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; @customElement('umb-dashboard-examine-overview') -export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin(LitElement) { +export class UmbDashboardExamineOverviewElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -65,16 +63,6 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin( @state() private _loadingSearchers = false; - private _notificationService?: UmbNotificationService; - - constructor() { - super(); - - this.consumeAllContexts(['umbNotificationService'], (instances) => { - this._notificationService = instances['umbNotificationService']; - }); - } - connectedCallback() { super.connectedCallback(); this._getIndexers(); @@ -83,31 +71,15 @@ export class UmbDashboardExamineOverviewElement extends UmbContextConsumerMixin( private async _getIndexers() { this._loadingIndexers = true; - try { - const indexers = await IndexerResource.getIndexer({ take: 9999, skip: 0 }); - this._indexers = indexers.items; - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch indexers' }; - this._notificationService?.peek('danger', { data }); - } - } + const { data } = await tryExecuteAndNotify(this, IndexerResource.getIndexer({ take: 9999, skip: 0 })); + this._indexers = data?.items ?? []; this._loadingIndexers = false; } private async _getSearchers() { this._loadingSearchers = true; - try { - const searchers = await SearcherResource.getSearcher({ take: 9999, skip: 0 }); - this._searchers = searchers.items; - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch searchers' }; - this._notificationService?.peek('danger', { data }); - } - } + const { data } = await tryExecuteAndNotify(this, SearcherResource.getSearcher({ take: 9999, skip: 0 })); + this._searchers = data?.items ?? []; this._loadingSearchers = false; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts index a3e1e0c2fc..71a7a66fe3 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/examine-management/views/section-view-examine-searchers.ts @@ -1,14 +1,12 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement, nothing } from 'lit'; +import { css, html, nothing } from 'lit'; import { customElement, state, query, property } from 'lit/decorators.js'; import { UmbModalService } from '../../../../../core/modal'; -import { UmbNotificationService } from '../../../../../core/notification'; -import { UmbNotificationDefaultData } from '../../../../../core/notification/layouts/default'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; - -import { ApiError, ProblemDetails, SearchResult, SearcherResource, Field } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { SearchResult, SearcherResource, Field } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; import './modal-views/fields-viewer.element'; import './modal-views/fields-settings.element'; @@ -19,7 +17,7 @@ interface ExposedSearchResultField { } @customElement('umb-dashboard-examine-searcher') -export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin(LitElement) { +export class UmbDashboardExamineSearcherElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -103,7 +101,6 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin( `, ]; - private _notificationService?: UmbNotificationService; private _modalService?: UmbModalService; @property() @@ -123,15 +120,13 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin( constructor() { super(); - this.consumeAllContexts(['umbNotificationService', 'umbModalService'], (instances) => { - this._notificationService = instances['umbNotificationService']; + this.consumeAllContexts(['umbModalService'], (instances) => { this._modalService = instances['umbModalService']; }); } private _onNameClick() { - const data: UmbNotificationDefaultData = { message: 'TODO: Open workspace for this' }; // TODO - this._notificationService?.peek('warning', { data }); + alert('TODO: Open workspace for ' + this.searcherName); } private _onKeyPress(e: KeyboardEvent) { @@ -141,22 +136,19 @@ export class UmbDashboardExamineSearcherElement extends UmbContextConsumerMixin( private async _onSearch() { if (!this._searchInput.value.length) return; this._searchLoading = true; - try { - const res = await SearcherResource.getSearcherBySearcherNameQuery({ + + const { data } = await tryExecuteAndNotify( + this, + SearcherResource.getSearcherBySearcherNameQuery({ searcherName: this.searcherName, term: this._searchInput.value, take: 100, skip: 0, - }); - this._searchResults = res.items; - this._updateFieldFilter(); - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.message ?? 'Could not fetch search results' }; - this._notificationService?.peek('danger', { data }); - } - } + }) + ); + + this._searchResults = data?.items ?? []; + this._updateFieldFilter(); this._searchLoading = false; } diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/models-builder/dashboard-models-builder.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/models-builder/dashboard-models-builder.element.ts index 621c9a4ecd..8f550d7bbd 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/models-builder/dashboard-models-builder.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/models-builder/dashboard-models-builder.element.ts @@ -3,10 +3,9 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html, nothing } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { UmbNotificationService } from '../../../../core/notification'; -import { UmbNotificationDefaultData } from '../../../../core/notification/layouts/default'; -import { ApiError, ModelsBuilder, ModelsBuilderResource, ModelsMode, ProblemDetails } from '@umbraco-cms/backend-api'; -import { UmbLitElement } from 'src/core/element/lit-element.element'; +import { ModelsBuilder, ModelsBuilderResource, ModelsMode } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; @customElement('umb-dashboard-models-builder') export class UmbDashboardModelsBuilderElement extends UmbLitElement { @@ -41,8 +40,6 @@ export class UmbDashboardModelsBuilderElement extends UmbLitElement { `, ]; - private _notificationService?: UmbNotificationService; - @state() private _modelsBuilder?: ModelsBuilder; @@ -55,27 +52,15 @@ export class UmbDashboardModelsBuilderElement extends UmbLitElement { constructor() { super(); this._getDashboardData(); - - this.consumeContext('umbNotificationService', (instance) => { - this._notificationService = instance; - }); } private async _getDashboardData() { - try { - const modelsBuilder = await ModelsBuilderResource.getModelsBuilderDashboard(); - this._modelsBuilder = modelsBuilder; + const { data } = await tryExecuteAndNotify(this, ModelsBuilderResource.getModelsBuilderDashboard()); + if (data) { + this._modelsBuilder = data; return true; - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { - message: error.message ?? 'Something went wrong', - }; - this._notificationService?.peek('danger', { data }); - } - return false; } + return false; } private async _onGenerateModels() { @@ -85,20 +70,13 @@ export class UmbDashboardModelsBuilderElement extends UmbLitElement { } private async _postGenerateModels() { - try { - await ModelsBuilderResource.postModelsBuilderBuild(); - this._getDashboardData(); - return true; - } catch (e) { - if (e instanceof ApiError) { - const error = e as ProblemDetails; - const data: UmbNotificationDefaultData = { - message: error.message ?? 'Model generation failed', - }; - this._notificationService?.peek('danger', { data }); - } + const { error } = await tryExecuteAndNotify(this, ModelsBuilderResource.postModelsBuilderBuild()); + if (error) { return false; } + + this._getDashboardData(); + return true; } private async _onDashboardReload() { diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/performance-profiling/dashboard-performance-profiling.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/performance-profiling/dashboard-performance-profiling.element.ts index 1590f0cedb..c499d4deb8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/performance-profiling/dashboard-performance-profiling.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/performance-profiling/dashboard-performance-profiling.element.ts @@ -1,7 +1,7 @@ import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { ProfilingResource, ProfilingStatus } from '@umbraco-cms/backend-api'; +import { ProfilingResource } from '@umbraco-cms/backend-api'; import { tryExecuteAndNotify } from 'src/core/resources/tryExecuteAndNotify.method'; import { UmbLitElement } from 'src/core/element/lit-element.element'; diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts index b25e3452ab..5995c9aea8 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/published-status/dashboard-published-status.element.ts @@ -1,17 +1,16 @@ import { UUIButtonState } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; +import { css, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbModalService } from '../../../../core/modal'; -import { UmbNotificationService } from '../../../../core/notification'; -import { UmbNotificationDefaultData } from '../../../../core/notification/layouts/default'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; -import { ApiError, ProblemDetails, PublishedCacheResource } from '@umbraco-cms/backend-api'; +import { PublishedCacheResource } from '@umbraco-cms/backend-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; +import { UmbLitElement } from '@umbraco-cms/element'; @customElement('umb-dashboard-published-status') -export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin(LitElement) { +export class UmbDashboardPublishedStatusElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -39,14 +38,12 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( @state() private _buttonStateCollect: UUIButtonState = undefined; - private _notificationService?: UmbNotificationService; private _modalService?: UmbModalService; constructor() { super(); - this.consumeAllContexts(['umbNotificationService', 'umbModalService'], (instances) => { - this._notificationService = instances['umbNotificationService']; + this.consumeAllContexts(['umbModalService'], (instances) => { this._modalService = instances['umbModalService']; }); } @@ -57,19 +54,13 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( } // Refresh - private async _getPublishedStatus() { - try { - const data = await PublishedCacheResource.getPublishedCacheStatus(); + const { data } = await tryExecuteAndNotify(this, PublishedCacheResource.getPublishedCacheStatus()); + if (data) { this._publishedStatusText = data; - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.detail ?? 'Something went wrong' }; - this._notificationService?.peek('danger', { data }); - } } } + private async _onRefreshCacheHandler() { this._buttonState = 'waiting'; await this._getPublishedStatus(); @@ -80,19 +71,14 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( private async _reloadMemoryCache() { this._buttonStateReload = 'waiting'; this._buttonState = 'waiting'; - try { - await PublishedCacheResource.postPublishedCacheReload(); + const { error } = await tryExecuteAndNotify(this, PublishedCacheResource.postPublishedCacheReload()); + if (error) { + this._buttonStateReload = 'failed'; + this._buttonState = 'failed'; + } else { this._buttonStateReload = 'success'; this._getPublishedStatus(); this._buttonState = 'success'; - } catch (e) { - this._buttonStateReload = 'failed'; - this._buttonState = 'failed'; - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.detail ?? 'Something went wrong' }; - this._notificationService?.peek('danger', { data }); - } } } private async _onReloadCacheHandler() { @@ -102,7 +88,7 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( color: 'danger', confirmLabel: 'Continue', }); - modalHandler?.onClose().then(({ confirmed }: any) => { + modalHandler?.onClose().then(({ confirmed }) => { if (confirmed) this._reloadMemoryCache(); }); } @@ -110,18 +96,14 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( // Rebuild private async _rebuildDatabaseCache() { this._buttonStateRebuild = 'waiting'; - try { - await PublishedCacheResource.postPublishedCacheRebuild(); - this._buttonStateRebuild = 'success'; - } catch (e) { + const { error } = await tryExecuteAndNotify(this, PublishedCacheResource.postPublishedCacheRebuild()); + if (error) { this._buttonStateRebuild = 'failed'; - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.detail ?? 'Something went wrong' }; - this._notificationService?.peek('danger', { data }); - } + } else { + this._buttonStateRebuild = 'success'; } } + private async _onRebuildCacheHandler() { const modalHandler = this._modalService?.confirm({ headline: 'Rebuild', @@ -129,25 +111,21 @@ export class UmbDashboardPublishedStatusElement extends UmbContextConsumerMixin( color: 'danger', confirmLabel: 'Continue', }); - modalHandler?.onClose().then(({ confirmed }: any) => { + modalHandler?.onClose().then(({ confirmed }) => { if (confirmed) this._rebuildDatabaseCache(); }); } //Collect private async _cacheCollect() { - try { - await PublishedCacheResource.postPublishedCacheCollect(); - this._buttonStateCollect = 'success'; - } catch (e) { + const { error } = await tryExecuteAndNotify(this, PublishedCacheResource.postPublishedCacheCollect()); + if (error) { this._buttonStateCollect = 'failed'; - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - const data: UmbNotificationDefaultData = { message: error.data.detail ?? 'Something went wrong' }; - this._notificationService?.peek('danger', { data }); - } + } else { + this._buttonStateCollect = 'success'; } } + private async _onSnapshotCacheHandler() { this._buttonStateCollect = 'waiting'; await this._cacheCollect(); diff --git a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/telemetry/dashboard-telemetry.element.ts b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/telemetry/dashboard-telemetry.element.ts index 544b2b1fa2..140e3b94f7 100644 --- a/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/telemetry/dashboard-telemetry.element.ts +++ b/src/Umbraco.Web.UI.Client/src/backoffice/settings/dashboards/telemetry/dashboard-telemetry.element.ts @@ -1,12 +1,14 @@ +import { css, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { UUIButtonState } from '@umbraco-ui/uui'; import { UUITextStyles } from '@umbraco-ui/uui-css/lib'; -import { css, html, LitElement } from 'lit'; -import { unsafeHTML } from 'lit/directives/unsafe-html.js'; -import { customElement, state } from 'lit/decorators.js'; -import { ApiError, ProblemDetails, Telemetry, TelemetryLevel, TelemetryResource } from '@umbraco-cms/backend-api'; +import { Telemetry, TelemetryLevel, TelemetryResource } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecuteAndNotify } from '@umbraco-cms/resources'; @customElement('umb-dashboard-telemetry') -export class UmbDashboardTelemetryElement extends LitElement { +export class UmbDashboardTelemetryElement extends UmbLitElement { static styles = [ UUITextStyles, css` @@ -38,46 +40,32 @@ export class UmbDashboardTelemetryElement extends LitElement { } private async _setup() { - try { - const consentLevels = await TelemetryResource.getTelemetry({skip: 0, take: 3}); - this._telemetryLevels = consentLevels.items ?? []; - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - this._errorMessage = error.detail; - } - } + const telemetryLevels = await tryExecuteAndNotify(this, TelemetryResource.getTelemetry({ skip: 0, take: 3 })); + this._telemetryLevels = telemetryLevels.data?.items ?? []; - try { - const consentSetting = await TelemetryResource.getTelemetryLevel(); - this._telemetryFormData = consentSetting.telemetryLevel ?? TelemetryLevel.BASIC; - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - this._errorMessage = error.detail; - } - } + const telemetryLevel = await tryExecuteAndNotify(this, TelemetryResource.getTelemetryLevel()); + this._telemetryFormData = telemetryLevel.data?.telemetryLevel ?? TelemetryLevel.BASIC; } private _handleSubmit = async (e: CustomEvent) => { e.stopPropagation(); + this._buttonState = 'waiting'; - try { - await TelemetryResource.postTelemetryLevel({ + + const { error } = await tryExecuteAndNotify( + this, + TelemetryResource.postTelemetryLevel({ requestBody: { telemetryLevel: this._telemetryFormData }, - }); - this._buttonState = 'success'; - } catch (e) { + }) + ); + + if (error) { this._buttonState = 'failed'; - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - if (e.status === 400) { - this._errorMessage = error.detail || 'Unknown error, please try again'; - } - } else { - this._errorMessage = 'Unknown error, please try again'; - } + this._errorMessage = error.detail; + return; } + + this._buttonState = 'success'; }; private _handleChange(e: InputEvent) { @@ -146,7 +134,12 @@ export class UmbDashboardTelemetryElement extends LitElement { will be fully anonymized.

${this._renderSettingSlider()} - + Save diff --git a/src/Umbraco.Web.UI.Client/src/core/element/index.ts b/src/Umbraco.Web.UI.Client/src/core/element/index.ts new file mode 100644 index 0000000000..4048b79c02 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/element/index.ts @@ -0,0 +1,2 @@ +export * from './element.mixin'; +export * from './lit-element.element'; diff --git a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts index 5684fbb522..620ef6b26b 100644 --- a/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts +++ b/src/Umbraco.Web.UI.Client/src/core/mocks/domains/install.handlers.ts @@ -72,7 +72,7 @@ export const handlers = [ ); }), - rest.post(umbracoPath('/install/validateDatabase'), async (req, res, ctx) => { + rest.post(umbracoPath('/install/validate-database'), async (req, res, ctx) => { const body = await req.json(); if (body.name === 'validate') { diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/index.ts b/src/Umbraco.Web.UI.Client/src/core/resources/index.ts index b4dfcb2097..b5aa810083 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/index.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/index.ts @@ -1 +1,3 @@ export * from './resource.controller'; +export * from './tryExecute.method'; +export * from './tryExecuteAndNotify.method'; diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts index 06cf06721e..5668c41180 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/resource.controller.ts @@ -6,25 +6,6 @@ import { ApiError, CancelablePromise, ProblemDetails } from '@umbraco-cms/backen import { UmbNotificationOptions, UmbNotificationService } from 'src/core/notification'; import { UmbNotificationDefaultData } from 'src/core/notification/layouts/default'; -/** - * Extract the ProblemDetails object from an ApiError. - * - * This assumes that all ApiErrors contain a ProblemDetails object in their body. - */ -function toProblemDetails(error: unknown): ProblemDetails | undefined { - if (error instanceof ApiError) { - const errorDetails = error.body as ProblemDetails; - return errorDetails; - } else if (error instanceof Error) { - return { - title: error.name, - detail: error.message, - }; - } - - return undefined; -} - export class UmbResourceController extends UmbController { #promise: Promise; @@ -40,22 +21,41 @@ export class UmbResourceController extends UmbController { }); } - hostConnected() { - this.hostConnected(); + hostConnected(): void { + // Do nothing } - hostDisconnected() { + hostDisconnected(): void { this.cancel(); } /** - * Wrap the {execute} function in a try/catch block and return a tuple with the result and the error. + * Extract the ProblemDetails object from an ApiError. + * + * This assumes that all ApiErrors contain a ProblemDetails object in their body. */ - async tryExecute(): Promise<{ data?: T; error?: ProblemDetails }> { + static toProblemDetails(error: unknown): ProblemDetails | undefined { + if (error instanceof ApiError) { + const errorDetails = error.body as ProblemDetails; + return errorDetails; + } else if (error instanceof Error) { + return { + title: error.name, + detail: error.message, + }; + } + + return undefined; + } + + /** + * Base execute function with a try/catch block and return a tuple with the result and the error. + */ + static async tryExecute(promise: Promise): Promise<{ data?: T; error?: ProblemDetails }> { try { - return { data: await this.#promise }; + return { data: await promise }; } catch (e) { - return { error: toProblemDetails(e) }; + return { error: UmbResourceController.toProblemDetails(e) }; } } @@ -64,7 +64,7 @@ export class UmbResourceController extends UmbController { * If the executor function throws an error, then show the details in a notification. */ async tryExecuteAndNotify(options?: UmbNotificationOptions): Promise<{ data?: T; error?: ProblemDetails }> { - const { data, error } = await this.tryExecute(); + const { data, error } = await UmbResourceController.tryExecute(this.#promise); if (error) { const data: UmbNotificationDefaultData = { diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts new file mode 100644 index 0000000000..a65503d760 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecute.method.ts @@ -0,0 +1,5 @@ +import { UmbResourceController } from './resource.controller'; + +export function tryExecute(promise: Promise) { + return UmbResourceController.tryExecute(promise); +} diff --git a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts index 919ffcd7d9..ac1b9c860d 100644 --- a/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts +++ b/src/Umbraco.Web.UI.Client/src/core/resources/tryExecuteAndNotify.method.ts @@ -1,13 +1,12 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { UmbControllerHostInterface } from '../controller/controller-host.mixin'; -import type { ProblemDetails } from '../backend-api/models/ProblemDetails'; import { UmbResourceController } from './resource.controller'; import { UmbNotificationOptions } from 'src/core/notification'; -export async function tryExecuteAndNotify( +export function tryExecuteAndNotify( host: UmbControllerHostInterface, resource: Promise, options?: UmbNotificationOptions -): Promise<{ data?: T; error?: ProblemDetails }> { - return await new UmbResourceController(host, resource).tryExecuteAndNotify(options); +) { + return new UmbResourceController(host, resource).tryExecuteAndNotify(options); } diff --git a/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts b/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts index 995e231b87..674917a6e9 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/database/installer-database.element.ts @@ -1,12 +1,9 @@ import { UUIButtonElement } from '@umbraco-ui/uui'; -import { css, CSSResultGroup, html, LitElement, nothing } from 'lit'; +import { css, CSSResultGroup, html, nothing } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { UmbInstallerContext } from '../installer.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextConsumerMixin } from '@umbraco-cms/context-api'; import { - ApiError, DatabaseInstall, DatabaseSettings, Install, @@ -14,9 +11,11 @@ import { InstallSettings, ProblemDetails, } from '@umbraco-cms/backend-api'; +import { UmbLitElement } from '@umbraco-cms/element'; +import { tryExecute } from '@umbraco-cms/resources'; @customElement('umb-installer-database') -export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerDatabaseElement extends UmbLitElement { static styles: CSSResultGroup = [ css` :host, @@ -88,7 +87,7 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse public databaseFormData!: DatabaseInstall; @state() - private _options: { name: string; value: string; selected?: boolean }[] = []; + private _options: Option[] = []; @state() private _databases: DatabaseSettings[] = []; @@ -115,17 +114,20 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse if (!this._installerContext) return; this.observe(this._installerContext.settings, (settings) => { - this._databases = settings.databases ?? []; + this._databases = settings?.databases ?? []; // If there is an isConfigured database in the databases array then we can skip the database selection step // and just use that. this._preConfiguredDatabase = this._databases.find((x) => x.isConfigured); if (!this._preConfiguredDatabase) { - this._options = this._databases.map((x, i) => ({ - name: x.displayName ?? 'Unknown database', - value: x.id!, - selected: i === 0, - })); + this._options = this._databases + .filter((x) => !!x.id) + .map((x, i) => ({ + name: x.displayName ?? 'Unknown database', + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + value: x.id!, + selected: i === 0, + })); } }); } @@ -134,8 +136,8 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse if (!this._installerContext) return; this.observe(this._installerContext.data, (data) => { - this.databaseFormData = data.database ?? {}; - this._options.forEach((x, i) => (x.selected = data.database?.id === x.value || i === 0)); + this.databaseFormData = data?.database ?? ({} as DatabaseInstall); + this._options.forEach((x, i) => (x.selected = data?.database?.id === x.value || i === 0)); }); } @@ -190,27 +192,23 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse } if (selectedDatabase.requiresConnectionTest) { - try { - const databaseDetails: DatabaseInstall = { - id, - username, - password, - server, - useIntegratedAuthentication, - name, - connectionString, - providerName: selectedDatabase.providerName, - }; + const databaseDetails: DatabaseInstall = { + id, + username, + password, + server, + useIntegratedAuthentication, + name, + connectionString, + providerName: selectedDatabase.providerName, + }; - await InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails }); - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - console.warn('Database validation failed', error.detail); - this._validationErrorMessage = error.detail ?? 'Could not verify database connection'; - } else { - this._validationErrorMessage = 'A server error happened when trying to validate the database'; - } + const { error } = await tryExecute( + InstallResource.postInstallValidateDatabase({ requestBody: databaseDetails }) + ); + + if (error) { + this._validationErrorMessage = `The server could not validate the database connection. Details: ${error.detail}`; this._installButton.state = 'failed'; return; } @@ -228,30 +226,31 @@ export class UmbInstallerDatabaseElement extends UmbContextConsumerMixin(UmbObse providerName: selectedDatabase.providerName, }; - this._installerContext?.appendData({ database }); + this._installerContext.appendData({ database }); } - this._installerContext?.nextStep(); - this._installerContext - .requestInstall() - .then(() => this._handleFulfilled()) - .catch((error: unknown) => this._handleRejected(error)); + this._installerContext.nextStep(); + + const { error } = await tryExecute( + InstallResource.postInstallSetup({ requestBody: this._installerContext.getData() }) + ); + + if (error) { + this._handleRejected(error); + } else { + this._handleFulfilled(); + } }; private _handleFulfilled() { + // TODO: The post install will probably return a user in the future, so we have to set that context somewhere to let the client know that it is authenticated console.warn('TODO: Set up real authentication'); sessionStorage.setItem('is-authenticated', 'true'); history.replaceState(null, '', '/content'); } - private _handleRejected(e: unknown) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - if (e.status === 400) { - this._installerContext?.setInstallStatus(error); - } - } - this._installerContext?.nextStep(); + private _handleRejected(e: ProblemDetails) { + this._installerContext?.setInstallStatus(e); } private _onBack() { diff --git a/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts b/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts index 5063330362..404372d9be 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/error/installer-error.element.ts @@ -23,7 +23,7 @@ export class UmbInstallerErrorElement extends UmbContextConsumerMixin(UmbObserve } #error-message { - color: var(--uui-color-error, red); + color: var(--uui-color-danger, red); } `, ]; diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts b/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts index 717ae07817..2a0db363fa 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.context.ts @@ -1,5 +1,6 @@ import { BehaviorSubject, Observable, ReplaySubject } from 'rxjs'; import { Install, InstallResource, InstallSettings, ProblemDetails, TelemetryLevel } from '@umbraco-cms/backend-api'; +import { tryExecute } from '@umbraco-cms/resources'; /** * Context API for the installer @@ -71,8 +72,8 @@ export class UmbInstallerContext { * @memberof UmbInstallerContext */ public reset(): void { - this._currentStep.next(1); this._installStatus.next(null); + this._currentStep.next(1); } /** @@ -95,17 +96,6 @@ export class UmbInstallerContext { return this._data.getValue(); } - /** - * Post the installation data to the API - * @public - * @return {*} - * @memberof UmbInstallerContext - */ - public requestInstall() { - // TODO: The post install will probably return a user in the future, so we have to set that context somewhere to let the client know that it is authenticated - return InstallResource.postInstallSetup({ requestBody: this.getData() }); - } - /** * Set the install status * @public @@ -121,9 +111,13 @@ export class UmbInstallerContext { * @private * @memberof UmbInstallerContext */ - private _loadInstallerSettings() { - InstallResource.getInstallSettings().then((installSettings) => { - this._settings.next(installSettings); - }); + private async _loadInstallerSettings() { + const { data, error } = await tryExecute(InstallResource.getInstallSettings()); + if (data) { + this._settings.next(data); + } else if (error) { + console.error(error.detail, error); + this._installStatus.next(error); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts index 66e04f97bf..13e3da22fc 100644 --- a/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts +++ b/src/Umbraco.Web.UI.Client/src/installer/installer.element.ts @@ -1,8 +1,7 @@ -import { css, CSSResultGroup, html, LitElement } from 'lit'; +import { css, CSSResultGroup, html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { UmbInstallerContext } from './installer.context'; -import { UmbObserverMixin } from '@umbraco-cms/observable-api'; -import { UmbContextProviderMixin } from '@umbraco-cms/context-api'; +import { UmbLitElement } from '@umbraco-cms/element'; import './consent/installer-consent.element'; import './database/installer-database.element'; @@ -12,7 +11,7 @@ import './shared/layout/installer-layout.element'; import './user/installer-user.element'; @customElement('umb-installer') -export class UmbInstallerElement extends UmbContextProviderMixin(UmbObserverMixin(LitElement)) { +export class UmbInstallerElement extends UmbLitElement { static styles: CSSResultGroup = [css``]; @state() @@ -31,8 +30,16 @@ export class UmbInstallerElement extends UmbContextProviderMixin(UmbObserverMixi } private _observeCurrentStep() { - this.observe(this._umbInstallerContext.currentStepChanges(), (step) => { - this.step = step; + this.observe(this._umbInstallerContext.currentStepChanges(), (step) => { + if (step) { + this.step = step; + } + }); + + this.observe(this._umbInstallerContext.installStatusChanges(), (error) => { + if (error) { + this.step = 5; + } }); } diff --git a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts index 803a2a4ae5..4374ffccc6 100644 --- a/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts +++ b/src/Umbraco.Web.UI.Client/src/upgrader/upgrader.element.ts @@ -1,15 +1,17 @@ import '../installer/shared/layout/installer-layout.element'; import './upgrader-view.element'; -import { html, LitElement } from 'lit'; +import { html } from 'lit'; import { customElement, state } from 'lit/decorators.js'; -import { ApiError, ProblemDetails, UpgradeResource, UpgradeSettings } from '@umbraco-cms/backend-api'; +import { UpgradeResource, UpgradeSettings } from '@umbraco-cms/backend-api'; +import { tryExecute } from '@umbraco-cms/resources'; +import { UmbLitElement } from '@umbraco-cms/element'; /** * @element umb-upgrader */ @customElement('umb-upgrader') -export class UmbUpgrader extends LitElement { +export class UmbUpgrader extends UmbLitElement { @state() private upgradeSettings?: UpgradeSettings; @@ -41,14 +43,12 @@ export class UmbUpgrader extends LitElement { private async _setup() { this.fetching = true; - try { - const data = await UpgradeResource.getUpgradeSettings(); + const { data, error } = await tryExecute(UpgradeResource.getUpgradeSettings()); + + if (data) { this.upgradeSettings = data; - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - this.errorMessage = error.detail; - } + } else if (error) { + this.errorMessage = error.detail; } this.fetching = false; @@ -59,18 +59,12 @@ export class UmbUpgrader extends LitElement { this.errorMessage = ''; this.upgrading = true; - try { - await UpgradeResource.postUpgradeAuthorize(); + const { error } = await tryExecute(UpgradeResource.postUpgradeAuthorize()); + + if (error) { + this.errorMessage = error.detail || 'Unknown error, please try again'; + } else { history.pushState(null, '', '/'); - } catch (e) { - if (e instanceof ApiError) { - const error = e.body as ProblemDetails; - if (e.status === 400) { - this.errorMessage = error.detail || 'Unknown error, please try again'; - } - } else { - this.errorMessage = 'Unknown error, please try again'; - } } this.upgrading = false; diff --git a/src/Umbraco.Web.UI.Client/tsconfig.json b/src/Umbraco.Web.UI.Client/tsconfig.json index 6f278ee1d3..3b5a74a536 100644 --- a/src/Umbraco.Web.UI.Client/tsconfig.json +++ b/src/Umbraco.Web.UI.Client/tsconfig.json @@ -23,6 +23,7 @@ "@umbraco-cms/models": ["src/core/models"], "@umbraco-cms/backend-api": ["src/core/backend-api"], "@umbraco-cms/context-api": ["src/core/context-api"], + "@umbraco-cms/element": ["src/core/element"], "@umbraco-cms/extensions-api": ["src/core/extensions-api"], "@umbraco-cms/extensions-registry": ["src/core/extensions-registry"], "@umbraco-cms/observable-api": ["src/core/observable-api"], diff --git a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs index bf55d97a2d..c4bef5c1ca 100644 --- a/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs +++ b/src/Umbraco.Web.UI.Client/web-test-runner.config.mjs @@ -14,12 +14,12 @@ export default { '@umbraco-cms/models': './src/core/models/index.ts', '@umbraco-cms/backend-api': './src/core/backend-api/index.ts', '@umbraco-cms/context-api': './src/core/context-api/index.ts', + '@umbraco-cms/element': './src/core/element/index.ts', '@umbraco-cms/extensions-api': './src/core/extensions-api/index.ts', '@umbraco-cms/observable-api': './src/core/observable-api/index.ts', - '@umbraco-cms/resource-api': './src/core/resource-api', '@umbraco-cms/utils': './src/core/utils/index.ts', '@umbraco-cms/test-utils': './src/core/test-utils/index.ts', - '@umbraco-cms/resources': './src/core/resources', + '@umbraco-cms/resources': './src/core/resources/index.ts', '@umbraco-cms/services': './src/core/services', '@umbraco-cms/extensions-registry': './src/core/extensions-registry/index.ts', },