UBERF-7690: Trigger improvements (#6340)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2024-08-19 12:41:31 +07:00 committed by GitHub
parent d26a03f814
commit c01a274cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
41 changed files with 868 additions and 699 deletions

2
.vscode/launch.json vendored
View File

@ -77,7 +77,7 @@
"ACCOUNT_PORT": "3000",
"FRONT_URL": "http://localhost:8080",
"outputCapture": "std",
"SES_URL": "http://localhost:8091",
"SES_URL": "",
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost"

View File

@ -1377,8 +1377,8 @@ dependencies:
specifier: ^1.20.2
version: 1.20.2
browserslist:
specifier: 4.21.5
version: 4.21.5
specifier: ^4.23.3
version: 4.23.3
bson:
specifier: ^6.8.0
version: 6.8.0
@ -1815,8 +1815,8 @@ dependencies:
specifier: ^5.3.3
version: 5.3.3
update-browserslist-db:
specifier: ~1.0.11
version: 1.0.13(browserslist@4.21.5)
specifier: ^1.1.0
version: 1.1.0(browserslist@4.23.3)
url-loader:
specifier: ~4.1.1
version: 4.1.1(file-loader@6.2.0)(webpack@5.90.3)
@ -2657,7 +2657,7 @@ packages:
dependencies:
'@babel/compat-data': 7.23.5
'@babel/helper-validator-option': 7.23.5
browserslist: 4.23.0
browserslist: 4.23.3
lru-cache: 5.1.1
semver: 6.3.1
dev: false
@ -10805,7 +10805,7 @@ packages:
peerDependencies:
postcss: ^8.1.0
dependencies:
browserslist: 4.23.0
browserslist: 4.23.3
caniuse-lite: 1.0.30001589
fraction.js: 4.3.7
normalize-range: 0.1.2
@ -11196,26 +11196,15 @@ packages:
pako: 0.2.9
dev: false
/browserslist@4.21.5:
resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==}
/browserslist@4.23.3:
resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001589
electron-to-chromium: 1.4.679
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.21.5)
dev: false
/browserslist@4.23.0:
resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
dependencies:
caniuse-lite: 1.0.30001589
electron-to-chromium: 1.4.679
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.23.0)
caniuse-lite: 1.0.30001651
electron-to-chromium: 1.5.11
node-releases: 2.0.18
update-browserslist-db: 1.1.0(browserslist@4.23.3)
dev: false
/bs-logger@0.2.6:
@ -11468,6 +11457,10 @@ packages:
resolution: {integrity: sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==}
dev: false
/caniuse-lite@1.0.30001651:
resolution: {integrity: sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==}
dev: false
/case-anything@2.1.13:
resolution: {integrity: sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==}
engines: {node: '>=12.13'}
@ -12019,7 +12012,7 @@ packages:
/core-js-compat@3.36.0:
resolution: {integrity: sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==}
dependencies:
browserslist: 4.23.0
browserslist: 4.23.3
dev: false
/core-js@3.36.0:
@ -13121,8 +13114,8 @@ packages:
type-fest: 2.19.0
dev: false
/electron-to-chromium@1.4.679:
resolution: {integrity: sha512-NhQMsz5k0d6m9z3qAxnsOR/ebal4NAGsrNVRwcDo4Kc/zQ7KdsTKZUxZoygHcVRb0QDW3waEDIcE3isZ79RP6g==}
/electron-to-chromium@1.5.11:
resolution: {integrity: sha512-R1CccCDYqndR25CaXFd6hp/u9RaaMcftMkphmvuepXr5b1vfLkRml6aWVeBhXJ7rbevHkKEMJtz8XqPf7ffmew==}
dev: false
/electron-updater@6.2.1:
@ -18443,8 +18436,8 @@ packages:
write-file-atomic: 1.3.4
dev: false
/node-releases@2.0.14:
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
/node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
dev: false
/nopt@1.0.10:
@ -19229,6 +19222,10 @@ packages:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: false
/picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
dev: false
/picomatch@2.3.1:
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
engines: {node: '>=8.6'}
@ -22785,26 +22782,15 @@ packages:
path-exists: 5.0.0
dev: false
/update-browserslist-db@1.0.13(browserslist@4.21.5):
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
/update-browserslist-db@1.1.0(browserslist@4.23.3):
resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
browserslist: 4.21.5
browserslist: 4.23.3
escalade: 3.1.2
picocolors: 1.0.0
dev: false
/update-browserslist-db@1.0.13(browserslist@4.23.0):
resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
hasBin: true
peerDependencies:
browserslist: '>= 4.21.0'
dependencies:
browserslist: 4.23.0
escalade: 3.1.2
picocolors: 1.0.0
picocolors: 1.0.1
dev: false
/uri-js@4.4.1:
@ -23282,7 +23268,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
browserslist: 4.23.0
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.4.1
@ -23323,7 +23309,7 @@ packages:
'@webassemblyjs/wasm-parser': 1.11.6
acorn: 8.11.3
acorn-import-assertions: 1.9.0(acorn@8.11.3)
browserslist: 4.23.0
browserslist: 4.23.3
chrome-trace-event: 1.0.3
enhanced-resolve: 5.15.0
es-module-lexer: 1.4.1
@ -24298,7 +24284,7 @@ packages:
dev: false
file:projects/attachment-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-ZEkxtUt4tXWqiEQOGDSFHr65Zw9PtVeCQJ5zS8zdDdpF+vh1BAaLW7OydhYafKYw5RheG50HOK/R6wPDcNd3Lw==, tarball: file:projects/attachment-resources.tgz}
resolution: {integrity: sha512-yEPWonMOvXhIk5+IaNQVsyw6wnfqdcB/4UsEh8BLCQwhpepBeVrNSXgS+mLspQaL9g15E81PHwyrWyF1OcqQOQ==, tarball: file:projects/attachment-resources.tgz}
id: file:projects/attachment-resources.tgz
name: '@rush-temp/attachment-resources'
version: 0.0.0
@ -25127,7 +25113,7 @@ packages:
dev: false
file:projects/contact-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-iVkm89qU3Sx3zlhJRYkBsMy03G536YvuMT1tc6WTLMzklcpqYyCOtklXRWUGGiF/+a56Dog4hu7ai10QUIajrA==, tarball: file:projects/contact-resources.tgz}
resolution: {integrity: sha512-Vlw7yfM/X0ViZ1mzmcJFVPAZk/2hp4l768lxErLF/sA2HKsRUCAvkjbzfHmImsK5e2WycFWOWwnUJu9p1irQWQ==, tarball: file:projects/contact-resources.tgz}
id: file:projects/contact-resources.tgz
name: '@rush-temp/contact-resources'
version: 0.0.0
@ -25320,7 +25306,7 @@ packages:
dev: false
file:projects/core.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-BgJt/g8bufJyz3tFDrdM6Y3GHHTzKs7DUVMylyP2kyPCyrq3TrBDOGajz6QJ36kObLH/KWWau/LRmB3ZWIFU7Q==, tarball: file:projects/core.tgz}
resolution: {integrity: sha512-qorGEpUYJYJMlHr0WkKpS8sUfQt3GQtF7xCdUSU5K9B/aRgAL6z0LGRJoUmn/kYHHV+r6DITWhOiP6zIHcKmdg==, tarball: file:projects/core.tgz}
id: file:projects/core.tgz
name: '@rush-temp/core'
version: 0.0.0
@ -25515,7 +25501,7 @@ packages:
dev: false
file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-j8onQxy51K29b/9q8g+lOG8e8dOFO2WpQQTdtnhP2vy2EcvIuSgckenQpg++0r1AtJ1G7FC7hr5QYNsrTPWkBA==, tarball: file:projects/desktop.tgz}
resolution: {integrity: sha512-+dbbvdgHJD76pLjOUSctj1tb9kC3hWa4CisFJxvSmlObAUII4NI+4EmE4u1x8OWaGEDqRCPCmrGFk1aSiPF5fQ==, tarball: file:projects/desktop.tgz}
id: file:projects/desktop.tgz
name: '@rush-temp/desktop'
version: 0.0.0
@ -25525,7 +25511,7 @@ packages:
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.3.3)
'@vercel/webpack-asset-relocator-loader': 1.7.4
autoprefixer: 10.4.17(postcss@8.4.35)
browserslist: 4.21.5
browserslist: 4.23.3
commander: 8.3.0
compression-webpack-plugin: 10.0.0(webpack@5.90.3)
copy-webpack-plugin: 11.0.0(webpack@5.90.3)
@ -25567,7 +25553,7 @@ packages:
ts-node: 10.9.2(@types/node@20.11.19)(typescript@5.3.3)
ts-node-dev: 2.0.0(@types/node@20.11.19)(typescript@5.3.3)
typescript: 5.3.3
update-browserslist-db: 1.0.13(browserslist@4.21.5)
update-browserslist-db: 1.1.0(browserslist@4.23.3)
webpack: 5.90.3(esbuild@0.20.1)(webpack-cli@5.1.4)
webpack-bundle-analyzer: 4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack-dev-server@4.15.1)(webpack@5.90.3)
@ -25812,7 +25798,7 @@ packages:
dev: false
file:projects/document-resources.tgz(@tiptap/pm@2.2.4)(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-paT/sxyRFUxsWTwzgk98+gGZZkT1a5EyFzSKE4UPyW9sqjM4IiyyIIrr57YZEpILDApfg51PF3qhjEtWV9kf1Q==, tarball: file:projects/document-resources.tgz}
resolution: {integrity: sha512-YK9gh+kYmMkJHyLvlo94mEr3QznTo4V7TMypOcnHxg4ZKTfRl1mrDNI/dSAW566wmbmm7Eln0WRboX30yRxPTA==, tarball: file:projects/document-resources.tgz}
id: file:projects/document-resources.tgz
name: '@rush-temp/document-resources'
version: 0.0.0
@ -26438,7 +26424,7 @@ packages:
dev: false
file:projects/hr-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-85fy55EMpfKKMiNAp0tW70KLnbppfEp9cDx93jYlteFoPpsi05GRZ2ZNYM9rLWaZ1wMYF1vsX42M0opDIX6KOQ==, tarball: file:projects/hr-resources.tgz}
resolution: {integrity: sha512-O0jhwaTcNjWwviO2wUzfwpH7AroMYBCAbgsRsLwBsDdW08fnp1LqKswgViDL/ZOsQ5ZvboCwRAOaR8VYVfojZg==, tarball: file:projects/hr-resources.tgz}
id: file:projects/hr-resources.tgz
name: '@rush-temp/hr-resources'
version: 0.0.0
@ -26776,7 +26762,7 @@ packages:
dev: false
file:projects/lead-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-DIL0rCz2r3nKYgE2fOrEdVSwLGq1Zz2MjsU1HvDxnIs4IE1Q4s8oNMBtvtunERIQ2fNnJHcE0XXijawtYilrjQ==, tarball: file:projects/lead-resources.tgz}
resolution: {integrity: sha512-TJVh5S1o+GvRWeeNWwXveGOpMtsQTR0n5RMGjK3kXsuAUYCQRPzgMh3noJxe2n4vvijK8IUNjAR9+AjeSPo5kw==, tarball: file:projects/lead-resources.tgz}
id: file:projects/lead-resources.tgz
name: '@rush-temp/lead-resources'
version: 0.0.0
@ -27182,7 +27168,7 @@ packages:
dev: false
file:projects/model-analytics-collector.tgz:
resolution: {integrity: sha512-OcczFEesVXSXUuJ/HpZ880slAZeV8Y/MhZArlxBlO90Tm3jO42cyQLAi0kO36ZAkO/gGVpAHUpuZT0QB70ovvw==, tarball: file:projects/model-analytics-collector.tgz}
resolution: {integrity: sha512-ht/R4aa2RvdT8odpbznVfFTed3dJGToS1q5tKHqJIF8BQhjNcVh9ZUmAJNnNZNTeD8J6yuKIPq7n/Io2CuTYdg==, tarball: file:projects/model-analytics-collector.tgz}
name: '@rush-temp/model-analytics-collector'
version: 0.0.0
dependencies:
@ -27391,7 +27377,7 @@ packages:
dev: false
file:projects/model-drive.tgz:
resolution: {integrity: sha512-V1Zaxs9HItgN9Zp8M2rdPjTUS1ZSkNaDUCeRcW5xuOdD0YY8xijRwZ+d2HF5G6xYgRwTpfqIw0O1cUBmqJAY8A==, tarball: file:projects/model-drive.tgz}
resolution: {integrity: sha512-JG62skvnh8n7bBwsriHH6TE71W/gVm287OVnmy4VRJVf/tDC0/sCKlg+75qvcSu3CmAjaYqnnJz5q1Lz9EH3xw==, tarball: file:projects/model-drive.tgz}
name: '@rush-temp/model-drive'
version: 0.0.0
dependencies:
@ -28389,7 +28375,7 @@ packages:
dev: false
file:projects/model-telegram.tgz:
resolution: {integrity: sha512-WjCLxLQvcXBwLNQQnCbClBIum033r+fwS3nwB7RmCTgERzxT7lqedPlCwsd1TUs7PF6C0Y6cp5O/Lrnn+7Z46g==, tarball: file:projects/model-telegram.tgz}
resolution: {integrity: sha512-uXXH0LQVtX+rJ0+XLPft4NLphBmtXs9ZY+g16hC9x7j5JToKwsYsBHR/rktANxYU3CyAHeoqA7S7CiYbGLrDVw==, tarball: file:projects/model-telegram.tgz}
name: '@rush-temp/model-telegram'
version: 0.0.0
dependencies:
@ -28674,7 +28660,7 @@ packages:
dev: false
file:projects/notification-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-D5FLFfyRUTqKy52JGk4IMGhROrTRq+YZ+34+y2awY2U7JBUbpjcxhMkFBMgr/9TrZhSTvZrw7VvL5FCruoEN4Q==, tarball: file:projects/notification-resources.tgz}
resolution: {integrity: sha512-xpvJbpFBrKB3RPG8SgT9+cJj2IkysBNcl4jQAV69ACUvnTQMh4RS9P31zpaO7CObYXrm9/ImRF0fXbH+AkiJsg==, tarball: file:projects/notification-resources.tgz}
id: file:projects/notification-resources.tgz
name: '@rush-temp/notification-resources'
version: 0.0.0
@ -29099,7 +29085,7 @@ packages:
dev: false
file:projects/pod-analytics-collector.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-Sx9mTOCsgDTcoC8h9VXeemVHerKLMDzdW4aJ08fdsF9UA35tLWmbDXw+fn3gPUNj2l+QT5ExEF9rV1Iez1FIbw==, tarball: file:projects/pod-analytics-collector.tgz}
resolution: {integrity: sha512-15hQZSiqnsgeZDg3n3AE2D2mx6xBToMB+2z+nDKLAKbaa+XAjMyj7J/Z9nLwfjs2EX6Z8x10JoPvs4au/SXxlA==, tarball: file:projects/pod-analytics-collector.tgz}
id: file:projects/pod-analytics-collector.tgz
name: '@rush-temp/pod-analytics-collector'
version: 0.0.0
@ -30043,7 +30029,7 @@ packages:
dev: false
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-U0i5q0YgmqM+29OoNKE5F7SYHLenVyjGDEus4MgN7CqMIC1n6n2gtztzRm2O6/j8hba5E6asgHgGF+ljESlbGw==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-lYEzH4pmYid/iB0WNGOZXt3y9nyDbs70r3jYZJ6qIQEyvQVqWXnAd8Rw+nqcEbI/QtTou81FrvAK2Fnp5Imeww==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -30051,7 +30037,7 @@ packages:
'@sentry/svelte': 7.101.1(svelte@4.2.12)
'@types/node': 20.11.19
autoprefixer: 10.4.17(postcss@8.4.35)
browserslist: 4.21.5
browserslist: 4.23.3
compression-webpack-plugin: 10.0.0(webpack@5.90.3)
cross-env: 7.0.3
css-loader: 5.2.7(webpack@5.90.3)
@ -30074,7 +30060,7 @@ packages:
svgo-loader: 3.0.3
ts-loader: 9.5.1(typescript@5.3.3)(webpack@5.90.3)
typescript: 5.3.3
update-browserslist-db: 1.0.13(browserslist@4.21.5)
update-browserslist-db: 1.1.0(browserslist@4.23.3)
webpack: 5.90.3(esbuild@0.20.1)(webpack-cli@5.1.4)
webpack-bundle-analyzer: 4.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
webpack-cli: 5.1.4(webpack-bundle-analyzer@4.10.1)(webpack-dev-server@4.15.1)(webpack@5.90.3)
@ -31700,7 +31686,7 @@ packages:
dev: false
file:projects/server-gmail-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-jc5tLhz7YJs4Nw0dwPgOpZnZA+wMstl6Vt3BuL5BOE8iMTeUTZP1fKJXf6pGVeZAGEaYzayHM9Jmw+S45/l9MQ==, tarball: file:projects/server-gmail-resources.tgz}
resolution: {integrity: sha512-V9M3+rquV4MPbPtmwWPdekguzA5WKJRDvU7xtpYKQSJ7J7hkLPKNah4GtSjin0opg6lTgtoAJUQaYLKheeCCbA==, tarball: file:projects/server-gmail-resources.tgz}
id: file:projects/server-gmail-resources.tgz
name: '@rush-temp/server-gmail-resources'
version: 0.0.0
@ -33412,7 +33398,7 @@ packages:
dev: false
file:projects/tags-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-LuFrjyWlBTgRhkuiQvvLLtUh4kw9sRrUH7kZaJYjXeHh6J4PDfbrLl1cNLCTKXZiCP4X3GIeMz0eK0eUqzdeVg==, tarball: file:projects/tags-resources.tgz}
resolution: {integrity: sha512-5ldBcfo7wMt0BzAf8fA0FMc2ICyqweNug+4KPSisBcFJRvqUysw+x97qpqBosCl2BI2ro/MTnUEwYpN/3m5oVQ==, tarball: file:projects/tags-resources.tgz}
id: file:projects/tags-resources.tgz
name: '@rush-temp/tags-resources'
version: 0.0.0
@ -33520,7 +33506,7 @@ packages:
dev: false
file:projects/task-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-BXlz1rd2Pp3cTgW0USdUS2Ha+hDUYnwAvT9uzlZY1Nn0zOxt6rSC/Qb4e6N1n0gNxpI63ZZl+zyhTkR100yL+g==, tarball: file:projects/task-resources.tgz}
resolution: {integrity: sha512-GxlxQIm65Qvb6rIh4tqIgtNxEhom+Q8bxQeclK4J+elxv3/YoQdQrS6pKiX+9ueY/TSz+ulPbgpNfk6y1cgSUA==, tarball: file:projects/task-resources.tgz}
id: file:projects/task-resources.tgz
name: '@rush-temp/task-resources'
version: 0.0.0
@ -34127,7 +34113,7 @@ packages:
dev: false
file:projects/time-resources.tgz(@tiptap/core@2.2.4)(@tiptap/pm@2.2.4)(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-YHDZu9vfplV3yHTWsp3czuSbb59RRHGIYpYvMZKVG7mnk52y121rzcxiu1W6j8vh69xFXgxMowO6TQEwTEFfKQ==, tarball: file:projects/time-resources.tgz}
resolution: {integrity: sha512-S0T2DEhL7CMoryh+gjH7KorQwrUvwu96LH2VUaSZTADN69oSN9XQZb05ab1zhn5MbwNoRr2aLnMDqRmhuxT1Ag==, tarball: file:projects/time-resources.tgz}
id: file:projects/time-resources.tgz
name: '@rush-temp/time-resources'
version: 0.0.0

View File

@ -42,8 +42,8 @@
"compression-webpack-plugin": "^10.0.0",
"html-webpack-plugin": "^5.5.0",
"fork-ts-checker-webpack-plugin": "~7.3.0",
"update-browserslist-db": "~1.0.11",
"browserslist": "4.21.5",
"update-browserslist-db": "^1.1.0",
"browserslist": "^4.23.3",
"typescript": "^5.3.3",
"ts-node": "^10.8.0",
"ts-node-dev": "^2.0.0",

View File

@ -62,8 +62,8 @@ services:
- LAST_NAME_FIRST=true
- ACCOUNTS_URL=http://localhost:3000
- BRANDING_PATH=/var/cfg/branding.json
- INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
- INIT_WORKSPACE=onboarding
# - INIT_SCRIPT_URL=https://raw.githubusercontent.com/hcengineering/init/main/script.yaml
# - INIT_WORKSPACE=onboarding
restart: unless-stopped
collaborator:
image: hardcoreeng/collaborator

View File

@ -43,8 +43,8 @@
"compression-webpack-plugin": "^10.0.0",
"html-webpack-plugin": "^5.5.0",
"fork-ts-checker-webpack-plugin": "~7.3.0",
"update-browserslist-db": "~1.0.11",
"browserslist": "4.21.5",
"update-browserslist-db": "^1.1.0",
"browserslist": "^4.23.3",
"esbuild": "^0.20.0",
"esbuild-loader": "^4.0.3",
"typescript": "^5.3.3",

View File

@ -37,8 +37,8 @@ async function createEmployeeEmail (client: TxOperations): Promise<void> {
).filter((it) => it.provider === contact.channelProvider.Email)
const channelsMap = new Map(channels.map((p) => [p.attachedTo, p]))
for (const employee of employees) {
const acc = await client.findOne(contact.class.PersonAccount, { person: employee._id })
if (acc === undefined) continue
const acc = client.getModel().getAccountByPersonId(employee._id)
if (acc.length === 0) continue
const current = channelsMap.get(employee._id)
if (current === undefined) {
await client.addCollection(
@ -49,13 +49,13 @@ async function createEmployeeEmail (client: TxOperations): Promise<void> {
'channels',
{
provider: contact.channelProvider.Email,
value: acc.email.trim()
value: acc[0].email.trim()
},
undefined,
employee.modifiedOn
)
} else if (current.value !== acc.email.trim()) {
await client.update(current, { value: acc.email.trim() }, false, current.modifiedOn)
} else if (current.value !== acc[0].email.trim()) {
await client.update(current, { value: acc[0].email.trim() }, false, current.modifiedOn)
}
}
}

View File

@ -19,6 +19,7 @@ import core from '@hcengineering/core/src/component'
import serverActivity from '@hcengineering/server-activity'
import serverNotification from '@hcengineering/server-notification'
import activity from '@hcengineering/activity'
import notification from '@hcengineering/notification'
export { activityServerOperation } from './migration'
export { serverActivityId } from '@hcengineering/server-activity'
@ -43,6 +44,9 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ActivityMessagesHandler,
txMatch: {
'tx.objectClass': { $nin: [activity.class.ActivityMessage, notification.class.DocNotifyContext] }
},
isAsync: true
})
@ -52,6 +56,10 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ReferenceTrigger,
txMatch: {
'tx.objectClass': { $ne: activity.class.ActivityMessage },
objectClass: { $nin: [notification.class.InboxNotification, notification.class.DocNotifyContext] }
},
isAsync: true
})
}

View File

@ -49,6 +49,6 @@ export function createModel (builder: Builder): void {
})
builder.mixin(gmail.ids.EmailNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, {
func: serverGmail.function.IsIncomingMessage
func: serverGmail.function.IsIncomingMessageTypeMatch
})
}

View File

@ -39,7 +39,7 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserEmployeeInFieldValue
func: serverNotification.function.IsUserEmployeeInFieldValueTypeMatch
}
)

View File

@ -16,21 +16,22 @@
import { type Builder, Mixin, Model } from '@hcengineering/model'
import core, { type Account, type Doc, type Ref, type Tx } from '@hcengineering/core'
import core, { type Ref } from '@hcengineering/core'
import { TClass, TDoc } from '@hcengineering/model-core'
import { TNotificationType } from '@hcengineering/model-notification'
import notification, { type NotificationProvider, type NotificationType } from '@hcengineering/notification'
import notification, { type NotificationProvider } from '@hcengineering/notification'
import { type Resource } from '@hcengineering/platform'
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
import serverCore from '@hcengineering/server-core'
import serverNotification, {
type HTMLPresenter,
type NotificationContentProvider,
type NotificationPresenter,
type NotificationProviderFunc,
type NotificationProviderResources,
type Presenter,
type TextPresenter,
type TypeMatch,
type NotificationContentProvider,
type NotificationProviderResources,
type NotificationProviderFunc
type TypeMatchFunc
} from '@hcengineering/server-notification'
export { serverNotificationId } from '@hcengineering/server-notification'
@ -52,9 +53,7 @@ export class TNotificationPresenter extends TClass implements NotificationPresen
@Mixin(serverNotification.mixin.TypeMatch, notification.class.NotificationType)
export class TTypeMatch extends TNotificationType implements TypeMatch {
func!: Resource<
(tx: Tx, doc: Doc, user: Ref<Account>, type: NotificationType, control: TriggerControl) => Promise<boolean>
>
func!: TypeMatchFunc
}
@Model(serverNotification.class.NotificationProviderResources, core.class.Doc)

View File

@ -93,7 +93,7 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserEmployeeInFieldValue
func: serverNotification.function.IsUserEmployeeInFieldValueTypeMatch
}
)
}

View File

@ -56,7 +56,7 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverTelegram.function.IsIncomingMessage
func: serverTelegram.function.IsIncomingMessageTypeMatch
}
)

View File

@ -77,7 +77,7 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserEmployeeInFieldValue
func: serverNotification.function.IsUserEmployeeInFieldValueTypeMatch
}
)
}

View File

@ -187,7 +187,7 @@ export function genMinModel (): TxCUD<Doc>[] {
txes.push(
createClass(core.class.Blob, {
label: 'Blob' as IntlString,
extends: core.class.Blob,
extends: core.class.Doc,
kind: ClassifierKind.CLASS
})
)

View File

@ -440,6 +440,8 @@ export interface Permission extends Doc {
export interface Account extends Doc {
email: string
role: AccountRole
person?: Ref<Doc>
}
/**

View File

@ -30,7 +30,7 @@ export class Hierarchy {
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
private readonly attributesById = new Map<Ref<AnyAttribute>, AnyAttribute>()
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
private readonly ancestors = new Map<Ref<Classifier>, Ref<Classifier>[]>()
private readonly ancestors = new Map<Ref<Classifier>, { ordered: Ref<Classifier>[], set: Set<Ref<Classifier>> }>()
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
private readonly classifierProperties = new Map<Ref<Classifier>, Record<string, any>>()
@ -166,7 +166,7 @@ export class Hierarchy {
if (result === undefined) {
throw new Error('ancestors not found: ' + _class)
}
return result
return result.ordered
}
getClass<T extends Obj = Obj>(_class: Ref<Class<T>>): Class<T> {
@ -301,17 +301,7 @@ export class Hierarchy {
* It will iterate over parents.
*/
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
let cl: Ref<Class<T>> | undefined = _class
while (cl !== undefined) {
if (cl === from) return true
const cll = this.classifiers.get(cl)
if (cll === undefined || this.isInterface(cll)) {
return false
}
cl = (cll as Class<T>).extends
}
return false
return this.ancestors.get(_class)?.set?.has(from) ?? false
}
/**
@ -398,15 +388,19 @@ export class Hierarchy {
const list = this.ancestors.get(_class)
if (list === undefined) {
if (add) {
this.ancestors.set(_class, [classifier])
this.ancestors.set(_class, { ordered: [classifier], set: new Set([classifier]) })
}
} else {
if (add) {
addIf(list, classifier)
if (!list.set.has(classifier)) {
list.ordered.push(classifier)
list.set.add(classifier)
}
} else {
const pos = list.indexOf(classifier)
const pos = list.ordered.indexOf(classifier)
if (pos !== -1) {
list.splice(pos, 1)
list.ordered.splice(pos, 1)
list.set.delete(classifier)
}
}
}

View File

@ -15,7 +15,7 @@
import { PlatformError, Severity, Status } from '@hcengineering/platform'
import { Lookup, MeasureContext, ReverseLookups, getObjectValue } from '.'
import type { AttachedDoc, Class, Doc, Ref } from './classes'
import type { Account, AttachedDoc, Class, Doc, Ref } from './classes'
import core from './component'
import { Hierarchy } from './hierarchy'
import { checkMixinKey, matchQuery, resultSort } from './query'
@ -31,6 +31,8 @@ export abstract class MemDb extends TxProcessor implements Storage {
private readonly objectsByClass = new Map<Ref<Class<Doc>>, Map<Ref<Doc>, Doc>>()
private readonly objectById = new Map<Ref<Doc>, Doc>()
private readonly accountByPersonId = new Map<Ref<Doc>, Account[]>()
constructor (protected readonly hierarchy: Hierarchy) {
super()
}
@ -75,6 +77,10 @@ export abstract class MemDb extends TxProcessor implements Storage {
return doc as T
}
getAccountByPersonId (ref: Ref<Doc>): Account[] {
return this.accountByPersonId.get(ref) ?? []
}
findObject<T extends Doc>(_id: Ref<T>): T | undefined {
const doc = this.objectById.get(_id)
return doc as T
@ -214,6 +220,12 @@ export abstract class MemDb extends TxProcessor implements Storage {
const arr = this.getObjectsByClass(_class)
arr.set(doc._id, doc)
})
if (this.hierarchy.isDerived(doc._class, core.class.Account)) {
const account = doc as Account
if (account.person !== undefined) {
this.accountByPersonId.set(account.person, [...(this.accountByPersonId.get(account.person) ?? []), account])
}
}
this.objectById.set(doc._id, doc)
}
@ -226,6 +238,37 @@ export abstract class MemDb extends TxProcessor implements Storage {
this.hierarchy.getAncestors(doc._class).forEach((_class) => {
this.cleanObjectByClass(_class, _id)
})
if (this.hierarchy.isDerived(doc._class, core.class.Account)) {
const account = doc as Account
if (account.person !== undefined) {
const acc = this.accountByPersonId.get(account.person) ?? []
this.accountByPersonId.set(
account.person,
acc.filter((it) => it._id !== _id)
)
}
}
}
updateDoc (_id: Ref<Doc>, doc: Doc, update: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): void {
if (
this.hierarchy.isDerived(doc._class, core.class.Account) &&
update._class === core.class.TxUpdateDoc &&
(update as TxUpdateDoc<Account>).operations.person !== undefined
) {
const account = doc as Account
if (account.person !== undefined) {
const acc = this.accountByPersonId.get(account.person) ?? []
this.accountByPersonId.set(
account.person,
acc.filter((it) => it._id !== _id)
)
}
const newPerson = (update as TxUpdateDoc<Account>).operations.person
if (newPerson !== undefined) {
this.accountByPersonId.set(newPerson, [...(this.accountByPersonId.get(newPerson) ?? []), account])
}
}
}
}
@ -297,6 +340,7 @@ export class ModelDb extends MemDb {
const cud = tx as TxUpdateDoc<Doc>
const doc = this.findObject(cud.objectId)
if (doc !== undefined) {
this.updateDoc(cud.objectId, doc, cud)
TxProcessor.updateDoc2Doc(doc, cud)
} else {
ctx.error('no document found, failed to apply model transaction, skipping', {
@ -320,9 +364,10 @@ export class ModelDb extends MemDb {
break
case core.class.TxMixin: {
const mix = tx as TxMixin<Doc, Doc>
const obj = this.findObject(mix.objectId)
if (obj !== undefined) {
TxProcessor.updateMixin4Doc(obj, mix)
const doc = this.findObject(mix.objectId)
if (doc !== undefined) {
this.updateDoc(mix.objectId, doc, mix)
TxProcessor.updateMixin4Doc(doc, mix)
} else {
ctx.error('no document found, failed to apply model transaction, skipping', {
_id: tx._id,
@ -339,6 +384,7 @@ export class ModelDb extends MemDb {
protected async txUpdateDoc (tx: TxUpdateDoc<Doc>): Promise<TxResult> {
try {
const doc = this.getObject(tx.objectId) as any
this.updateDoc(tx.objectId, doc, tx)
TxProcessor.updateDoc2Doc(doc, tx)
return tx.retrieve === true ? { object: doc } : {}
} catch (err: any) {}
@ -354,8 +400,9 @@ export class ModelDb extends MemDb {
// TODO: process ancessor mixins
protected async txMixin (tx: TxMixin<Doc, Doc>): Promise<TxResult> {
const obj = this.getObject(tx.objectId) as any
TxProcessor.updateMixin4Doc(obj, tx)
const doc = this.getObject(tx.objectId) as any
this.updateDoc(tx.objectId, doc, tx)
TxProcessor.updateMixin4Doc(doc, tx)
return {}
}
}

View File

@ -18,10 +18,10 @@ import { Editor, Extensions, getSchema } from '@tiptap/core'
import { generateHTML, generateJSON } from '@tiptap/html'
import { Node as ProseMirrorNode, Schema } from '@tiptap/pm/model'
import { defaultExtensions } from '../extensions'
import { MarkupMark, MarkupNode, MarkupNodeType, emptyMarkupNode } from './model'
import { nodeDoc, nodeParagraph, nodeText } from './dsl'
import { deepEqual } from 'fast-equals'
import { defaultExtensions } from '../extensions'
import { nodeDoc, nodeParagraph, nodeText } from './dsl'
import { MarkupMark, MarkupNode, MarkupNodeType, emptyMarkupNode } from './model'
/** @public */
export const EmptyMarkup: Markup = jsonToMarkup(emptyMarkupNode())
@ -226,9 +226,11 @@ export function pmNodeToHTML (node: ProseMirrorNode, extensions?: Extensions): s
const ELLIPSIS_CHAR = '…'
const WHITESPACE = ' '
const defaultSchema = getSchema(defaultExtensions)
/** @public */
export function stripTags (markup: Markup, textLimit = 0, extensions: Extensions | undefined = undefined): string {
const schema = getSchema(extensions ?? defaultExtensions)
const schema = extensions === undefined ? defaultSchema : getSchema(extensions)
const parsed = markupToPmNode(markup, schema)
const textParts: string[] = []

View File

@ -56,13 +56,15 @@ export function yDocContentToNode (
return yDocToNode(ydoc, field, schema, extensions)
}
const defaultSchema = getSchema(defaultExtensions)
/**
* Get ProseMirror node from Y.Doc
*
* @public
*/
export function yDocToNode (ydoc: YDoc, field?: string, schema?: Schema, extensions?: Extensions): Node {
schema ??= getSchema(extensions ?? defaultExtensions)
schema ??= extensions === undefined ? defaultSchema : getSchema(extensions ?? defaultExtensions)
try {
const body = yDocToProsemirrorJSON(ydoc, field)
@ -79,7 +81,7 @@ export function yDocToNode (ydoc: YDoc, field?: string, schema?: Schema, extensi
* @public
*/
export function yDocContentToNodes (content: ArrayBuffer, schema?: Schema, extensions?: Extensions): Node[] {
schema ??= getSchema(extensions ?? defaultExtensions)
schema ??= extensions === undefined ? defaultSchema : getSchema(extensions ?? defaultExtensions)
const nodes: Node[] = []
@ -112,7 +114,7 @@ export function updateYDocContent (
schema?: Schema,
extensions?: Extensions
): YDoc | undefined {
schema ??= getSchema(extensions ?? defaultExtensions)
schema ??= extensions === undefined ? defaultSchema : getSchema(extensions ?? defaultExtensions)
try {
const ydoc = new YDoc()
@ -140,7 +142,7 @@ export function updateYDocContent (
* @public
*/
export function YDocFromContent (content: MarkupNode, field: string, schema?: Schema, extensions?: Extensions): YDoc {
schema ??= getSchema(extensions ?? defaultExtensions)
schema ??= extensions === undefined ? defaultSchema : getSchema(extensions ?? defaultExtensions)
const res = new YDoc({ gc: false })

View File

@ -268,8 +268,8 @@ async function kickEmployee (doc: Person): Promise<void> {
const client = getClient()
const employee = client.getHierarchy().as(doc, contact.mixin.Employee)
const email = await client.findOne(contact.class.PersonAccount, { person: doc._id })
if (email === undefined) {
const accounts = client.getModel().getAccountByPersonId(doc._id)
if (accounts.length === 0) {
await client.update(employee, { active: false })
} else {
showPopup(
@ -282,9 +282,12 @@ async function kickEmployee (doc: Person): Promise<void> {
(res?: boolean) => {
if (res === true) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
getResource(login.function.LeaveWorkspace).then(async (f) => {
await f(email.email)
})
const p = getResource(login.function.LeaveWorkspace)
for (const i of accounts) {
void p.then(async (f) => {
await f(i.email)
})
}
}
}
)

View File

@ -320,7 +320,7 @@ function fillStores (): void {
const accountPersonQuery = createQuery(true)
const query = createQuery(true)
query.query(contact.mixin.Employee, { [contact.mixin.Employee + '.active']: { $in: [true, false] } }, (res) => {
query.query(contact.mixin.Employee, { active: { $in: [true, false] } }, (res) => {
employeesStore.set(res)
employeeByIdStore.set(toIdMap(res))
})

View File

@ -13,7 +13,9 @@
// limitations under the License.
//
import activity, { ActivityMessage, ActivityReference, UserMentionInfo } from '@hcengineering/activity'
import { loadCollaborativeDoc, yDocToBuffer } from '@hcengineering/collaboration'
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import core, {
Account,
AttachedDoc,
@ -34,41 +36,30 @@ import core, {
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
Type
Type,
type MeasureContext
} from '@hcengineering/core'
import notification, { CommonInboxNotification, MentionInboxNotification } from '@hcengineering/notification'
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
import {
applyNotificationProviders,
getCommonNotificationTxes,
getNotificationContent,
getNotificationProviderControl,
getPushCollaboratorTx,
isShouldNotifyTx,
NotifyResult,
shouldNotifyCommon,
toReceiverInfo,
type NotificationProviderControl
} from '@hcengineering/server-notification-resources'
import {
areEqualJson,
extractReferences,
markupToPmNode,
pmNodeToMarkup,
yDocContentToNodes,
areEqualJson
yDocContentToNodes
} from '@hcengineering/text'
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
import activity, { ActivityMessage, ActivityReference, UserMentionInfo } from '@hcengineering/activity'
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import {
getCommonNotificationTxes,
getPushCollaboratorTx,
shouldNotifyCommon,
isShouldNotifyTx,
NotifyResult,
applyNotificationProviders,
getNotificationContent,
toReceiverInfo
} from '@hcengineering/server-notification-resources'
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
return (
await control.modelDb.findAll(
contact.class.PersonAccount,
{
person
},
{ limit: 1 }
)
)[0]
}
export function isDocMentioned (doc: Ref<Doc>, content: string | Buffer): boolean {
const references = []
@ -93,20 +84,22 @@ export function isDocMentioned (doc: Ref<Doc>, content: string | Buffer): boolea
}
export async function getPersonNotificationTxes (
ctx: MeasureContext,
reference: Data<ActivityReference>,
control: TriggerControl,
senderId: Ref<Account>,
space: Ref<Space>,
originTx: TxCUD<Doc>
originTx: TxCUD<Doc>,
notificationControl: NotificationProviderControl
): Promise<Tx[]> {
const receiverPersonId = reference.attachedTo as Ref<Person>
const receiver = await getPersonAccount(receiverPersonId, control)
const receiver = control.modelDb.getAccountByPersonId(receiverPersonId) as PersonAccount[]
if (receiver === undefined) {
if (receiver.length === 0) {
return []
}
if (receiver._id === senderId) {
if (receiver.some((it) => it._id === senderId)) {
return []
}
@ -120,14 +113,21 @@ export async function getPersonNotificationTxes (
const doc = (await control.findAll(reference.srcDocClass, { _id: reference.srcDocId }))[0]
const receiverPerson = (
await control.findAll(contact.mixin.Employee, { _id: receiver.person as Ref<Employee>, active: true }, { limit: 1 })
await control.findAll(
contact.mixin.Employee,
{ _id: receiverPersonId as Ref<Employee>, active: true },
{ limit: 1 }
)
)[0]
if (receiverPerson === undefined) return res
const receiverSpace = (await control.findAll(contact.class.PersonSpace, { person: receiver.person }, { limit: 1 }))[0]
const receiverSpace = (
await control.findAll(contact.class.PersonSpace, { person: receiverPersonId }, { limit: 1 })
)[0]
if (receiverSpace === undefined) return res
const collaboratorsTx = await getCollaboratorsTxes(reference, control, receiver, doc)
// TODO: Do we need for all or just one?
const collaboratorsTx = await getCollaboratorsTxes(reference, control, receiver[0], doc)
res.push(...collaboratorsTx)
@ -159,13 +159,13 @@ export async function getPersonNotificationTxes (
})
)
}
// TODO: Select a proper reciever
const data: Omit<Data<MentionInboxNotification>, 'docNotifyContext'> = {
header: activity.string.MentionedYouIn,
messageHtml: reference.message,
mentionedIn: reference.attachedDocId ?? reference.srcDocId,
mentionedInClass: reference.attachedDocClass ?? reference.srcDocClass,
user: receiver._id,
user: receiver[0]._id,
isViewed: false,
archived: false
}
@ -180,8 +180,8 @@ export async function getPersonNotificationTxes (
: undefined
const receiverInfo = toReceiverInfo(control.hierarchy, {
_id: receiver._id,
account: receiver,
_id: receiver[0]._id,
account: receiver[0],
person: receiverPerson,
space: receiverSpace._id
})
@ -193,8 +193,20 @@ export async function getPersonNotificationTxes (
person: senderPerson
}
const notifyResult = await shouldNotifyCommon(control, receiver._id, notification.ids.MentionCommonNotificationType)
const messageNotifyResult = await getMessageNotifyResult(reference, receiver, control, originTx, doc)
const notifyResult = await shouldNotifyCommon(
control,
receiver.map((it) => it._id),
notification.ids.MentionCommonNotificationType,
notificationControl
)
const messageNotifyResult = await getMessageNotifyResult(
reference,
receiver,
control,
originTx,
doc,
notificationControl
)
for (const [provider] of messageNotifyResult.entries()) {
if (notifyResult.has(provider)) {
@ -204,6 +216,7 @@ export async function getPersonNotificationTxes (
if (notifyResult.has(notification.providers.InboxNotificationProvider)) {
const txes = await getCommonNotificationTxes(
ctx,
control,
doc,
data,
@ -221,7 +234,7 @@ export async function getPersonNotificationTxes (
const context = (
await control.findAll(
notification.class.DocNotifyContext,
{ objectId: reference.srcDocId, user: receiver._id },
{ objectId: reference.srcDocId, user: { $in: receiver.map((it) => it._id) } },
{ projection: { _id: 1 } }
)
)[0]
@ -255,21 +268,24 @@ export async function getPersonNotificationTxes (
}
async function checkSpace (
user: PersonAccount,
users: PersonAccount[],
spaceId: Ref<Space>,
control: TriggerControl,
res: Tx[]
): Promise<boolean> {
const space = (await control.findAll<Space>(core.class.Space, { _id: spaceId }, { limit: 1 }))[0]
const isMember = space.members.includes(user._id)
const toAdd = users.filter((user) => !space.members.includes(user._id))
const isMember = toAdd.length === 0
if (space.private) {
return isMember
}
if (!isMember) {
res.push(
control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, { $push: { members: user._id } })
)
for (const user of toAdd) {
res.push(
control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, { $push: { members: user._id } })
)
}
}
return true
@ -327,10 +343,11 @@ async function getCollaboratorsTxes (
async function getMessageNotifyResult (
reference: Data<ActivityReference>,
account: PersonAccount,
account: PersonAccount[],
control: TriggerControl,
originTx: TxCUD<Doc>,
doc: Doc
doc: Doc,
notificationControl: NotificationProviderControl
): Promise<NotifyResult> {
const { hierarchy } = control
const tx = TxProcessor.extractTx(originTx) as TxCUD<Doc>
@ -345,7 +362,7 @@ async function getMessageNotifyResult (
const mixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
if (mixin === undefined || !mixin.collaborators.includes(account._id)) {
if (mixin === undefined || !account.some((account) => mixin.collaborators.includes(account._id))) {
return new Map()
}
@ -353,7 +370,7 @@ async function getMessageNotifyResult (
return new Map()
}
return await isShouldNotifyTx(control, tx, originTx, doc, account, false, false, undefined)
return await isShouldNotifyTx(control, tx, originTx, doc, account, false, false, notificationControl, undefined)
}
function isMarkupType (type: Ref<Class<Type<any>>>): boolean {
@ -365,6 +382,7 @@ function isCollaborativeType (type: Ref<Class<Type<any>>>): boolean {
}
async function getCreateReferencesTxes (
ctx: MeasureContext,
control: TriggerControl,
storage: StorageAdapter,
txFactory: TxFactory,
@ -410,10 +428,11 @@ async function getCreateReferencesTxes (
? (srcDocId as Ref<Space>)
: srcDocSpace
return await getReferencesTxes(control, txFactory, refs, refSpace, [], [], originTx)
return await getReferencesTxes(ctx, control, txFactory, refs, refSpace, [], [], originTx)
}
async function getUpdateReferencesTxes (
ctx: MeasureContext,
control: TriggerControl,
storage: StorageAdapter,
txFactory: TxFactory,
@ -474,7 +493,7 @@ async function getUpdateReferencesTxes (
? (srcDocId as Ref<Space>)
: srcDocSpace
return await getReferencesTxes(control, txFactory, references, refSpace, current, userMentions, originTx)
return await getReferencesTxes(ctx, control, txFactory, references, refSpace, current, userMentions, originTx)
}
return []
@ -519,14 +538,16 @@ export function getReferencesData (
}
async function createReferenceTxes (
ctx: MeasureContext,
control: TriggerControl,
txFactory: TxFactory,
ref: Data<ActivityReference>,
space: Ref<Space>,
originTx: TxCUD<Doc>
originTx: TxCUD<Doc>,
notificationControl: NotificationProviderControl
): Promise<Tx[]> {
if (control.hierarchy.isDerived(ref.attachedToClass, contact.class.Person)) {
return await getPersonNotificationTxes(ref, control, txFactory.account, space, originTx)
return await getPersonNotificationTxes(ctx, ref, control, txFactory.account, space, originTx, notificationControl)
}
const refTx = control.txFactory.createTxCreateDoc(activity.class.ActivityReference, space, ref)
@ -536,6 +557,7 @@ async function createReferenceTxes (
}
async function getReferencesTxes (
ctx: MeasureContext,
control: TriggerControl,
txFactory: TxFactory,
references: Data<ActivityReference>[],
@ -568,6 +590,8 @@ async function getReferencesTxes (
}
}
const notificationControl = await getNotificationProviderControl(ctx, control)
for (const mention of mentions) {
const refIndex = references.findIndex(
(r) => mention.user === r.attachedTo && mention.attachedTo === r.attachedDocId
@ -588,7 +612,7 @@ async function getReferencesTxes (
// Add missing references
for (const ref of references) {
txes.push(...(await createReferenceTxes(control, txFactory, ref, space, originTx)))
txes.push(...(await createReferenceTxes(ctx, control, txFactory, ref, space, originTx, notificationControl)))
}
return txes
@ -659,6 +683,7 @@ async function ActivityReferenceCreate (tx: TxCUD<Doc>, etx: TxCUD<Doc>, control
const targetTx = guessReferenceTx(control.hierarchy, tx)
const txes: Tx[] = await getCreateReferencesTxes(
control.ctx,
control,
control.storageAdapter,
txFactory,
@ -706,6 +731,7 @@ async function ActivityReferenceUpdate (tx: TxCUD<Doc>, etx: TxCUD<Doc>, control
const targetTx = guessReferenceTx(control.hierarchy, tx)
const txes: Tx[] = await getUpdateReferencesTxes(
control.ctx,
control,
control.storageAdapter,
txFactory,

View File

@ -22,7 +22,7 @@ import chunter, {
ChunterSpace,
ThreadMessage
} from '@hcengineering/chunter'
import contact, { Person, PersonAccount } from '@hcengineering/contact'
import { Person, PersonAccount } from '@hcengineering/contact'
import core, {
Account,
AttachedDoc,
@ -56,7 +56,7 @@ import {
import { markupToHTML, markupToText, stripTags } from '@hcengineering/text'
import { workbenchId } from '@hcengineering/workbench'
import { NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
import { getPersonAccountById, NOTIFICATION_BODY_SIZE } from '@hcengineering/server-notification'
import { encodeObjectURI } from '@hcengineering/view'
const updateChatInfoDelay = 12 * 60 * 60 * 1000 // 12 hours
@ -121,13 +121,8 @@ export async function CommentRemove (
}
async function OnThreadMessageCreated (originTx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
const tx = TxProcessor.extractTx(originTx) as TxCreateDoc<ThreadMessage>
if (tx._class !== core.class.TxCreateDoc || !hierarchy.isDerived(tx.objectClass, chunter.class.ThreadMessage)) {
return []
}
const threadMessage = TxProcessor.createDoc2Doc(tx)
const message = (await control.findAll(activity.class.ActivityMessage, { _id: threadMessage.attachedTo }))[0]
@ -166,13 +161,6 @@ async function OnChatMessageCreated (tx: TxCUD<Doc>, control: TriggerControl): P
const hierarchy = control.hierarchy
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
if (
actualTx._class !== core.class.TxCreateDoc ||
!hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
) {
return []
}
const message = TxProcessor.createDoc2Doc(actualTx)
const mixin = hierarchy.classHierarchyMixin(message.attachedToClass, notification.mixin.ClassCollaborators)
@ -244,13 +232,8 @@ function joinChannel (control: TriggerControl, channel: Channel, user: Ref<Accou
}
async function OnThreadMessageDeleted (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const hierarchy = control.hierarchy
const removeTx = TxProcessor.extractTx(tx) as TxRemoveDoc<ThreadMessage>
if (!hierarchy.isDerived(removeTx.objectClass, chunter.class.ThreadMessage)) {
return []
}
const message = control.removedMap.get(removeTx.objectId) as ThreadMessage
if (message === undefined) {
@ -286,15 +269,40 @@ async function OnThreadMessageDeleted (tx: Tx, control: TriggerControl): Promise
*/
export async function ChunterTrigger (tx: TxCUD<Doc>, control: TriggerControl): Promise<Tx[]> {
const res: Tx[] = []
res.push(
...(await control.ctx.with('OnThreadMessageCreated', {}, async (ctx) => await OnThreadMessageCreated(tx, control)))
)
res.push(
...(await control.ctx.with('OnThreadMessageDeleted', {}, async (ctx) => await OnThreadMessageDeleted(tx, control)))
)
res.push(
...(await control.ctx.with('OnChatMessageCreated', {}, async (ctx) => await OnChatMessageCreated(tx, control)))
)
const actualTx = TxProcessor.extractTx(tx) as TxCreateDoc<ChatMessage>
if (
actualTx._class === core.class.TxCreateDoc &&
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
) {
res.push(
...(await control.ctx.with(
'OnThreadMessageCreated',
{},
async (ctx) => await OnThreadMessageCreated(tx, control)
))
)
}
if (
actualTx._class === core.class.TxRemoveDoc &&
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ThreadMessage)
) {
res.push(
...(await control.ctx.with(
'OnThreadMessageDeleted',
{},
async (ctx) => await OnThreadMessageDeleted(tx, control)
))
)
}
if (
actualTx._class === core.class.TxCreateDoc &&
control.hierarchy.isDerived(actualTx.objectClass, chunter.class.ChatMessage)
) {
res.push(
...(await control.ctx.with('OnChatMessageCreated', {}, async (ctx) => await OnChatMessageCreated(tx, control)))
)
}
return res
}
@ -455,7 +463,7 @@ async function hideOldChannels (
}
export async function updateChatInfo (control: TriggerControl, status: UserStatus, date: Timestamp): Promise<void> {
const account = await control.modelDb.findOne(contact.class.PersonAccount, { _id: status.user as Ref<PersonAccount> })
const account = getPersonAccountById(status.user as Ref<PersonAccount>, control)
if (account === undefined) return
const update = (await control.findAll(chunter.class.ChatInfo, { user: account.person })).shift()
@ -559,14 +567,14 @@ async function OnContextUpdate (tx: TxUpdateDoc<DocNotifyContext>, control: Trig
return []
}
async function JoinChannelTypeMatch (originTx: Tx, _: Doc, user: Ref<Account>): Promise<boolean> {
if (originTx.modifiedBy === user) return false
function JoinChannelTypeMatch (originTx: Tx, _: Doc, user: Ref<Account>[]): boolean {
if (user.some((it) => originTx.modifiedBy === it)) return false
if (originTx._class !== core.class.TxUpdateDoc) return false
const tx = originTx as TxUpdateDoc<Channel>
const added = combineAttributes([tx.operations], 'members', '$push', '$each')
return added.includes(user)
return user.some((it) => added.includes(it))
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type

View File

@ -21,13 +21,13 @@ import contact, {
Organization,
Person,
PersonAccount,
PersonSpace,
contactId,
formatContactName,
formatName,
getFirstName,
getLastName,
getName,
PersonSpace
getName
} from '@hcengineering/contact'
import core, {
Account,
@ -37,13 +37,13 @@ import core, {
Ref,
SpaceType,
Tx,
TxCUD,
TxCreateDoc,
TxMixin,
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
concatLink,
TxCUD
concatLink
} from '@hcengineering/core'
import notification, { Collaborators } from '@hcengineering/notification'
import { getMetadata } from '@hcengineering/platform'
@ -85,41 +85,49 @@ export async function OnSpaceTypeMembers (tx: Tx, control: TriggerControl): Prom
export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const mixinTx = tx as TxMixin<Person, Employee>
if (mixinTx.attributes.active !== true) return []
const acc = await control.modelDb.findOne(contact.class.PersonAccount, { person: mixinTx.objectId })
if (acc === undefined) return []
const acc = control.modelDb.getAccountByPersonId(mixinTx.objectId)
if (acc.length === 0) return []
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace(acc._id, mixinTx.objectId, control)
const txes = await createPersonSpace(
acc.map((it) => it._id),
mixinTx.objectId,
control
)
result.push(...txes)
for (const space of spaces) {
if (space.members.includes(acc._id)) continue
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$push: {
members: acc._id
}
})
result.push(pushTx)
const toAdd = acc.filter((it) => !space.members.includes(it._id))
if (toAdd.length === 0) continue
for (const a of toAdd) {
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
$push: {
members: a._id
}
})
result.push(pushTx)
}
}
return result
}
async function createPersonSpace (
account: Ref<Account>,
account: Ref<Account>[],
person: Ref<Person>,
control: TriggerControl
): Promise<TxCUD<PersonSpace>[]> {
const personSpace = (await control.findAll(contact.class.PersonSpace, { person }, { limit: 1 })).shift()
if (personSpace !== undefined) {
if (personSpace.members.includes(account)) return []
return [
const toAdd = account.filter((it) => !personSpace.members.includes(it))
if (toAdd.length === 0) return []
return toAdd.map((it) =>
control.txFactory.createTxUpdateDoc(personSpace._class, personSpace.space, personSpace._id, {
$push: {
members: account
members: it
}
})
]
)
}
return [
@ -129,7 +137,7 @@ async function createPersonSpace (
private: true,
archived: false,
person,
members: [account]
members: account
})
]
}
@ -143,7 +151,7 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace(acc._id, person._id, control)
const txes = await createPersonSpace([acc._id], person._id, control)
result.push(...txes)

View File

@ -88,13 +88,13 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
/**
* @public
*/
export async function IsIncomingMessage (
export function IsIncomingMessageTypeMatch (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
): boolean {
const message = TxProcessor.createDoc2Doc(TxProcessor.extractTx(tx) as TxCreateDoc<Message>)
return message.incoming && message.sendOn > (doc.createdOn ?? doc.modifiedOn)
}
@ -183,7 +183,7 @@ export default async () => ({
OnMessageCreate
},
function: {
IsIncomingMessage,
IsIncomingMessageTypeMatch,
FindMessages,
SendEmailNotifications
}

View File

@ -32,7 +32,7 @@ export default plugin(serverGmailId, {
OnMessageCreate: '' as Resource<TriggerFunc>
},
function: {
IsIncomingMessage: '' as TypeMatchFunc,
IsIncomingMessageTypeMatch: '' as TypeMatchFunc,
FindMessages: '' as Resource<ObjectDDParticipantFunc>,
SendEmailNotifications: '' as Resource<NotificationProviderFunc>
}

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import contact, { Contact, Employee, Person, PersonAccount, formatName, getName } from '@hcengineering/contact'
import contact, { Contact, Employee, formatName, getName, Person, PersonAccount } from '@hcengineering/contact'
import core, {
Doc,
Ref,
@ -27,6 +27,7 @@ import core, {
TxRemoveDoc,
TxUpdateDoc
} from '@hcengineering/core'
import gmail from '@hcengineering/gmail'
import hr, {
Department,
DepartmentMember,
@ -39,10 +40,13 @@ import hr, {
import notification, { NotificationType } from '@hcengineering/notification'
import { translate } from '@hcengineering/platform'
import { TriggerControl } from '@hcengineering/server-core'
import { getEmployee, getPersonAccountById } from '@hcengineering/server-notification'
import { getContentByTemplate, isAllowed } from '@hcengineering/server-notification-resources'
import gmail from '@hcengineering/gmail'
import { sendEmailNotification } from '@hcengineering/server-gmail-resources'
import { getEmployee, getPersonAccountById } from '@hcengineering/server-notification'
import {
getContentByTemplate,
getNotificationProviderControl,
isAllowed
} from '@hcengineering/server-notification-resources'
async function getOldDepartment (
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
@ -97,21 +101,29 @@ function exlude (first: Ref<Department>[], second: Ref<Department>[]): Ref<Depar
function getTxes (
factory: TxFactory,
account: Ref<DepartmentMember>,
account: Ref<DepartmentMember>[],
added: Ref<Department>[],
removed?: Ref<Department>[]
): Tx[] {
const pushTxes = added.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep, {
$push: { members: account }
})
)
const pushTxes = added
.map((dep) =>
account.map((it) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep, {
$push: { members: it }
})
)
)
.flat()
if (removed === undefined) return pushTxes
const pullTxes = removed.map((dep) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep, {
$pull: { members: account }
})
)
const pullTxes = removed
.map((dep) =>
account.map((it) =>
factory.createTxUpdateDoc(hr.class.Department, core.space.Workspace, dep, {
$pull: { members: it }
})
)
)
.flat()
return [...pullTxes, ...pushTxes]
}
@ -121,12 +133,8 @@ function getTxes (
export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxMixin<Employee, Staff>
const targetAccount = (
await control.modelDb.findAll(contact.class.PersonAccount, {
person: ctx.objectId
})
)[0]
if (targetAccount === undefined) return []
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
if (targetAccount.length === 0) return []
if (ctx.attributes.department !== undefined) {
const lastDepartment = await getOldDepartment(ctx, control)
@ -137,7 +145,7 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(
control.txFactory,
targetAccount._id,
targetAccount.map((it) => it._id),
[],
removed.map((p) => p._id)
)
@ -146,13 +154,22 @@ export async function OnDepartmentStaff (tx: Tx, control: TriggerControl): Promi
const push = (await buildHierarchy(departmentId, control)).map((p) => p._id)
if (lastDepartment === undefined) {
return getTxes(control.txFactory, targetAccount._id, push)
return getTxes(
control.txFactory,
targetAccount.map((it) => it._id),
push
)
}
let removed = (await buildHierarchy(lastDepartment, control)).map((p) => p._id)
const added = exlude(removed, push)
removed = exlude(push, removed)
return getTxes(control.txFactory, targetAccount._id, added, removed)
return getTxes(
control.txFactory,
targetAccount.map((it) => it._id),
added,
removed
)
}
return []
@ -183,16 +200,14 @@ export async function OnDepartmentRemove (tx: Tx, control: TriggerControl): Prom
employee.forEach((em) => {
res.push(control.txFactory.createTxMixin(em._id, em._class, em.space, hr.mixin.Staff, { department: undefined }))
})
targetAccounts.forEach((acc) => {
res.push(
...getTxes(
control.txFactory,
acc._id,
[],
removed.map((p) => p._id)
)
res.push(
...getTxes(
control.txFactory,
targetAccounts.map((it) => it._id),
[],
removed.map((p) => p._id)
)
})
)
return res
}
@ -232,19 +247,15 @@ export async function OnEmployeeDeactivate (tx: Tx, control: TriggerControl): Pr
return []
}
const targetAccount = (
await control.modelDb.findAll(contact.class.PersonAccount, {
person: ctx.objectId
})
)[0]
if (targetAccount === undefined) return []
const targetAccount = control.modelDb.getAccountByPersonId(ctx.objectId) as PersonAccount[]
if (targetAccount.length === 0) return []
const lastDepartment = await getOldDepartment(ctx, control)
if (lastDepartment === undefined) return []
const removed = await buildHierarchy(lastDepartment, control)
return getTxes(
control.txFactory,
targetAccount._id,
targetAccount.map((it) => it._id),
[],
removed.map((p) => p._id)
)
@ -268,19 +279,21 @@ async function sendEmailNotifications (
}
// should respect employee settings
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
person: { $in: Array.from(contacts.values()) as Ref<Employee>[] }
})
const type = await control.modelDb.findOne(notification.class.NotificationType, { _id: typeId })
if (type === undefined) return
const provider = await control.modelDb.findOne(notification.class.NotificationProvider, {
_id: gmail.providers.EmailNotificationProvider
})
if (provider === undefined) return
for (const account of accounts) {
const allowed = await isAllowed(control, account._id, type, provider)
if (!allowed) {
contacts.delete(account.person)
const notificationControl = await getNotificationProviderControl(control.ctx, control)
for (const accountId of contacts.values()) {
const accounts = control.modelDb.getAccountByPersonId(accountId) as PersonAccount[]
for (const account of accounts) {
const allowed = isAllowed(control, account._id, type, provider, notificationControl)
if (!allowed) {
contacts.delete(account.person)
}
}
}
@ -306,7 +319,7 @@ async function sendEmailNotifications (
export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
const sender = await getPersonAccountById(ctx.modifiedBy, control)
const sender = getPersonAccountById(ctx.modifiedBy, control)
if (sender === undefined) return []
const request = TxProcessor.createDoc2Doc(ctx)
@ -321,7 +334,7 @@ export async function OnRequestCreate (tx: Tx, control: TriggerControl): Promise
export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxUpdateDoc<Request>
const sender = await getPersonAccountById(ctx.modifiedBy, control)
const sender = getPersonAccountById(ctx.modifiedBy, control)
if (sender === undefined) return []
const request = (await control.findAll(hr.class.Request, { _id: ctx.objectId }))[0] as Request
@ -337,7 +350,7 @@ export async function OnRequestUpdate (tx: Tx, control: TriggerControl): Promise
export async function OnRequestRemove (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<Request>
const sender = await getPersonAccountById(ctx.modifiedBy, control)
const sender = getPersonAccountById(ctx.modifiedBy, control)
if (sender === undefined) return []
const request = control.removedMap.get(ctx.objectId) as Request
@ -389,7 +402,7 @@ export async function RequestTextPresenter (doc: Doc, control: TriggerControl):
export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ctx = TxProcessor.extractTx(tx) as TxCreateDoc<PublicHoliday>
const sender = await getPersonAccountById(ctx.modifiedBy, control)
const sender = getPersonAccountById(ctx.modifiedBy, control)
if (sender === undefined) return []
const employee = await getEmployee(sender.person as Ref<Employee>, control)
if (employee === undefined) return []
@ -410,7 +423,7 @@ export async function OnPublicHolidayCreate (tx: Tx, control: TriggerControl): P
*/
export async function PublicHolidayHTMLPresenter (doc: Doc, control: TriggerControl): Promise<string> {
const holiday = doc as PublicHoliday
const sender = await getPersonAccountById(holiday.modifiedBy, control)
const sender = getPersonAccountById(holiday.modifiedBy, control)
if (sender === undefined) return ''
const employee = await getEmployee(sender.person as Ref<Employee>, control)
if (employee === undefined) return ''
@ -426,7 +439,7 @@ export async function PublicHolidayHTMLPresenter (doc: Doc, control: TriggerCont
*/
export async function PublicHolidayTextPresenter (doc: Doc, control: TriggerControl): Promise<string> {
const holiday = doc as PublicHoliday
const sender = await getPersonAccountById(holiday.modifiedBy, control)
const sender = getPersonAccountById(holiday.modifiedBy, control)
if (sender === undefined) return ''
const employee = await getEmployee(sender.person as Ref<Employee>, control)
if (employee === undefined) return ''

View File

@ -37,7 +37,11 @@ import love, {
import notification from '@hcengineering/notification'
import { translate } from '@hcengineering/platform'
import { TriggerControl } from '@hcengineering/server-core'
import { createPushNotification, isAllowed } from '@hcengineering/server-notification-resources'
import {
createPushNotification,
getNotificationProviderControl,
isAllowed
} from '@hcengineering/server-notification-resources'
import { workbenchId } from '@hcengineering/workbench'
export async function OnEmployee (tx: Tx, control: TriggerControl): Promise<Tx[]> {
@ -267,16 +271,19 @@ export async function OnKnock (tx: Tx, control: TriggerControl): Promise<Tx[]> {
_id: notification.providers.PushNotificationProvider
})
if (provider === undefined) return []
const notificationControl = await getNotificationProviderControl(control.ctx, control)
for (const user of roomInfo.persons) {
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: user })
if (userAcc === undefined) continue
if (await isAllowed(control, userAcc._id, type, provider)) {
const userAcc = control.modelDb.getAccountByPersonId(user) as PersonAccount[]
if (userAcc.length === 0) continue
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
const title = await translate(love.string.KnockingLabel, {})
const body = await translate(love.string.IsKnocking, {
name: formatName(from.name, control.branding?.lastNameFirst)
})
await createPushNotification(control, userAcc._id, title, body, request._id, from, path)
// TODO: Select proper account target
await createPushNotification(control, userAcc[0]._id, title, body, request._id, from, path)
}
}
return res
@ -293,8 +300,8 @@ export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]>
if (invite.status === RequestStatus.Pending) {
const target = (await control.findAll(contact.class.Person, { _id: invite.target }))[0]
if (target === undefined) return []
const userAcc = await control.modelDb.findOne(contact.class.PersonAccount, { person: target._id })
if (userAcc === undefined) return []
const userAcc = control.modelDb.getAccountByPersonId(target._id) as PersonAccount[]
if (userAcc.length === 0) return []
const from = (await control.findAll(contact.class.Person, { _id: invite.from }))[0]
const type = await control.modelDb.findOne(notification.class.NotificationType, {
_id: love.ids.InviteNotification
@ -304,7 +311,8 @@ export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]>
_id: notification.providers.PushNotificationProvider
})
if (provider === undefined) return []
if (await isAllowed(control, userAcc._id, type, provider)) {
const notificationControl = await getNotificationProviderControl(control.ctx, control)
if (userAcc.some((it) => isAllowed(control, it._id, type, provider, notificationControl))) {
const path = [workbenchId, control.workspace.workspaceUrl, loveId]
const title = await translate(love.string.InivitingLabel, {})
const body =
@ -313,7 +321,8 @@ export async function OnInvite (tx: Tx, control: TriggerControl): Promise<Tx[]>
name: formatName(from.name, control.branding?.lastNameFirst)
})
: await translate(love.string.InivitingLabel, {})
await createPushNotification(control, userAcc._id, title, body, invite._id, from, path)
// TODO: Select a proper user
await createPushNotification(control, userAcc[0]._id, title, body, invite._id, from, path)
}
}
}

View File

@ -17,11 +17,11 @@
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import contact, {
type AvatarInfo,
getAvatarProviderId,
getGravatarUrl,
Person,
PersonAccount
PersonAccount,
type AvatarInfo
} from '@hcengineering/contact'
import core, {
Account,
@ -72,7 +72,6 @@ import { getMetadata, getResource, translate } from '@hcengineering/platform'
import { type TriggerControl } from '@hcengineering/server-core'
import serverCore from '@hcengineering/server-core'
import serverNotification, {
getPersonAccount,
getPersonAccountById,
NOTIFICATION_BODY_SIZE,
PUSH_NOTIFICATION_TITLE_SIZE,
@ -92,17 +91,19 @@ import {
getHTMLPresenter,
getNotificationContent,
getNotificationLink,
getNotificationProviderControl,
getTextPresenter,
getUsersInfo,
isAllowed,
isMixinTx,
isShouldNotifyTx,
isUserEmployeeInFieldValue,
isUserInFieldValue,
isUserEmployeeInFieldValueTypeMatch,
isUserInFieldValueTypeMatch,
messageToMarkup,
replaceAll,
toReceiverInfo,
updateNotifyContextsSpace
updateNotifyContextsSpace,
type NotificationProviderControl
} from './utils'
export function getPushCollaboratorTx (
@ -124,6 +125,7 @@ export function getPushCollaboratorTx (
}
export async function getCommonNotificationTxes (
ctx: MeasureContext,
control: TriggerControl,
doc: Doc,
data: Partial<Data<CommonInboxNotification>>,
@ -144,6 +146,7 @@ export async function getCommonNotificationTxes (
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { objectId: attachedTo })
const notificationTx = await pushInboxNotifications(
ctx,
control,
res,
receiver,
@ -255,15 +258,14 @@ export async function getContentByTemplate (
}
}
async function getValueCollaborators (value: any, attr: AnyAttribute, control: TriggerControl): Promise<Ref<Account>[]> {
function getValueCollaborators (value: any, attr: AnyAttribute, control: TriggerControl): Ref<Account>[] {
const hierarchy = control.hierarchy
if (attr.type._class === core.class.RefTo) {
const to = (attr.type as RefTo<Doc>).to
if (hierarchy.isDerived(to, contact.class.Person)) {
const acc = await getPersonAccount(value, control)
return acc !== undefined ? [acc._id] : []
return (control.modelDb.getAccountByPersonId(value) as PersonAccount[]).map((it) => it._id)
} else if (hierarchy.isDerived(to, core.class.Account)) {
const acc = await getPersonAccountById(value, control)
const acc = getPersonAccountById(value, control)
return acc !== undefined ? [acc._id] : []
}
} else if (attr.type._class === core.class.ArrOf) {
@ -271,12 +273,12 @@ async function getValueCollaborators (value: any, attr: AnyAttribute, control: T
if (arrOf._class === core.class.RefTo) {
const to = (arrOf as RefTo<Doc>).to
if (hierarchy.isDerived(to, contact.class.Person)) {
const employeeAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
const employeeAccounts = control.modelDb.findAllSync(contact.class.PersonAccount, {
person: { $in: Array.isArray(value) ? value : [value] }
})
return employeeAccounts.map((p) => p._id)
} else if (hierarchy.isDerived(to, core.class.Account)) {
const employeeAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
const employeeAccounts = control.modelDb.findAllSync(contact.class.PersonAccount, {
_id: { $in: Array.isArray(value) ? value : [value] }
})
return employeeAccounts.map((p) => p._id)
@ -295,7 +297,7 @@ async function getKeyCollaborators (
if (value !== undefined && value !== null) {
const attr = control.hierarchy.findAttribute(docClass, field)
if (attr !== undefined) {
return await getValueCollaborators(value, attr, control)
return getValueCollaborators(value, attr, control)
}
}
}
@ -315,7 +317,7 @@ export async function getDocCollaborators (
const newCollaborators = await ctx.with(
'getKeyCollaborators',
{},
async () => await getKeyCollaborators(doc._class, value, field, control)
async (ctx) => await getKeyCollaborators(doc._class, value, field, control)
)
if (newCollaborators !== undefined) {
for (const newCollaborator of newCollaborators) {
@ -327,6 +329,7 @@ export async function getDocCollaborators (
}
export async function pushInboxNotifications (
ctx: MeasureContext,
control: TriggerControl,
res: Tx[],
receiver: ReceiverInfo,
@ -344,6 +347,7 @@ export async function pushInboxNotifications (
if (context === undefined) {
docNotifyContextId = await createNotifyContext(
ctx,
control,
objectId,
objectClass,
@ -584,6 +588,7 @@ async function sendPushToSubscription (
* @public
*/
export async function pushActivityInboxNotifications (
ctx: MeasureContext,
originTx: TxCUD<Doc>,
control: TriggerControl,
res: Tx[],
@ -594,7 +599,7 @@ export async function pushActivityInboxNotifications (
activityMessage: ActivityMessage,
shouldUpdateTimestamp: boolean
): Promise<TxCreateDoc<InboxNotification> | undefined> {
const content = await getNotificationContent(originTx, receiver.account, sender, object, control)
const content = await getNotificationContent(originTx, [receiver.account], sender, object, control)
const data: Partial<Data<ActivityInboxNotification>> = {
...content,
attachedTo: activityMessage._id,
@ -602,6 +607,7 @@ export async function pushActivityInboxNotifications (
}
return await pushInboxNotifications(
ctx,
control,
res,
receiver,
@ -628,7 +634,7 @@ export async function applyNotificationProviders (
sender: SenderInfo,
message?: ActivityMessage
): Promise<void> {
const resources = await control.modelDb.findAll(serverNotification.class.NotificationProviderResources, {})
const resources = control.modelDb.findAllSync(serverNotification.class.NotificationProviderResources, {})
for (const [provider, types] of notifyResult.entries()) {
if (provider === notification.providers.PushNotificationProvider) {
// const now = Date.now()
@ -663,6 +669,7 @@ export async function applyNotificationProviders (
}
async function createNotifyContext (
ctx: MeasureContext,
control: TriggerControl,
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>,
@ -678,7 +685,7 @@ async function createNotifyContext (
isPinned: false,
lastUpdateTimestamp: updateTimestamp
})
await control.apply([createTx])
await ctx.with('apply', {}, () => control.apply([createTx]))
if (receiver.account?.email !== undefined) {
control.operationContext.derived.targets['docNotifyContext' + createTx._id] = (it) => {
if (it._id === createTx._id) {
@ -690,6 +697,7 @@ async function createNotifyContext (
}
export async function getNotificationTxes (
ctx: MeasureContext,
control: TriggerControl,
object: Doc,
tx: TxCUD<Doc>,
@ -698,7 +706,8 @@ export async function getNotificationTxes (
sender: SenderInfo,
params: NotifyParams,
docNotifyContexts: DocNotifyContext[],
activityMessages: ActivityMessage[]
activityMessages: ActivityMessage[],
settings: NotificationProviderControl
): Promise<Tx[]> {
if (receiver.account === undefined) {
return []
@ -713,14 +722,16 @@ export async function getNotificationTxes (
tx,
originTx,
object,
receiver.account,
[receiver.account],
params.isOwn,
params.isSpace,
settings,
docMessage
)
if (notifyResult.has(notification.providers.InboxNotificationProvider)) {
const notificationTx = await pushActivityInboxNotifications(
ctx,
originTx,
control,
res,
@ -753,6 +764,7 @@ export async function getNotificationTxes (
if (context === undefined) {
await createNotifyContext(
ctx,
control,
message.attachedTo,
message.attachedToClass,
@ -767,20 +779,17 @@ export async function getNotificationTxes (
}
async function updateContextsTimestamp (
ctx: MeasureContext,
contexts: DocNotifyContext[],
timestamp: Timestamp,
control: TriggerControl,
modifiedBy: Ref<Account>
): Promise<void> {
if (contexts.length === 0) return
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, {
_id: { $in: contexts.map((it) => it.user as Ref<PersonAccount>) }
})
const res: Tx[] = []
for (const context of contexts) {
const account = accounts.find(({ _id }) => _id === context.user)
const account = getPersonAccountById(context.user, control) // accounts.find(({ _id }) => _id === context.user)
const isViewed =
context.lastViewedTimestamp !== undefined && (context.lastUpdateTimestamp ?? 0) <= context.lastViewedTimestamp
const updateTx = control.txFactory.createTxUpdateDoc(context._class, context.space, context._id, {
@ -803,10 +812,13 @@ async function updateContextsTimestamp (
}
}
await control.apply(res)
if (res.length > 0) {
await ctx.with('apply', {}, () => control.apply(res))
}
}
async function removeContexts (
ctx: MeasureContext,
contexts: DocNotifyContext[],
unsubscribe: Ref<PersonAccount>[],
control: TriggerControl
@ -814,13 +826,13 @@ async function removeContexts (
if (contexts.length === 0) return
if (unsubscribe.length === 0) return
const unsubscribeAccounts = await control.modelDb.findAll(contact.class.PersonAccount, {
_id: { $in: unsubscribe }
})
const res: Tx[] = []
for (const context of contexts) {
const account = unsubscribeAccounts.find(({ _id }) => _id === context.user)
if (!unsubscribe.includes(context.user as Ref<PersonAccount>)) {
continue
}
const account = control.modelDb.getObject(context.user)
if (account === undefined) continue
const removeTx = control.txFactory.createTxRemoveDoc(context._class, context.space, context._id)
@ -864,23 +876,31 @@ export async function createCollabDocInfo (
objectId: object._id,
user: { $in: unsubscribe }
})
await removeContexts(notifyContexts, unsubscribe, control)
await removeContexts(ctx, notifyContexts, unsubscribe, control)
}
return res
}
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id })
await removeContexts(notifyContexts, unsubscribe, control)
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
let notifyContexts: DocNotifyContext[] = []
if (!(object._id === tx.objectId && tx._class === core.class.TxCreateDoc)) {
notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id })
}
if (notifyContexts.length > 0) {
await updateContextsTimestamp(ctx, notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
}
if (notifyContexts.length > 0 && unsubscribe.length > 0) {
await removeContexts(ctx, notifyContexts, unsubscribe, control)
}
const targets = new Set(collaborators)
// user is not collaborator of himself, but we should notify user of changes related to users account (mentions, comments etc)
if (control.hierarchy.isDerived(object._class, contact.class.Person)) {
const acc = await getPersonAccount(object._id as Ref<Person>, control)
if (acc !== undefined) {
targets.add(acc._id)
const acc = control.modelDb.getAccountByPersonId(object._id as Ref<Person>) as PersonAccount[]
for (const a of acc.map((it) => it._id)) {
targets.add(a)
}
}
@ -893,19 +913,19 @@ export async function createCollabDocInfo (
{},
async (ctx) => await getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
)
const sender: SenderInfo = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
const sender: SenderInfo = usersInfo.get(originTx.modifiedBy) ?? {
_id: originTx.modifiedBy
}
const settings = await getNotificationProviderControl(ctx, control)
for (const target of targets) {
const info: ReceiverInfo | undefined = toReceiverInfo(
control.hierarchy,
usersInfo.find(({ _id }) => _id === target)
)
const info: ReceiverInfo | undefined = toReceiverInfo(control.hierarchy, usersInfo.get(target))
if (info === undefined) continue
const targetRes = await getNotificationTxes(
ctx,
control,
object,
tx,
@ -914,7 +934,8 @@ export async function createCollabDocInfo (
sender,
params,
notifyContexts,
docMessages
docMessages,
settings
)
const ids = new Set(targetRes.map((it) => it._id))
if (info.account?.email !== undefined) {
@ -994,6 +1015,7 @@ async function getTxCollabs (
}
async function getSpaceCollabTxes (
ctx: MeasureContext,
control: TriggerControl,
doc: Doc,
tx: TxCUD<Doc>,
@ -1005,7 +1027,8 @@ async function getSpaceCollabTxes (
return []
}
const space = cache.get(doc.space) ?? (await control.findAll(core.class.Space, { _id: doc.space }, { limit: 1 }))[0]
const space =
cache.get(doc.space) ?? (await control.findAllCtx(ctx, core.class.Space, { _id: doc.space }, { limit: 1 }))[0]
if (space === undefined) return []
cache.set(space._id, space)
@ -1018,7 +1041,7 @@ async function getSpaceCollabTxes (
const collabs = control.hierarchy.as<Doc, Collaborators>(space, notification.mixin.Collaborators)
if (collabs.collaborators !== undefined) {
return await createCollabDocInfo(
control.ctx,
ctx,
collabs.collaborators as Ref<PersonAccount>[],
control,
tx,
@ -1059,7 +1082,7 @@ async function createCollaboratorDoc (
const notificationTxes = await ctx.with(
'create-collabdocinfo',
{},
async () =>
async (ctx) =>
await createCollabDocInfo(
ctx,
collaborators as Ref<PersonAccount>[],
@ -1078,7 +1101,7 @@ async function createCollaboratorDoc (
...(await ctx.with(
'get-space-collabtxes',
{},
async () => await getSpaceCollabTxes(control, doc, tx, originTx, activityMessage, cache)
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessage, cache)
))
)
@ -1100,6 +1123,8 @@ async function updateCollaboratorsMixin (
const res: Tx[] = []
const notificationControl = await getNotificationProviderControl(ctx, control)
if (tx.attributes.collaborators !== undefined) {
const createTx = hierarchy.isDerived(tx.objectClass, core.class.AttachedDoc)
? (
@ -1142,7 +1167,7 @@ async function updateCollaboratorsMixin (
for (const collab of tx.attributes.collaborators) {
if (!prevCollabs.has(collab) && tx.modifiedBy !== collab) {
for (const provider of providers) {
if (await isAllowed(control, collab as Ref<PersonAccount>, type, provider)) {
if (isAllowed(control, collab as Ref<PersonAccount>, type, provider, notificationControl)) {
newCollabs.push(collab)
break
}
@ -1161,17 +1186,15 @@ async function updateCollaboratorsMixin (
{},
async (ctx) => await getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
)
const sender: SenderInfo = infos.find(({ _id }) => _id === originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
const sender: SenderInfo = infos.get(originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
for (const collab of newCollabs) {
const target = toReceiverInfo(
hierarchy,
infos.find(({ _id }) => _id === collab)
)
const target = toReceiverInfo(hierarchy, infos.get(collab))
if (target === undefined) continue
for (const message of activityMessages) {
await pushActivityInboxNotifications(
ctx,
originTx,
control,
res,
@ -1225,7 +1248,7 @@ async function collectionCollabDoc (
const collaborators = await ctx.with(
'get-collaborators',
{},
async () => await getCollaborators(doc, control, tx, res)
async (ctx) => await getCollaborators(ctx, doc, control, tx, res)
)
res = res.concat(
@ -1377,7 +1400,7 @@ async function updateCollaboratorDoc (
const doc = await ctx.with(
'find-doc',
{ _class: tx.objectClass },
async () =>
async (ctx) =>
cache.get(tx.objectId) ?? (await control.findAllCtx(ctx, tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
)
if (doc === undefined) return []
@ -1388,7 +1411,7 @@ async function updateCollaboratorDoc (
const collabsInfo = await ctx.with(
'get-tx-collaborators',
{},
async () => await getTxCollabs(ctx, tx, control, doc)
async (ctx) => await getTxCollabs(ctx, tx, control, doc)
)
if (collabsInfo.added.length > 0) {
@ -1403,7 +1426,7 @@ async function updateCollaboratorDoc (
await ctx.with(
'create-collab-docinfo',
{},
async () =>
async (ctx) =>
await createCollabDocInfo(
ctx,
collabsInfo.result as Ref<PersonAccount>[],
@ -1421,7 +1444,7 @@ async function updateCollaboratorDoc (
const collaborators = await ctx.with(
'get-doc-collaborators',
{},
async () => await getDocCollaborators(ctx, doc, mixin, control)
async (ctx) => await getDocCollaborators(ctx, doc, mixin, control)
)
res.push(getMixinTx(tx, control, collaborators))
res = res.concat(
@ -1442,11 +1465,11 @@ async function updateCollaboratorDoc (
await ctx.with(
'get-space-collabtxes',
{},
async () => await getSpaceCollabTxes(control, doc, tx, originTx, activityMessages, cache)
async (ctx) => await getSpaceCollabTxes(ctx, control, doc, tx, originTx, activityMessages, cache)
)
)
res = res.concat(
await ctx.with('update-notify-context-space', {}, async () => await updateNotifyContextsSpace(control, tx))
await ctx.with('update-notify-context-space', {}, async (ctx) => await updateNotifyContextsSpace(ctx, control, tx))
)
return res
@ -1546,7 +1569,7 @@ async function applyUserTxes (
for (const [user, txs] of map.entries()) {
const account =
(cache.get(user) as PersonAccount) ??
(await ctx.with('get-person-account', {}, async () => await getPersonAccountById(user, control)))
ctx.withSync('get-person-account', {}, () => getPersonAccountById(user, control))
if (account !== undefined) {
cache.set(account._id, account)
@ -1604,15 +1627,15 @@ async function updateCollaborators (ctx: MeasureContext, control: TriggerControl
const contexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: objectId })
const addedInfo = await getUsersInfo(ctx, toAdd as Ref<PersonAccount>[], control)
for (const addedUser of addedInfo) {
for (const addedUser of addedInfo.values()) {
const info = toReceiverInfo(hierarchy, addedUser)
if (info === undefined) continue
const context = getDocNotifyContext(control, contexts, objectId, info._id)
if (context !== undefined) continue
await createNotifyContext(control, objectId, objectClass, objectSpace, info)
await createNotifyContext(ctx, control, objectId, objectClass, objectSpace, info)
}
await removeContexts(contexts, removedCollaborators as Ref<PersonAccount>[], control)
await removeContexts(ctx, contexts, removedCollaborators as Ref<PersonAccount>[], control)
return res
}
@ -1630,7 +1653,7 @@ export async function createCollaboratorNotifications (
return await ctx.with(
'updateDerivedCollaborators',
{},
async () => await updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>)
async (ctx) => await updateCollaborators(ctx, control, TxProcessor.extractTx(tx) as TxCUD<Doc>)
)
}
@ -1643,7 +1666,7 @@ export async function createCollaboratorNotifications (
const res = await ctx.with(
'createCollaboratorDoc',
{},
async () =>
async (ctx) =>
await createCollaboratorDoc(ctx, tx as TxCreateDoc<Doc>, control, activityMessages, originTx ?? tx, cache)
)
@ -1654,14 +1677,14 @@ export async function createCollaboratorNotifications (
let res = await ctx.with(
'updateCollaboratorDoc',
{},
async () =>
async (ctx) =>
await updateCollaboratorDoc(ctx, tx as TxUpdateDoc<Doc>, control, originTx ?? tx, activityMessages, cache)
)
res = res.concat(
await ctx.with(
'updateCollaboratorMixin',
{},
async () =>
async (ctx) =>
await updateCollaboratorsMixin(
ctx,
tx as TxMixin<Doc, Collaborators>,
@ -1703,6 +1726,7 @@ export async function removeDocInboxNotifications (_id: Ref<ActivityMessage>, co
}
export async function getCollaborators (
ctx: MeasureContext,
doc: Doc,
control: TriggerControl,
tx: TxCUD<Doc>,
@ -1717,7 +1741,7 @@ export async function getCollaborators (
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
return control.hierarchy.as(doc, notification.mixin.Collaborators).collaborators
} else {
const collaborators = await getDocCollaborators(control.ctx, doc, mixin, control)
const collaborators = await getDocCollaborators(ctx, doc, mixin, control)
res.push(getMixinTx(tx, control, collaborators))
return collaborators
@ -1821,7 +1845,7 @@ export default async () => ({
OnDocRemove
},
function: {
IsUserInFieldValue: isUserInFieldValue,
IsUserEmployeeInFieldValue: isUserEmployeeInFieldValue
IsUserInFieldValueTypeMatch: isUserInFieldValueTypeMatch,
IsUserEmployeeInFieldValueTypeMatch: isUserEmployeeInFieldValueTypeMatch
}
})

View File

@ -12,24 +12,19 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
import notification, {
BaseNotificationType,
Collaborators,
CommonNotificationType,
NotificationContent,
notificationId,
NotificationProvider,
NotificationType
} from '@hcengineering/notification'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
import { Analytics } from '@hcengineering/analytics'
import contact, { formatName, PersonAccount } from '@hcengineering/contact'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import contact, { formatName, PersonAccount, type Person, type PersonSpace } from '@hcengineering/contact'
import core, {
Account,
Class,
concatLink,
Doc,
DocumentUpdate,
groupByArray,
Hierarchy,
Markup,
matchQuery,
MixinUpdate,
Ref,
@ -42,11 +37,23 @@ import core, {
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
type IdMap,
type MeasureContext,
Markup,
concatLink
type WithLookup
} from '@hcengineering/core'
import notification, {
BaseNotificationType,
Collaborators,
CommonNotificationType,
NotificationContent,
notificationId,
NotificationProvider,
NotificationType,
type NotificationProviderSetting,
type NotificationTypeSetting
} from '@hcengineering/notification'
import { getMetadata, getResource, IntlString, translate } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
getPersonAccountById,
HTMLPresenter,
@ -55,28 +62,26 @@ import serverNotification, {
SenderInfo,
TextPresenter
} from '@hcengineering/server-notification'
import activity, { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
import serverView from '@hcengineering/server-view'
import { workbenchId } from '@hcengineering/workbench'
import { encodeObjectURI } from '@hcengineering/view'
import chunter, { ChatMessage } from '@hcengineering/chunter'
import { workbenchId } from '@hcengineering/workbench'
import { NotifyResult } from './types'
/**
* @public
*/
export async function isUserEmployeeInFieldValue (
export function isUserEmployeeInFieldValueTypeMatch (
_: Tx,
doc: Doc,
user: Ref<Account>,
user: Ref<Account>[],
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
): boolean {
if (type.field === undefined) return false
const value = (doc as any)[type.field]
if (value == null) return false
const employee = (await control.modelDb.findAll(contact.class.PersonAccount, { _id: user as Ref<PersonAccount> }))[0]
const employee = control.modelDb.findAllSync(contact.class.PersonAccount, { _id: user[0] as Ref<PersonAccount> })[0]
if (employee === undefined) return false
if (Array.isArray(value)) {
return value.includes(employee.person)
@ -88,12 +93,7 @@ export async function isUserEmployeeInFieldValue (
/**
* @public
*/
export async function isUserInFieldValue (
_: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType
): Promise<boolean> {
export function isUserInFieldValueTypeMatch (_: Tx, doc: Doc, user: Ref<Account>[], type: NotificationType): boolean {
if (type.field === undefined) {
return false
}
@ -104,7 +104,7 @@ export async function isUserInFieldValue (
return false
}
return Array.isArray(value) ? value.includes(user) : value === user
return Array.isArray(value) ? user.some((it) => value.includes(it)) : user.includes(value)
}
export function replaceAll (str: string, find: string, replace: string): string {
@ -117,8 +117,9 @@ function escapeRegExp (str: string): string {
export async function shouldNotifyCommon (
control: TriggerControl,
user: Ref<Account>,
typeId: Ref<CommonNotificationType>
user: Ref<Account>[],
typeId: Ref<CommonNotificationType>,
notificationControl: NotificationProviderControl
): Promise<NotifyResult> {
const type = (await control.modelDb.findAll(notification.class.CommonNotificationType, { _id: typeId }))[0]
@ -130,7 +131,9 @@ export async function shouldNotifyCommon (
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
for (const provider of providers) {
const allowed = await isAllowed(control, user as Ref<PersonAccount>, type, provider)
const allowed = user.some((user) =>
isAllowed(control, user as Ref<PersonAccount>, type, provider, notificationControl)
)
if (allowed) {
const cur = result.get(provider._id) ?? []
result.set(provider._id, [...cur, type])
@ -140,17 +143,15 @@ export async function shouldNotifyCommon (
return result
}
export async function isAllowed (
export function isAllowed (
control: TriggerControl,
receiver: Ref<PersonAccount>,
type: BaseNotificationType,
provider: NotificationProvider
): Promise<boolean> {
const providersSettings = await control.queryFind(control.ctx, notification.class.NotificationProviderSetting, {
space: core.space.Workspace
})
const providerSetting = providersSettings.find(
({ attachedTo, modifiedBy }) => attachedTo === provider._id && modifiedBy === receiver
provider: NotificationProvider,
notificationControl: NotificationProviderControl
): boolean {
const providerSetting = (notificationControl.byProvider.get(provider._id) ?? []).find(
({ attachedTo, modifiedBy }) => modifiedBy === receiver
)
if (providerSetting !== undefined && !providerSetting.enabled) {
@ -161,17 +162,13 @@ export async function isAllowed (
return false
}
const providerDefaults = await control.modelDb.findAll(notification.class.NotificationProviderDefaults, {})
const providerDefaults = control.modelDb.findAllSync(notification.class.NotificationProviderDefaults, {})
if (providerDefaults.some((it) => it.provider === provider._id && it.ignoredTypes.includes(type._id))) {
return false
}
const typesSettings = await control.queryFind(control.ctx, notification.class.NotificationTypeSetting, {
space: core.space.Workspace
})
const setting = typesSettings.find(
(it) => it.attachedTo === provider._id && it.type === type._id && it.modifiedBy === receiver
const setting = (notificationControl.settingsByProvider.get(provider._id) ?? []).find(
(it) => it.type === type._id && it.modifiedBy === receiver
)
if (setting !== undefined) {
@ -192,29 +189,23 @@ export async function isShouldNotifyTx (
tx: TxCUD<Doc>,
originTx: TxCUD<Doc>,
object: Doc,
user: PersonAccount,
user: PersonAccount[],
isOwn: boolean,
isSpace: boolean,
notificationControl: NotificationProviderControl,
docUpdateMessage?: DocUpdateMessage
): Promise<NotifyResult> {
const types = await getMatchedTypes(
control,
tx,
originTx,
isOwn,
isSpace,
docUpdateMessage?.attributeUpdates?.attrKey
)
const modifiedAccount = await getPersonAccountById(tx.modifiedBy, control)
const types = getMatchedTypes(control, tx, originTx, isOwn, isSpace, docUpdateMessage?.attributeUpdates?.attrKey)
const modifiedAccount = getPersonAccountById(tx.modifiedBy, control)
const result = new Map<Ref<NotificationProvider>, BaseNotificationType[]>()
const providers = await control.modelDb.findAll(notification.class.NotificationProvider, {})
const providers = control.modelDb.findAllSync(notification.class.NotificationProvider, {})
for (const type of types) {
if (
type.allowedForAuthor !== true &&
(tx.modifiedBy === user._id ||
(user.some((it) => tx.modifiedBy === it._id) ||
// Also check if we have different account for same user.
(user?.person !== undefined && user?.person === modifiedAccount?.person))
(user?.[0].person !== undefined && user?.[0]?.person === modifiedAccount?.person))
) {
continue
}
@ -222,12 +213,18 @@ export async function isShouldNotifyTx (
const mixin = control.hierarchy.as(type, serverNotification.mixin.TypeMatch)
if (mixin.func !== undefined) {
const f = await getResource(mixin.func)
const res = await f(tx, object, user._id, type, control)
const res = f(
tx,
object,
user.map((it) => it._id),
type,
control
)
if (!res) continue
}
}
for (const provider of providers) {
const allowed = await isAllowed(control, user._id, type, provider)
const allowed = user.some((it) => isAllowed(control, it._id, type, provider, notificationControl))
if (allowed) {
const cur = result.get(provider._id) ?? []
@ -239,17 +236,17 @@ export async function isShouldNotifyTx (
return result
}
async function getMatchedTypes (
function getMatchedTypes (
control: TriggerControl,
tx: TxCUD<Doc>,
originTx: TxCUD<Doc>,
isOwn: boolean,
isSpace: boolean,
field?: string
): Promise<NotificationType[]> {
const allTypes = (
await control.modelDb.findAll(notification.class.NotificationType, { ...(field !== undefined ? { field } : {}) })
).filter((p) => (isSpace ? p.spaceSubscribe === true : p.spaceSubscribe !== true))
): NotificationType[] {
const allTypes = control.modelDb
.findAllSync(notification.class.NotificationType, { ...(field !== undefined ? { field } : {}) })
.filter((p) => (isSpace ? p.spaceSubscribe === true : p.spaceSubscribe !== true))
const filtered: NotificationType[] = []
for (const type of allTypes) {
if (isTypeMatched(control, type, tx, originTx, isOwn)) {
@ -300,6 +297,7 @@ function fieldUpdated (field: string, ops: DocumentUpdate<Doc> | MixinUpdate<Doc
}
export async function updateNotifyContextsSpace (
ctx: MeasureContext,
control: TriggerControl,
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>
): Promise<Tx[]> {
@ -312,7 +310,7 @@ export async function updateNotifyContextsSpace (
return []
}
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { objectId: tx.objectId })
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: tx.objectId })
return notifyContexts.map((value) =>
control.txFactory.createTxUpdateDoc(value._class, value.space, value._id, {
@ -421,7 +419,7 @@ function getNotificationPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy
export async function getNotificationContent (
originTx: TxCUD<Doc>,
targetUser: PersonAccount,
targetUser: PersonAccount[],
sender: SenderInfo,
object: Doc,
control: TriggerControl
@ -440,7 +438,7 @@ export async function getNotificationContent (
if (notificationPresenter !== undefined) {
const getFuillfillmentParams = await getResource(notificationPresenter.presenter)
const updateParams = await getFuillfillmentParams(object, originTx, targetUser._id, control)
const updateParams = await getFuillfillmentParams(object, originTx, targetUser[0]._id, control)
title = updateParams.title
body = updateParams.body
data = updateParams.data
@ -474,33 +472,57 @@ export async function getUsersInfo (
ctx: MeasureContext,
ids: Ref<PersonAccount>[],
control: TriggerControl
): Promise<(ReceiverInfo | SenderInfo)[]> {
if (ids.length === 0) return []
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, { _id: { $in: ids } })
): Promise<Map<Ref<Account>, ReceiverInfo | SenderInfo>> {
if (ids.length === 0) return new Map()
const accounts = control.modelDb.findAllSync(contact.class.PersonAccount, { _id: { $in: ids } })
const personIds = accounts.map((it) => it.person)
const personIdsMap = new Set(personIds)
const accountById = toIdMap(accounts)
const persons = toIdMap(
await ctx.with(
'find-persons',
{},
async () => await control.findAll(contact.class.Person, { _id: { $in: personIds } })
const persons: IdMap<WithLookup<Person>> = new Map()
const spaces = new Map<Ref<Person>, PersonSpace>()
const p = await ctx.with('find-persons', {}, async (ctx) =>
(await control.queryFind(ctx, contact.mixin.Employee, { active: { $in: [true, false] } }, {})).filter((it) =>
personIdsMap.has(it._id)
)
)
const spaces = await ctx.with('find-person-spaces', {}, async () => {
const res = await control.findAll(contact.class.PersonSpace, { person: { $in: personIds } })
for (const pp of p) {
persons.set(pp._id, pp)
}
return new Map(res.map((s) => [s.person, s]))
})
const nonEmployee = personIds.filter((it) => !persons.has(it))
return ids.map((_id) => {
const account = accountById.get(_id)
return {
_id,
account,
person: account !== undefined ? persons.get(account.person) : undefined,
space: account !== undefined ? spaces.get(account.person)?._id : undefined
}
})
const p2 = await ctx.with('find-persons', {}, async (ctx) =>
(await control.queryFind(ctx, contact.class.Person, { _id: { $in: Array.from(nonEmployee) } }, {})).filter((it) =>
personIdsMap.has(it._id)
)
)
for (const pp of p2) {
persons.set(pp._id, pp)
}
const res = await ctx.with('find-person-spaces', {}, async (ctx) =>
(await control.queryFind(ctx, contact.class.PersonSpace, {}, {})).filter((it) => personIdsMap.has(it.person))
)
for (const r of res) {
spaces.set(r.person, r)
}
return new Map(
ids.map((_id) => {
const account = accountById.get(_id)
return [
_id,
{
_id,
account,
person: account !== undefined ? persons.get(account.person) : undefined,
space: account !== undefined ? spaces.get(account.person)?._id : undefined
}
]
})
)
}
export function toReceiverInfo (hierarchy: Hierarchy, info?: SenderInfo | ReceiverInfo): ReceiverInfo | undefined {
@ -611,3 +633,36 @@ export async function messageToMarkup (control: TriggerControl, message: Activit
return undefined
}
export class NotificationProviderControl {
public byProvider: Map<Ref<NotificationProvider>, NotificationProviderSetting[]>
public settingsByProvider: Map<Ref<NotificationProvider>, NotificationTypeSetting[]>
constructor (
readonly providersSettings: NotificationProviderSetting[],
readonly typesSettings: NotificationTypeSetting[]
) {
this.byProvider = groupByArray(providersSettings, (it) => it.attachedTo)
this.settingsByProvider = groupByArray(typesSettings, (it) => it.attachedTo)
}
}
const notificationProvidersKey = 'notification_provider_settings'
const typesSettingsKey = 'notification_type_settings'
export async function getNotificationProviderControl (
ctx: MeasureContext,
control: TriggerControl
): Promise<NotificationProviderControl> {
let providersSettings: NotificationProviderSetting[] = control.contextCache.get(notificationProvidersKey)
if (providersSettings === undefined) {
providersSettings = await control.queryFind(ctx, notification.class.NotificationProviderSetting, {
space: core.space.Workspace
})
control.contextCache.set(notificationProvidersKey, providersSettings)
}
let typesSettings: NotificationTypeSetting[] = control.contextCache.get(typesSettingsKey)
if (typesSettings === undefined) {
typesSettings = await control.queryFind(ctx, notification.class.NotificationTypeSetting, {
space: core.space.Workspace
})
control.contextCache.set(typesSettingsKey, typesSettings)
}
return new NotificationProviderControl(providersSettings, typesSettings)
}

View File

@ -14,6 +14,7 @@
// limitations under the License.
//
import { ActivityMessage } from '@hcengineering/activity'
import contact, { Employee, Person, PersonAccount, PersonSpace } from '@hcengineering/contact'
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
import {
@ -25,7 +26,6 @@ import {
} from '@hcengineering/notification'
import { Metadata, Plugin, Resource, plugin } from '@hcengineering/platform'
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
import { ActivityMessage } from '@hcengineering/activity'
/**
* @public
@ -35,37 +35,13 @@ export const serverNotificationId = 'server-notification' as Plugin
/**
* @public
*/
export async function getPersonAccount (
person: Ref<Person>,
control: TriggerControl
): Promise<PersonAccount | undefined> {
const account = (
await control.modelDb.findAll(
contact.class.PersonAccount,
{
person
},
{ limit: 1 }
)
)[0]
return account
}
/**
* @public
*/
export async function getPersonAccountById (
_id: Ref<Account>,
control: TriggerControl
): Promise<PersonAccount | undefined> {
const account = (
await control.modelDb.findAll(
contact.class.PersonAccount,
{
_id: _id as Ref<PersonAccount>
},
{ limit: 1 }
)
export function getPersonAccountById (_id: Ref<Account>, control: TriggerControl): PersonAccount | undefined {
const account = control.modelDb.findAllSync(
contact.class.PersonAccount,
{
_id: _id as Ref<PersonAccount>
},
{ limit: 1 }
)[0]
return account
}
@ -109,7 +85,7 @@ export interface TextPresenter<T extends Doc = any> extends Class<T> {
* @public
*/
export type TypeMatchFunc = Resource<
(tx: Tx, doc: Doc, user: Ref<Account>, type: NotificationType, control: TriggerControl) => Promise<boolean>
(tx: Tx, doc: Doc, user: Ref<Account>[], type: NotificationType, control: TriggerControl) => boolean
>
/**
@ -192,7 +168,7 @@ export default plugin(serverNotificationId, {
OnDocRemove: '' as Resource<TriggerFunc>
},
function: {
IsUserInFieldValue: '' as TypeMatchFunc,
IsUserEmployeeInFieldValue: '' as TypeMatchFunc
IsUserInFieldValueTypeMatch: '' as TypeMatchFunc,
IsUserEmployeeInFieldValueTypeMatch: '' as TypeMatchFunc
}
})

View File

@ -13,7 +13,17 @@
// limitations under the License.
//
import core, { Doc, Tx, TxCUD, TxCollectionCUD, TxCreateDoc, TxUpdateDoc, TxProcessor, Ref } from '@hcengineering/core'
import core, {
Doc,
Tx,
TxCUD,
TxCollectionCUD,
TxCreateDoc,
TxUpdateDoc,
TxProcessor,
Ref,
type MeasureContext
} from '@hcengineering/core'
import request, { Request, RequestStatus } from '@hcengineering/request'
import { getResource, translate } from '@hcengineering/platform'
import type { TriggerControl } from '@hcengineering/server-core'
@ -25,7 +35,8 @@ import {
getCollaborators,
getTextPresenter,
getUsersInfo,
toReceiverInfo
toReceiverInfo,
getNotificationProviderControl
} from '@hcengineering/server-notification-resources'
import { PersonAccount } from '@hcengineering/contact'
@ -46,7 +57,7 @@ export async function OnRequest (tx: Tx, control: TriggerControl): Promise<Tx[]>
let res: Tx[] = []
res = res.concat(await getRequestNotificationTx(ptx, control))
res = res.concat(await getRequestNotificationTx(control.ctx, ptx, control))
if (ptx.tx._class === core.class.TxUpdateDoc) {
res = res.concat(await OnRequestUpdate(ptx, control))
@ -121,7 +132,11 @@ async function getRequest (tx: TxCUD<Request>, control: TriggerControl): Promise
}
// We need request-specific logic to attach a activity message on request create/update to parent, but use request collaborators for notifications
async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, control: TriggerControl): Promise<Tx[]> {
async function getRequestNotificationTx (
ctx: MeasureContext,
tx: TxCollectionCUD<Doc, Request>,
control: TriggerControl
): Promise<Tx[]> {
const request = await getRequest(tx.tx, control)
if (request === undefined) return []
@ -140,7 +155,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
const messages = messagesTxes.map((messageTx) =>
TxProcessor.createDoc2Doc(messageTx.tx as TxCreateDoc<DocUpdateMessage>)
)
const collaborators = await getCollaborators(request, control, tx.tx, res)
const collaborators = await getCollaborators(control.ctx, request, control, tx.tx, res)
if (collaborators.length === 0) return res
@ -148,18 +163,18 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
objectId: doc._id
})
const usersInfo = await getUsersInfo(control.ctx, [...collaborators, tx.modifiedBy] as Ref<PersonAccount>[], control)
const senderInfo = usersInfo.find(({ _id }) => _id === tx.modifiedBy) ?? {
const senderInfo = usersInfo.get(tx.modifiedBy) ?? {
_id: tx.modifiedBy
}
const notificationControl = await getNotificationProviderControl(ctx, control)
for (const target of collaborators) {
const targetInfo = toReceiverInfo(
control.hierarchy,
usersInfo.find(({ _id }) => _id === target)
)
const targetInfo = toReceiverInfo(control.hierarchy, usersInfo.get(target))
if (targetInfo === undefined) continue
const txes = await getNotificationTxes(
ctx,
control,
request,
tx.tx,
@ -168,7 +183,8 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
senderInfo,
{ isOwn: true, isSpace: false, shouldUpdateTimestamp: true },
notifyContexts,
messages
messages,
notificationControl
)
res.push(...txes)
}

View File

@ -90,13 +90,13 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
/**
* @public
*/
export async function IsIncomingMessage (
export function IsIncomingMessageTypeMatch (
tx: Tx,
doc: Doc,
user: Ref<Account>,
user: Ref<Account>[],
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
): boolean {
const message = TxProcessor.createDoc2Doc(TxProcessor.extractTx(tx) as TxCreateDoc<TelegramMessage>)
return message.incoming && message.sendOn > (doc.createdOn ?? doc.modifiedOn)
}
@ -280,7 +280,7 @@ export default async () => ({
OnMessageCreate
},
function: {
IsIncomingMessage,
IsIncomingMessageTypeMatch,
FindMessages,
GetCurrentEmployeeTG,
GetIntegrationOwnerTG,

View File

@ -35,7 +35,7 @@ export default plugin(serverTelegramId, {
OnMessageCreate: '' as Resource<TriggerFunc>
},
function: {
IsIncomingMessage: '' as TypeMatchFunc,
IsIncomingMessageTypeMatch: '' as TypeMatchFunc,
FindMessages: '' as Resource<ObjectDDParticipantFunc>,
GetCurrentEmployeeTG: '' as Resource<TemplateFieldServerFunc>,
GetIntegrationOwnerTG: '' as Resource<TemplateFieldServerFunc>,

View File

@ -34,17 +34,18 @@ import core, {
import notification, { CommonInboxNotification } from '@hcengineering/notification'
import { getResource } from '@hcengineering/platform'
import type { TriggerControl } from '@hcengineering/server-core'
import { ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
import {
getCommonNotificationTxes,
getNotificationContent,
getNotificationProviderControl,
isShouldNotifyTx
} from '@hcengineering/server-notification-resources'
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
import task, { makeRank } from '@hcengineering/task'
import { jsonToMarkup, nodeDoc, nodeParagraph, nodeText } from '@hcengineering/text'
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
import { ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
/**
* @public
@ -179,9 +180,9 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
}
const todo = TxProcessor.createDoc2Doc(createTx)
const account = await getPersonAccount(todo.user, control)
const account = control.modelDb.getAccountByPersonId(todo.user) as PersonAccount[]
if (account === undefined) {
if (account.length === 0) {
return []
}
@ -189,23 +190,24 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
if (object === undefined) return []
const person = (
await control.findAll(contact.mixin.Employee, { _id: account.person as Ref<Employee>, active: true }, { limit: 1 })
await control.findAll(contact.mixin.Employee, { _id: todo.user as Ref<Employee>, active: true }, { limit: 1 })
)[0]
if (person === undefined) return []
const personSpace = (await control.findAll(contact.class.PersonSpace, { person: account.person }, { limit: 1 }))[0]
const personSpace = (await control.findAll(contact.class.PersonSpace, { person: todo.user }, { limit: 1 }))[0]
if (personSpace === undefined) return []
// TODO: Select a proper account
const receiverInfo: ReceiverInfo = {
_id: account._id,
account,
_id: account[0]._id,
account: account[0],
person,
space: personSpace._id
}
const senderAccount = await control.modelDb.findOne(contact.class.PersonAccount, {
const senderAccount = control.modelDb.findAllSync(contact.class.PersonAccount, {
_id: tx.modifiedBy as Ref<PersonAccount>
})
})[0]
const senderPerson =
senderAccount !== undefined
? (await control.findAll(contact.class.Person, { _id: senderAccount.person }))[0]
@ -216,8 +218,8 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
account: senderAccount,
person: senderPerson
}
const notifyResult = await isShouldNotifyTx(control, createTx, tx, todo, account, true, false)
const notificationControl = await getNotificationProviderControl(control.ctx, control)
const notifyResult = await isShouldNotifyTx(control, createTx, tx, todo, account, true, false, notificationControl)
const content = await getNotificationContent(tx, account, senderInfo, todo, control)
const data: Partial<Data<CommonInboxNotification>> = {
...content,
@ -229,6 +231,7 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
}
const txes = await getCommonNotificationTxes(
control.ctx,
control,
object,
data,
@ -465,31 +468,18 @@ async function createIssueHandler (issue: Issue, control: TriggerControl): Promi
return []
}
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
const account = (
await control.modelDb.findAll(
contact.class.PersonAccount,
{
person
},
{ limit: 1 }
)
)[0]
return account
}
async function getIssueToDoData (
issue: Issue,
user: Ref<Person>,
control: TriggerControl
): Promise<AttachedData<ProjectToDo> | undefined> {
const acc = await getPersonAccount(user, control)
if (acc === undefined) return
const acc = control.modelDb.getAccountByPersonId(user) as PersonAccount[]
if (acc.length === 0) return
const firstTodoItem = (
await control.findAll(
time.class.ToDo,
{
user: acc.person,
user: { $in: acc.map((it) => it.person) },
doneOn: null
},
{
@ -506,7 +496,7 @@ async function getIssueToDoData (
priority: ToDoPriority.NoPriority,
visibility: 'public',
title: issue.title,
user: acc.person,
user,
rank
}
return data

View File

@ -13,25 +13,25 @@
// limitations under the License.
//
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
import { type Account, type Ref, type TxCUD } from '@hcengineering/core'
import type { NotificationType } from '@hcengineering/notification'
import type { TriggerControl } from '@hcengineering/server-core'
import type { TrainingRequest } from '@hcengineering/training'
import contact, { type Employee, type PersonAccount } from '@hcengineering/contact'
import { isTxCreateDoc } from '../utils/isTxCreateDoc'
import { isTxUpdateDoc } from '../utils/isTxUpdateDoc'
export async function TrainingRequestNotificationTypeMatch (
export function TrainingRequestNotificationTypeMatch (
tx: TxCUD<TrainingRequest>,
doc: TrainingRequest,
user: Ref<Account>,
user: Ref<Account>[],
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
): boolean {
if (isTxCreateDoc(tx)) {
const employeeRef: Ref<Employee> = (
await control.modelDb.findAll(contact.class.PersonAccount, { _id: user as Ref<PersonAccount> })
)[0]?.person as Ref<Employee>
const employeeRef: Ref<Employee> = control.modelDb.findAllSync(contact.class.PersonAccount, {
_id: user[0] as Ref<PersonAccount>
})[0]?.person as Ref<Employee>
return doc.trainees.includes(employeeRef)
}
@ -41,9 +41,9 @@ export async function TrainingRequestNotificationTypeMatch (
return false
}
const employeeRef: Ref<Employee> = (
await control.modelDb.findAll(contact.class.PersonAccount, { _id: user as Ref<PersonAccount> })
)[0]?.person as Ref<Employee>
const employeeRef: Ref<Employee> = control.modelDb.findAllSync(contact.class.PersonAccount, {
_id: user[0] as Ref<PersonAccount>
})[0]?.person as Ref<Employee>
const newTrainees = typeof pushed === 'object' ? pushed.$each : [pushed]
return newTrainees.includes(employeeRef)
}

View File

@ -1983,7 +1983,7 @@ async function createPersonAccount (
client ??
(await connect(getEndpoint(ctx, workspaceInfo, EndpointKind.Internal), getWorkspaceId(workspace, productId)))
try {
const ops = new TxOperations(connection, core.account.System)
const ops = new TxOperations(connection, core.account.System).apply('create-person' + generateId())
const name = combineName(account.first, account.last)
// Check if PersonAccount is not exists
@ -2033,6 +2033,7 @@ async function createPersonAccount (
}
}
}
await ops.commit()
} finally {
if (client === undefined) {
await connection.close()

View File

@ -879,12 +879,10 @@ export class TServerStorage implements ServerStorage {
ctx: SessionOperationContext,
findAll: ServerStorage['findAll']
): Promise<Tx[]> {
derived.sort((a, b) => a.modifiedOn - b.modifiedOn)
await ctx.with('derived-route-tx', {}, (ctx) => this.routeTx(ctx, ...derived))
const nestedTxes: Tx[] = []
if (derived.length > 0) {
derived.sort((a, b) => a.modifiedOn - b.modifiedOn)
await ctx.with('derived-route-tx', {}, (ctx) => this.routeTx(ctx, ...derived))
nestedTxes.push(...(await this.processDerived(ctx, derived, findAll)))
}
@ -992,6 +990,7 @@ export class TServerStorage implements ServerStorage {
const onEnds: (() => void)[] = []
const result: TxResult[] = []
const derived: Tx[] = [...txes].filter((it) => it._class !== core.class.TxApplyIf)
let txPromise: Promise<TxResult[]> | undefined
try {
this.fillTxes(txes, txToStore, modelTx, txToProcess, applyTxes)
@ -1023,7 +1022,7 @@ export class TServerStorage implements ServerStorage {
await this.modelDb.tx(tx)
}
if (txToStore.length > 0) {
await ctx.with(
txPromise = ctx.with(
'domain-tx',
{},
async (ctx) => await this.getAdapter(DOMAIN_TX, true).tx(ctx.ctx, ...txToStore),
@ -1034,9 +1033,8 @@ export class TServerStorage implements ServerStorage {
)
}
if (txToProcess.length > 0) {
result.push(...(await ctx.with('routeTx', {}, (ctx) => this.routeTx(ctx, ...txToProcess))), {
count: txToProcess.length
})
const routerResult = await ctx.with('routeTx', {}, (ctx) => this.routeTx(ctx, ...txToProcess))
result.push(...routerResult, { count: txToProcess.length })
// invoke triggers and store derived objects
derived.push(...(await this.processDerived(ctx, txToProcess, _findAll)))
}
@ -1053,6 +1051,10 @@ export class TServerStorage implements ServerStorage {
{ count: txToProcess.length + derived.length }
)
}
if (txPromise !== undefined) {
// Wait for main Tx to be stored
await txPromise
}
} catch (err: any) {
ctx.ctx.error('error process tx', { error: err })
Analytics.handleError(err)

View File

@ -16,7 +16,6 @@
import core, {
DOMAIN_MODEL,
DOMAIN_TX,
RateLimiter,
SortingOrder,
TxProcessor,
addOperation,
@ -76,6 +75,7 @@ import { createHash } from 'crypto'
import {
type AbstractCursor,
type AnyBulkWriteOperation,
type BulkWriteResult,
type Collection,
type Db,
type Document,
@ -141,9 +141,6 @@ export interface DbAdapterOptions {
abstract class MongoAdapterBase implements DbAdapter {
_db: DBCollectionHelper
findRateLimit = new RateLimiter(parseInt(process.env.FIND_RLIMIT ?? '1000'))
rateLimit = new RateLimiter(parseInt(process.env.TX_RLIMIT ?? '5'))
handlers: DbAdapterHandler[] = []
on (handler: DbAdapterHandler): void {
@ -706,130 +703,119 @@ abstract class MongoAdapterBase implements DbAdapter {
const stTime = Date.now()
const mongoQuery = this.translateQuery(_class, query, options)
const fQuery = { ...mongoQuery.base, ...mongoQuery.lookup }
return await addOperation(
ctx,
'find-all',
{},
async () =>
await this.findRateLimit.exec(async () => {
const st = Date.now()
let result: FindResult<T>
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
if (
options != null &&
(options?.lookup != null || this.isEnumSort(_class, options) || this.isRulesSort(options))
) {
return await this.findWithPipeline(ctx, domain, _class, query, options, stTime)
}
const coll = this.collection(domain)
if (options?.limit === 1 || typeof query._id === 'string') {
// Skip sort/projection/etc.
return await ctx.with(
'find-one',
{ domain },
async (ctx) => {
const findOptions: MongoFindOptions = {}
if (options?.sort !== undefined) {
findOptions.sort = this.collectSort<T>(options, _class)
}
if (options?.projection !== undefined) {
findOptions.projection = this.calcProjection<T>(options, _class)
}
let doc: WithId<Document> | null
if (typeof fQuery._id === 'string') {
doc = await coll.findOne({ _id: fQuery._id }, findOptions)
if (doc != null && matchQuery([doc as unknown as Doc], query, _class, this.hierarchy).length === 0) {
doc = null
}
} else {
doc = await coll.findOne(fQuery, findOptions)
}
let total = -1
if (options?.total === true) {
total = await coll.countDocuments({ ...mongoQuery.base, ...mongoQuery.lookup })
}
if (doc != null) {
return toFindResult([this.stripHash<T>(doc as unknown as T) as T], total)
}
return toFindResult([], total)
},
{ domain, mongoQuery, _idOnly: typeof fQuery._id === 'string' }
)
}
let cursor = coll.find<T>(fQuery)
if (options?.projection !== undefined) {
const projection = this.calcProjection<T>(options, _class)
if (projection != null) {
cursor = cursor.project(projection)
}
}
let total: number = -1
if (options != null) {
if (options.sort !== undefined) {
const sort = this.collectSort<T>(options, _class)
if (sort !== undefined) {
cursor = cursor.sort(sort)
}
}
if (options.limit !== undefined || typeof query._id === 'string') {
if (options.total === true) {
total = await coll.countDocuments(fQuery)
}
cursor = cursor.limit(options.limit ?? 1)
}
}
// Error in case of timeout
try {
let res: T[] = []
await ctx.with(
'find-all',
{},
async (ctx) => {
res = await toArray(cursor)
},
() => ({
size: res.length,
queueTime: stTime - st,
mongoQuery,
options,
domain
})
)
if (options?.total === true && options?.limit === undefined) {
total = res.length
}
result = toFindResult(this.stripHash(res) as T[], total)
} catch (e) {
console.error('error during executing cursor in findAll', _class, cutObjectArray(query), options, e)
throw e
}
const edTime = Date.now()
if (edTime - st > 1000 || st - stTime > 1000) {
ctx.error('FindAll', {
time: edTime - st,
_class,
query: fQuery,
options,
queueTime: st - stTime
})
}
this.handleEvent(domain, 'read', result.length)
return result
}),
{
_class,
query: fQuery,
options
return await addOperation(ctx, 'find-all', {}, async () => {
const st = Date.now()
let result: FindResult<T>
const domain = options?.domain ?? this.hierarchy.getDomain(_class)
if (
options != null &&
(options?.lookup != null || this.isEnumSort(_class, options) || this.isRulesSort(options))
) {
return await this.findWithPipeline(ctx, domain, _class, query, options, stTime)
}
)
const coll = this.collection(domain)
if (options?.limit === 1 || typeof query._id === 'string') {
// Skip sort/projection/etc.
return await ctx.with(
'find-one',
{ domain },
async (ctx) => {
const findOptions: MongoFindOptions = {}
if (options?.sort !== undefined) {
findOptions.sort = this.collectSort<T>(options, _class)
}
if (options?.projection !== undefined) {
findOptions.projection = this.calcProjection<T>(options, _class)
}
let doc: WithId<Document> | null
if (typeof fQuery._id === 'string') {
doc = await coll.findOne({ _id: fQuery._id }, findOptions)
if (doc != null && matchQuery([doc as unknown as Doc], query, _class, this.hierarchy).length === 0) {
doc = null
}
} else {
doc = await coll.findOne(fQuery, findOptions)
}
let total = -1
if (options?.total === true) {
total = await coll.countDocuments({ ...mongoQuery.base, ...mongoQuery.lookup })
}
if (doc != null) {
return toFindResult([this.stripHash<T>(doc as unknown as T) as T], total)
}
return toFindResult([], total)
},
{ domain, mongoQuery, _idOnly: typeof fQuery._id === 'string' }
)
}
let cursor = coll.find<T>(fQuery)
if (options?.projection !== undefined) {
const projection = this.calcProjection<T>(options, _class)
if (projection != null) {
cursor = cursor.project(projection)
}
}
let total: number = -1
if (options != null) {
if (options.sort !== undefined) {
const sort = this.collectSort<T>(options, _class)
if (sort !== undefined) {
cursor = cursor.sort(sort)
}
}
if (options.limit !== undefined || typeof query._id === 'string') {
if (options.total === true) {
total = await coll.countDocuments(fQuery)
}
cursor = cursor.limit(options.limit ?? 1)
}
}
// Error in case of timeout
try {
let res: T[] = []
await ctx.with(
'find-all',
{},
async (ctx) => {
res = await toArray(cursor)
},
() => ({
size: res.length,
queueTime: stTime - st,
mongoQuery,
options,
domain
})
)
if (options?.total === true && options?.limit === undefined) {
total = res.length
}
result = toFindResult(this.stripHash(res) as T[], total)
} catch (e) {
console.error('error during executing cursor in findAll', _class, cutObjectArray(query), options, e)
throw e
}
const edTime = Date.now()
if (edTime - st > 1000 || st - stTime > 1000) {
ctx.error('FindAll', {
time: edTime - st,
_class,
query: fQuery,
options,
queueTime: st - stTime
})
}
this.handleEvent(domain, 'read', result.length)
return result
})
}
private collectSort<T extends Doc>(
@ -1129,13 +1115,14 @@ class MongoAdapter extends MongoAdapterBase {
if (bulk.length === 0) {
return
}
const promises: Promise<BulkWriteResult>[] = []
for (const [domain, ops] of bulk) {
if (ops === undefined || ops.length === 0) {
continue
}
const coll = this.db.collection<Doc>(domain)
await this.rateLimit.exec(() =>
promises.push(
addOperation(
ctx,
'bulk-write',
@ -1146,8 +1133,7 @@ class MongoAdapter extends MongoAdapterBase {
{ domain },
() =>
coll.bulkWrite(ops, {
ordered: false,
writeConcern: { w: 0 }
ordered: false
}),
{
domain,
@ -1157,6 +1143,7 @@ class MongoAdapter extends MongoAdapterBase {
)
)
}
await Promise.all(promises)
}
async pushBulk (ctx: MeasureContext, domain: Domain, ops: AnyBulkWriteOperation<Doc>[]): Promise<void> {
@ -1166,6 +1153,8 @@ class MongoAdapter extends MongoAdapterBase {
} else {
this.bulkOps.set(domain, ops)
}
// We need to wait next cycle to send request
await new Promise<void>((resolve) => setImmediate(resolve))
await this._pushBulk(ctx)
}
@ -1528,6 +1517,10 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
const txes = this.txBulk
this.txBulk = []
if (txes.length === 0) {
return
}
const opName = txes.length === 1 ? 'tx-one' : 'tx'
await addOperation(
ctx,
@ -1541,8 +1534,7 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
this.txCollection().insertMany(
txes.map((it) => translateDoc(it)),
{
ordered: false,
writeConcern: { w: 0 }
ordered: false
}
),
{
@ -1561,6 +1553,9 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
return []
}
this.txBulk.push(...tx)
// We need to wait next cycle to send request
await new Promise<void>((resolve) => setImmediate(resolve))
await this._bulkTx(ctx)
return []
}

View File

@ -27,8 +27,8 @@
"compression-webpack-plugin": "^10.0.0",
"html-webpack-plugin": "^5.5.0",
"fork-ts-checker-webpack-plugin": "~7.3.0",
"update-browserslist-db": "~1.0.11",
"browserslist": "4.21.5",
"update-browserslist-db": "^1.1.0",
"browserslist": "^4.23.3",
"esbuild": "^0.20.0",
"esbuild-loader": "^4.0.3",
"typescript": "^5.3.3",

View File

@ -27,6 +27,9 @@ export class ChannelPage extends CommonPage {
readonly chooseChannel = (channel: string): Locator => this.page.getByRole('button', { name: channel })
readonly closePopupWindow = (): Locator => this.page.locator('.notifyPopup button[data-id="btnNotifyClose"]')
readonly openAddMemberToChannel = (userName: string): Locator => this.page.getByRole('button', { name: userName })
readonly addMemberToChannelTableButton = (userName: string): Locator =>
this.page.locator('.antiTable-body__row').getByText(userName)
readonly addMemberToChannelButton = (userName: string): Locator => this.page.getByText(userName)
readonly joinChannelButton = (): Locator => this.page.getByRole('button', { name: 'Join' })
readonly addEmojiButton = (): Locator =>
@ -204,7 +207,7 @@ export class ChannelPage extends CommonPage {
}
async clickOnUser (user: string): Promise<void> {
await this.addMemberToChannelButton(user).click()
await this.addMemberToChannelTableButton(user).click()
}
async addMemberToChannel (user: string): Promise<void> {