From e34d290865224b406791abff3d856ce81cebaaf0 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Fri, 30 Oct 2026 14:24:58 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9C=D0=BE=D0=BD=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D0=B3:=20=D0=BB=D0=BE=D0=B3=D0=B8,=20=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=B1=D0=BA=D0=B8=20=D0=B8=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D1=8C=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BE=D1=80=D1=82=D0=B0=D0=BB=D0=B0=20=D0=B7=D0=B0=D1=8F=D0=B2?= =?UTF-8?q?=D0=BE=D0=BA=20(@tnsu/monitoring-web)=20(#29)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Логи, ошибки и запись экрана портала заявок в центральный мониторинг (monitoring.tnsu.kz). **Что поменялось** - `@tnsu/monitoring-web` 0.3.1 из реестра tansu. - `RequestPortalWebPart.onInit`: `initMonitoring({ service: 'request-portal', getAccessToken: spfxTokenProvider(this.context.aadTokenProviderFactory) })` — до `installErrorLogging`. Один на страницу (экземпляров веб-части бывает несколько) — это делает сама библиотека. - `config/package-solution.json`: запрос `Logs.Write` к регистрации мониторинга `9232d9e2-…` (рядом с уже запрошенным `Reports.Read` той же регистрации). - Хэш коммита сборки (`git.commit` в логах, «Сборка» в плеере): `config/webpack-patch.json` ссылается на патч из пакета (`node_modules/@tnsu/monitoring-web/spfx/webpack-patch.cjs`), он подставляет `__TNSU_GIT_COMMIT__` из `GIT_COMMIT`, `GITHUB_SHA` или `git rev-parse`. Пайплайн менять не нужно. **Что пишется** - Необработанные ошибки и отказы промисов, `console.error` (в том числе вывод `installErrorLogging`), `log.*`. - Запись экрана (rrweb) — **ничего не маскируется**, записывается вся страница SharePoint, не только веб-часть. Неудачные запросы — вкладка «Сеть» в плеере. - В workbench / при `gulp serve` окружение `development` — в админке мониторинга такие записи по умолчанию скрыты. - Нет связи с мониторингом час — тост «Ошибка №… Обратитесь к администратору» (не чаще 3 раз в сутки). **Проверено:** продакшен-сборка (`heft build --production`) проходит, патч применяется, хэш коммита попадает в бандл веб-части. **После выкатки .sppkg** администратор один раз одобряет запрос: SharePoint admin center → Advanced → API access → `Logs.Write` → Approve. Пока не одобрено, токена нет и данные копятся в браузере (до 50 МБ), портал работает как обычно. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: gorbenko.pavel Reviewed-on: https://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/29 --- config/package-solution.json | 4 + config/webpack-patch.json | 4 + package-lock.json | 156 +++++++++++++++--- package.json | 2 + .../requestPortal/RequestPortalWebPart.ts | 9 + .../approval/approverDecision.service.ts | 46 ++++++ src/webparts/requestPortal/shared/dataList.ts | 33 ++++ 7 files changed, 230 insertions(+), 24 deletions(-) create mode 100644 config/webpack-patch.json diff --git a/config/package-solution.json b/config/package-solution.json index 2307bd4..0ef3b19 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -30,6 +30,10 @@ "resource": "9232d9e2-9807-4753-808f-07250e5474b0", "scope": "Reports.Read" }, + { + "resource": "9232d9e2-9807-4753-808f-07250e5474b0", + "scope": "Logs.Write" + }, { "resource": "Microsoft Flow Service", "scope": "Flows.Read.All" diff --git a/config/webpack-patch.json b/config/webpack-patch.json new file mode 100644 index 0000000..55d994e --- /dev/null +++ b/config/webpack-patch.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/spfx-build/webpack-patch.schema.json", + "patchFiles": ["./node_modules/@tnsu/monitoring-web/spfx/webpack-patch.cjs"] +} diff --git a/package-lock.json b/package-lock.json index b807ab1..040daa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,8 @@ "@microsoft/sp-webpart-base": "1.22.1", "@pnp/sp": "^4.20.0", "@pnp/spfx-controls-react": "^3.25.0", + "@pnp/telemetry-js": "2.0.0", + "@tnsu/monitoring-web": "^0.3.4", "@tnsu/ui-kit": "^0.15.0", "jszip": "~3.8.0", "react": "17.0.1", @@ -341,7 +343,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -351,7 +353,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -382,7 +384,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -408,7 +410,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -425,7 +427,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "devOptional": true, + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -457,7 +459,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -503,7 +505,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -513,7 +515,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -3979,7 +3981,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -8726,6 +8728,28 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rrweb/rrweb-plugin-console-record": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rrweb/rrweb-plugin-console-record/-/rrweb-plugin-console-record-2.2.0.tgz", + "integrity": "sha512-2c6LEsoH3+1NFcHckcwuErg7bkpFadt9++uKJh2x9BtWUDxoVKzLp+qLCq6f90MLTjSIABDkAiCswVskENZT3g==", + "license": "MIT", + "peerDependencies": { + "@rrweb/utils": "^2.1.1", + "rrweb": "^2.1.1" + } + }, + "node_modules/@rrweb/types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rrweb/types/-/types-2.2.0.tgz", + "integrity": "sha512-2gTA/pRpIbuET123oSdX5ZUDZfWCOIMYBvpSai3fr5pk9LcbccPqqaXflX+roP4oKenLjNtJPGpFK3BfYeBKnA==", + "license": "MIT" + }, + "node_modules/@rrweb/utils": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@rrweb/utils/-/utils-2.2.0.tgz", + "integrity": "sha512-e8FEp8wLswqgjDHpQNUUVfEXeDsrRMOVouvqIGKvnPxKYonjLlh0mU3O/17QNvCixFG5eUFR1B2jgTdiLlZxeQ==", + "license": "MIT" + }, "node_modules/@rushstack/debug-certificate-manager": { "version": "1.6.2", "resolved": "https://registry.npmjs.org/@rushstack/debug-certificate-manager/-/debug-certificate-manager-1.6.2.tgz", @@ -9885,6 +9909,28 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/@tnsu/monitoring-web": { + "version": "0.3.4", + "resolved": "https://git.tnsu.kz/api/packages/tansu/npm/%40tnsu%2Fmonitoring-web/-/0.3.4/monitoring-web-0.3.4.tgz", + "integrity": "sha512-tQe0ZM1tBVO3XtbNFliRaaooqvCX3gb/Q/j2lV6+rYCXprodBZByQ6PLrCu/P0me/rW8KdQ02hu7avyLXXfkoQ==", + "license": "UNLICENSED", + "dependencies": { + "@rrweb/rrweb-plugin-console-record": "^2.2.0", + "rrweb": "^2.2.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "vue": "^3.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, "node_modules/@tnsu/ui-kit": { "version": "0.15.0", "resolved": "https://git.tnsu.kz/api/packages/tansu/npm/%40tnsu%2Fui-kit/-/0.15.0/ui-kit-0.15.0.tgz", @@ -10022,6 +10068,12 @@ "@types/node": "*" } }, + "node_modules/@types/css-font-loading-module": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.7.tgz", + "integrity": "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q==", + "license": "MIT" + }, "node_modules/@types/ejs": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", @@ -10250,7 +10302,7 @@ "version": "26.1.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz", "integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~8.3.0" @@ -10279,6 +10331,7 @@ "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -10308,6 +10361,7 @@ "version": "17.0.45", "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.45.tgz", "integrity": "sha512-YfhQ22Lah2e3CHPsb93tRwIGNiSwkuz1/blk4e6QrWS0jQzCSNbGLtOEYhPg02W0yGTTmpajp7dCTbBAMN3qsg==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -10319,6 +10373,7 @@ "version": "17.0.17", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.17.tgz", "integrity": "sha512-VjnqEmqGnasQKV0CWLevqMTXBYG9GbwuE6x3VetERLh0cq2LTptFE73MrQi2S7GkKXCf2GgwItB/melLnxfnsg==", + "dev": true, "license": "MIT", "dependencies": { "@types/react": "^17" @@ -10341,6 +10396,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", + "dev": true, "license": "MIT" }, "node_modules/@types/send": { @@ -11627,6 +11683,12 @@ "node": ">=10.0.0" } }, + "node_modules/@xstate/fsm": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@xstate/fsm/-/fsm-1.6.5.tgz", + "integrity": "sha512-b5o1I6aLNeYlU/3CPlj/Z91ybk1gUsKT+5NAJI+2W4UjvS5KLG28K9v5UvNoFVjHV8PajVZ00RH3vnjyQO7ZAw==", + "license": "MIT" + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -12513,6 +12575,15 @@ } } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -12537,7 +12608,7 @@ "version": "2.11.7", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.7.tgz", "integrity": "sha512-APw5YuIQAg6L9w4sHDI6j26DGFJI6RpYOhnkMPdC9lWbkKvsyPHzDsve1yd73lk21yz7Y09Kci8B2Pp9FonzWA==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -12711,7 +12782,7 @@ "version": "4.28.7", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", "integrity": "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", @@ -12920,7 +12991,7 @@ "version": "1.0.30001806", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", @@ -13345,7 +13416,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -14234,7 +14305,7 @@ "version": "1.5.398", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.398.tgz", "integrity": "sha512-AsvhAxopJGh6museTDMIjn6JpDYOfgu4RLlygomt87MUwBUqTfd/1EiPtx10/LZE8xpTvkP2E9Gafq7lkLtodQ==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/embla-carousel": { @@ -14564,7 +14635,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -15984,7 +16055,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -19133,7 +19204,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -19813,6 +19884,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -19935,7 +20012,6 @@ "version": "3.3.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", - "dev": true, "funding": [ { "type": "github", @@ -20077,7 +20153,7 @@ "version": "2.0.51", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -20742,7 +20818,6 @@ "version": "8.5.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", - "dev": true, "funding": [ { "type": "opencollective", @@ -22018,6 +22093,40 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rrdom": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rrdom/-/rrdom-2.2.0.tgz", + "integrity": "sha512-gxcBJWVtS/Xz+Ys8xcjPoWqr+C7wz/MS8NgmKw136I47Xygy17FYOGZqoGwEyvBn724jRDK1ljS+8w9FsZStOg==", + "license": "MIT", + "dependencies": { + "rrweb-snapshot": "^2.2.0" + } + }, + "node_modules/rrweb": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rrweb/-/rrweb-2.2.0.tgz", + "integrity": "sha512-ofVzPq11/yIQ7WhVb++rJGI2LoPPrxdqu0QAvRA4KGeXJ7LH52jQ0VlRH4+VXn5PA/7pz85/rRkQyBUqT1LraQ==", + "license": "MIT", + "dependencies": { + "@rrweb/types": "^2.2.0", + "@rrweb/utils": "^2.2.0", + "@types/css-font-loading-module": "0.0.7", + "@xstate/fsm": "^1.4.0", + "base64-arraybuffer": "^1.0.1", + "mitt": "^3.0.0", + "rrdom": "^2.2.0", + "rrweb-snapshot": "^2.2.0" + } + }, + "node_modules/rrweb-snapshot": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rrweb-snapshot/-/rrweb-snapshot-2.2.0.tgz", + "integrity": "sha512-Tg/PiCLy+zV9C0Jfb4IAGABtt5l/UOvwW5xjFk41ZQhBfUjeqPP1rZ9HMDrFPc7cawCUEenWw54+g/4vluO5lg==", + "license": "MIT", + "dependencies": { + "postcss": "^8.4.38" + } + }, "node_modules/rtl-css-js": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.16.1.tgz", @@ -23127,7 +23236,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -24407,7 +24515,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unicorn-magic": { @@ -24461,7 +24569,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "devOptional": true, + "dev": true, "funding": [ { "type": "opencollective", diff --git a/package.json b/package.json index 768353b..96208f0 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@microsoft/sp-webpart-base": "1.22.1", "@pnp/sp": "^4.20.0", "@pnp/spfx-controls-react": "^3.25.0", + "@pnp/telemetry-js": "2.0.0", + "@tnsu/monitoring-web": "^0.3.4", "@tnsu/ui-kit": "^0.15.0", "jszip": "~3.8.0", "react": "17.0.1", diff --git a/src/webparts/requestPortal/RequestPortalWebPart.ts b/src/webparts/requestPortal/RequestPortalWebPart.ts index 988eb37..725e0db 100644 --- a/src/webparts/requestPortal/RequestPortalWebPart.ts +++ b/src/webparts/requestPortal/RequestPortalWebPart.ts @@ -9,6 +9,8 @@ import { } from '@microsoft/sp-property-pane'; import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; import { IReadonlyTheme } from '@microsoft/sp-component-base'; +import PnPTelemetry from '@pnp/telemetry-js'; +import { initMonitoring, spfxTokenProvider } from '@tnsu/monitoring-web'; import * as strings from 'RequestPortalWebPartStrings'; import RequestPortal from './components/RequestPortal'; @@ -98,7 +100,14 @@ export default class RequestPortalWebPart extends BaseClientSideWebPart { + // Логи, ошибки, console.error и запись экрана в мониторинг (monitoring.tnsu.kz). Токен — молча через + // AAD веб-части (разрешение Logs.Write в package-solution.json); при отладке в workbench окружение — development. + // Один на страницу: повторный вызов (несколько веб-частей) библиотека игнорирует сама. + initMonitoring({ service: 'request-portal', getAccessToken: spfxTokenProvider(this.context.aadTokenProviderFactory) }); installErrorLogging(); // до всего остального — чтобы поймать и ранние отклонения + // Телеметрия PnP (её включает @pnp/spfx-controls-react) шлёт fetch на pnptelemetryproxy.azurewebsites.net + // без catch: где адрес закрыт прокси или блокировщиком, это необработанный отказ промиса в мониторинге. + PnPTelemetry.getInstance().optOut(); initSP(this.context); // единый клиент PnPjs для записи в списки // Адрес approval-route должен быть выставлен ДО загрузки партиций — они diff --git a/src/webparts/requestPortal/shared/approval/approverDecision.service.ts b/src/webparts/requestPortal/shared/approval/approverDecision.service.ts index bfba56e..970e1c6 100644 --- a/src/webparts/requestPortal/shared/approval/approverDecision.service.ts +++ b/src/webparts/requestPortal/shared/approval/approverDecision.service.ts @@ -1,3 +1,4 @@ +import { log } from '@tnsu/monitoring-web'; import { getSP } from '../pnp/pnpjs'; import '@pnp/sp/webs'; import '@pnp/sp/lists'; @@ -25,6 +26,14 @@ export interface IPendingRow extends IApprovalRow { isDelegator: boolean; } +// Решение → текст события в логах мониторинга. +const DECISION_MESSAGE: Record = { + approve: 'Заявка согласована', + return: 'Заявка возвращена на доработку', + reject: 'Заявка отклонена', + execute: 'Заявка выполнена исполнителем', +}; + // Решение → статус его строки в списке согласования. const ROW_STATUS: Record = { approve: REQUEST_STATUS.approved, @@ -336,6 +345,7 @@ export class ApproverService { const rows = await this.approval.getForRequest(requestId); const requestStatus = this.computeRequestStatus(rows); await this.updateRequestStatus(requestId, requestStatus); + await this.logDecision(requestId, rowId, decision, comment, requestStatus); emitRequestsChanged(); return requestStatus; } @@ -364,6 +374,42 @@ export class ApproverService { return REQUEST_STATUS.execution; } + /** + * Решение — в логи мониторинга, с кодом и названием заявки, чтобы её было видно прямо в списке логов. + * Код и название дочитываются отдельно; не получилось — пишем без них: решение уже применено. + */ + private async logDecision( + requestId: number, + rowId: number, + decision: ApproverDecision, + comment: string, + requestStatus: string + ): Promise { + let code = ''; + let title = ''; + try { + const item = (await this.dataList().items.getById(requestId).select('Title', 'RequestUniqueCode')()) as { + Title?: string; + RequestUniqueCode?: string; + }; + code = item.RequestUniqueCode || ''; + title = item.Title || ''; + } catch { + // без кода и названия — лог всё равно нужен + } + const what = [code, title].filter(Boolean).join(' · ') || `№${requestId}`; + log.info(`${DECISION_MESSAGE[decision]}: ${what}`, { + 'request.id': requestId, + 'request.code': code, + 'request.title': title, + 'request.list': this.dataListRef, + 'request.status': requestStatus, + 'approval.decision': decision, + 'approval.rowId': rowId, + 'approval.comment': comment.slice(0, 500), + }); + } + private dataList(): IList { return this.dataListRef.startsWith('/') ? getSP().web.getList(this.dataListRef) diff --git a/src/webparts/requestPortal/shared/dataList.ts b/src/webparts/requestPortal/shared/dataList.ts index 97620e5..1eab111 100644 --- a/src/webparts/requestPortal/shared/dataList.ts +++ b/src/webparts/requestPortal/shared/dataList.ts @@ -2,10 +2,13 @@ import '@pnp/sp/webs'; import '@pnp/sp/lists'; import '@pnp/sp/items'; import { IList } from '@pnp/sp/lists'; +import { log } from '@tnsu/monitoring-web'; import { getSP } from './pnp/pnpjs'; import { buildRequestUniqueCode } from './requestCode'; import { dataListTitle } from './listTitles'; import { RequestTypeId } from '../models/types'; +import { REQUEST_TYPES } from '../data/requests'; +import { isSubmittedStatus } from './approval/models/statuses'; /** * Список данных, в который фактически пишем: listId (конкретная партиция — @@ -50,6 +53,7 @@ export async function saveRequestItem( if (existingId) { await list.items.getById(existingId).update(item); + logSaved(formId, existingId, undefined, item, false); return { id: existingId }; } @@ -58,5 +62,34 @@ export async function saveRequestItem( // Код известен только после создания элемента (нужен его Id). const code = buildRequestUniqueCode(codePrefix, id, new Date()); await list.items.getById(id).update({ RequestUniqueCode: code }); + logSaved(formId, id, code, item, true); return { id, code }; } + +/** + * События для мониторинга: какая заявка создана и отправлена на согласование (пользователь — из токена). + * В тексте — вид, код и название заявки, чтобы её было видно прямо в списке логов. Решения согласующих + * пишет approverDecision.service.ts. + */ +function logSaved( + formId: RequestTypeId, + id: number, + code: string | undefined, + item: Record, + created: boolean +): void { + const typeTitle = REQUEST_TYPES.find((t) => t.id === formId)?.title ?? formId; + const title = typeof item.Title === 'string' ? item.Title : ''; + const status = typeof item.RequestStatus === 'string' ? item.RequestStatus : ''; + const what = [typeTitle, code, title].filter(Boolean).join(' · '); + const attributes = { + 'request.type': formId, + 'request.typeTitle': typeTitle, + 'request.id': id, + 'request.code': code ?? '', + 'request.title': title, + 'request.status': status, + }; + if (created) log.info(`Создана заявка: ${what}`, attributes); + if (isSubmittedStatus(status)) log.info(`Заявка отправлена на согласование: ${what}`, attributes); +}