mirror of
https://github.com/hcengineering/platform.git
synced 2024-12-22 11:01:54 +03:00
Support Bitrix Lead import (#2445)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
fac40733f4
commit
27b003f35f
@ -17,6 +17,9 @@ specifiers:
|
||||
'@rush-temp/automation': file:./projects/automation.tgz
|
||||
'@rush-temp/automation-assets': file:./projects/automation-assets.tgz
|
||||
'@rush-temp/automation-resources': file:./projects/automation-resources.tgz
|
||||
'@rush-temp/bitrix': file:./projects/bitrix.tgz
|
||||
'@rush-temp/bitrix-assets': file:./projects/bitrix-assets.tgz
|
||||
'@rush-temp/bitrix-resources': file:./projects/bitrix-resources.tgz
|
||||
'@rush-temp/board': file:./projects/board.tgz
|
||||
'@rush-temp/board-assets': file:./projects/board-assets.tgz
|
||||
'@rush-temp/board-resources': file:./projects/board-resources.tgz
|
||||
@ -70,6 +73,7 @@ specifiers:
|
||||
'@rush-temp/model-all': file:./projects/model-all.tgz
|
||||
'@rush-temp/model-attachment': file:./projects/model-attachment.tgz
|
||||
'@rush-temp/model-automation': file:./projects/model-automation.tgz
|
||||
'@rush-temp/model-bitrix': file:./projects/model-bitrix.tgz
|
||||
'@rush-temp/model-board': file:./projects/model-board.tgz
|
||||
'@rush-temp/model-calendar': file:./projects/model-calendar.tgz
|
||||
'@rush-temp/model-chunter': file:./projects/model-chunter.tgz
|
||||
@ -238,6 +242,7 @@ specifiers:
|
||||
'@types/minio': ~7.0.11
|
||||
'@types/node': ~16.11.12
|
||||
'@types/pdfkit': ~0.12.3
|
||||
'@types/qs': ~6.9.7
|
||||
'@types/request': ~2.48.8
|
||||
'@types/sharp': ~0.30.4
|
||||
'@types/tar-stream': ^2.2.2
|
||||
@ -296,6 +301,7 @@ specifiers:
|
||||
mini-css-extract-plugin: ^2.2.0
|
||||
minio: ^7.0.26
|
||||
mongodb: ^4.11.0
|
||||
p-queue: ~7.3.0
|
||||
pdfkit: ~0.13.0
|
||||
postcss: ^8.3.4
|
||||
postcss-load-config: ^3.1.0
|
||||
@ -308,6 +314,7 @@ specifiers:
|
||||
prosemirror-state: ~1.4.1
|
||||
prosemirror-transform: ~1.7.0
|
||||
prosemirror-view: ~1.29.0
|
||||
qs: ~6.11.0
|
||||
rfc6902: ~5.0.1
|
||||
sass: ^1.53.0
|
||||
sass-loader: ^12.1.0
|
||||
@ -354,6 +361,9 @@ dependencies:
|
||||
'@rush-temp/automation': file:projects/automation.tgz
|
||||
'@rush-temp/automation-assets': file:projects/automation-assets.tgz_typescript@4.8.4
|
||||
'@rush-temp/automation-resources': file:projects/automation-resources.tgz_49b4785992daa3b61a639b2b31601e76
|
||||
'@rush-temp/bitrix': file:projects/bitrix.tgz
|
||||
'@rush-temp/bitrix-assets': file:projects/bitrix-assets.tgz_typescript@4.8.4
|
||||
'@rush-temp/bitrix-resources': file:projects/bitrix-resources.tgz_49b4785992daa3b61a639b2b31601e76
|
||||
'@rush-temp/board': file:projects/board.tgz
|
||||
'@rush-temp/board-assets': file:projects/board-assets.tgz_typescript@4.8.4
|
||||
'@rush-temp/board-resources': file:projects/board-resources.tgz_49b4785992daa3b61a639b2b31601e76
|
||||
@ -407,6 +417,7 @@ dependencies:
|
||||
'@rush-temp/model-all': file:projects/model-all.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-attachment': file:projects/model-attachment.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-automation': file:projects/model-automation.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-bitrix': file:projects/model-bitrix.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-board': file:projects/model-board.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-calendar': file:projects/model-calendar.tgz_typescript@4.8.4
|
||||
'@rush-temp/model-chunter': file:projects/model-chunter.tgz_typescript@4.8.4
|
||||
@ -575,6 +586,7 @@ dependencies:
|
||||
'@types/minio': 7.0.14
|
||||
'@types/node': 16.11.68
|
||||
'@types/pdfkit': 0.12.8
|
||||
'@types/qs': 6.9.7
|
||||
'@types/request': 2.48.8
|
||||
'@types/sharp': 0.30.5
|
||||
'@types/tar-stream': 2.2.2
|
||||
@ -633,6 +645,7 @@ dependencies:
|
||||
mini-css-extract-plugin: 2.6.1_webpack@5.75.0
|
||||
minio: 7.0.32
|
||||
mongodb: 4.11.0
|
||||
p-queue: 7.3.0
|
||||
pdfkit: 0.13.0
|
||||
postcss: 8.4.19
|
||||
postcss-load-config: 3.1.4_postcss@8.4.19+ts-node@10.9.1
|
||||
@ -645,6 +658,7 @@ dependencies:
|
||||
prosemirror-state: 1.4.2
|
||||
prosemirror-transform: 1.7.0
|
||||
prosemirror-view: 1.29.0
|
||||
qs: 6.11.0
|
||||
rfc6902: 5.0.1
|
||||
sass: 1.56.1
|
||||
sass-loader: 12.6.0_sass@1.56.1+webpack@5.75.0
|
||||
@ -676,6 +690,21 @@ dependencies:
|
||||
|
||||
packages:
|
||||
|
||||
/@2bad/bitrix/2.5.0:
|
||||
resolution: {integrity: sha512-AXlbCsn5NV1x8H982JHk2yyNU/vtTBx34a6DdtqqgHQEYd6nfEVnHEhUF/yAmElV1/rtjubOtKVxwVdFUrwLDA==}
|
||||
dependencies:
|
||||
'@types/lodash.chunk': 4.2.7
|
||||
'@types/lodash.frompairs': 4.0.7
|
||||
'@types/lodash.range': 3.2.7
|
||||
'@types/qs': 6.9.7
|
||||
got: 11.8.2
|
||||
lodash.chunk: 4.2.0
|
||||
lodash.frompairs: 4.0.1
|
||||
lodash.range: 3.2.0
|
||||
p-queue: 6.6.2
|
||||
qs: 6.11.0
|
||||
dev: false
|
||||
|
||||
/@_ueberdosis/prosemirror-tables/1.1.3:
|
||||
resolution: {integrity: sha512-su3pbFi1DT89g6Cuh72TE0MWWKHmWgHcQJ3ODRkm6XfIppWaGpU49t02ur3sgJc7hUhfQXjB93aSkDgOmIii2w==}
|
||||
dependencies:
|
||||
@ -3091,6 +3120,28 @@ packages:
|
||||
'@types/koa': 2.13.5
|
||||
dev: false
|
||||
|
||||
/@types/lodash.chunk/4.2.7:
|
||||
resolution: {integrity: sha512-//tmaWHiANgToom/YYYKKqiCtlNz11fwYtMUUbaemNSbWTI+2zHtYW5nt1PHNCRWHPAJHHhn4UVFD9LKUFvatA==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.191
|
||||
dev: false
|
||||
|
||||
/@types/lodash.frompairs/4.0.7:
|
||||
resolution: {integrity: sha512-7UeH2+GF9yop4AqnPwae5/2TE+eY0WRDy0RRQtNGHjzIgdUhilRskMXvXqUcCSazvbkxasjqydXrIE1OB6bPKA==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.191
|
||||
dev: false
|
||||
|
||||
/@types/lodash.range/3.2.7:
|
||||
resolution: {integrity: sha512-sq3eRZWspMKGAE0QLH+tIE/EWeSEVKlFqaUoqmgR/c4keXiTP8ob24TUzclYKihw/ojftz6vBb632NRmxR4qrQ==}
|
||||
dependencies:
|
||||
'@types/lodash': 4.14.191
|
||||
dev: false
|
||||
|
||||
/@types/lodash/4.14.191:
|
||||
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
|
||||
dev: false
|
||||
|
||||
/@types/mime-types/2.1.1:
|
||||
resolution: {integrity: sha512-vXOTGVSLR2jMw440moWTC7H19iUyLtP3Z1YTj7cSsubOICinjMxFeb/V57v9QdyyPGbbWolUFSSmSiRSn94tFw==}
|
||||
dev: false
|
||||
@ -6354,6 +6405,23 @@ packages:
|
||||
get-intrinsic: 1.1.3
|
||||
dev: false
|
||||
|
||||
/got/11.8.2:
|
||||
resolution: {integrity: sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==}
|
||||
engines: {node: '>=10.19.0'}
|
||||
dependencies:
|
||||
'@sindresorhus/is': 4.6.0
|
||||
'@szmarczak/http-timer': 4.0.6
|
||||
'@types/cacheable-request': 6.0.3
|
||||
'@types/responselike': 1.0.0
|
||||
cacheable-lookup: 5.0.4
|
||||
cacheable-request: 7.0.2
|
||||
decompress-response: 6.0.0
|
||||
http2-wrapper: 1.0.3
|
||||
lowercase-keys: 2.0.0
|
||||
p-cancelable: 2.1.1
|
||||
responselike: 2.0.1
|
||||
dev: false
|
||||
|
||||
/got/11.8.5:
|
||||
resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
|
||||
engines: {node: '>=10.19.0'}
|
||||
@ -7994,10 +8062,18 @@ packages:
|
||||
p-locate: 5.0.0
|
||||
dev: false
|
||||
|
||||
/lodash.chunk/4.2.0:
|
||||
resolution: {integrity: sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==}
|
||||
dev: false
|
||||
|
||||
/lodash.debounce/4.0.8:
|
||||
resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
|
||||
dev: false
|
||||
|
||||
/lodash.frompairs/4.0.1:
|
||||
resolution: {integrity: sha512-dvqe2I+cO5MzXCMhUnfYFa9MD+/760yx2aTAN1lqEcEkf896TxgrX373igVdqSJj6tQd0jnSLE1UMuKufqqxFw==}
|
||||
dev: false
|
||||
|
||||
/lodash.get/4.4.2:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
dev: false
|
||||
@ -8010,6 +8086,10 @@ packages:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: false
|
||||
|
||||
/lodash.range/3.2.0:
|
||||
resolution: {integrity: sha512-Fgkb7SinmuzqgIhNhAElo0BL/R1rHCnhwSZf78omqSwvWqD0kD2ssOAutQonDKH/ldS8BxA72ORYI09qAY9CYg==}
|
||||
dev: false
|
||||
|
||||
/lodash/4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: false
|
||||
@ -8595,6 +8675,11 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: false
|
||||
|
||||
/p-finally/1.0.0:
|
||||
resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/p-limit/2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
@ -8623,6 +8708,22 @@ packages:
|
||||
p-limit: 3.1.0
|
||||
dev: false
|
||||
|
||||
/p-queue/6.6.2:
|
||||
resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
p-timeout: 3.2.0
|
||||
dev: false
|
||||
|
||||
/p-queue/7.3.0:
|
||||
resolution: {integrity: sha512-5fP+yVQ0qp0rEfZoDTlP2c3RYBgxvRsw30qO+VtPPc95lyvSG+x6USSh1TuLB4n96IO6I8/oXQGsTgtna4q2nQ==}
|
||||
engines: {node: '>=12'}
|
||||
dependencies:
|
||||
eventemitter3: 4.0.7
|
||||
p-timeout: 5.1.0
|
||||
dev: false
|
||||
|
||||
/p-retry/4.6.2:
|
||||
resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==}
|
||||
engines: {node: '>=8'}
|
||||
@ -8631,6 +8732,18 @@ packages:
|
||||
retry: 0.13.1
|
||||
dev: false
|
||||
|
||||
/p-timeout/3.2.0:
|
||||
resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==}
|
||||
engines: {node: '>=8'}
|
||||
dependencies:
|
||||
p-finally: 1.0.0
|
||||
dev: false
|
||||
|
||||
/p-timeout/5.1.0:
|
||||
resolution: {integrity: sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==}
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/p-try/2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
@ -11139,7 +11252,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/account.tgz:
|
||||
resolution: {integrity: sha512-QSlfOq1M5zdpWg0tNGWBu3VegEZkkPLQG2gim+GXAC7vEQd7vRsWXTMbKntwOdkovV29NDNsIcUWp6G5DUfFRg==, tarball: file:projects/account.tgz}
|
||||
resolution: {integrity: sha512-Cu4HQ37cZjY4LU7x7/btaVEfxIjb+rKA9hll64ZloH41F54jembIyQHHufIMatcMOnPFivsRZ4P9oj7WMyvOsA==, tarball: file:projects/account.tgz}
|
||||
name: '@rush-temp/account'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11422,6 +11535,89 @@ packages:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/bitrix-assets.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-Hod/zdxq3gCpr1vfwff7eAusB6KTImLvny1hk3x5Rcn9Rri0SMTJKm3NY0Ds+XTCnnOFZQscD81EfIpaeQPITw==, tarball: file:projects/bitrix-assets.tgz}
|
||||
id: file:projects/bitrix-assets.tgz
|
||||
name: '@rush-temp/bitrix-assets'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@types/node': 16.11.68
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
prettier: 2.7.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
file:projects/bitrix-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-QQs9ffRu6ZbMcDsIHbbqwEnqlVIFPT5SOemp5PMLCfLcAhJn4arE89dCCuVLZ6p/JMYF3g7SCy/dgUllg4nNxA==, tarball: file:projects/bitrix-resources.tgz}
|
||||
id: file:projects/bitrix-resources.tgz
|
||||
name: '@rush-temp/bitrix-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@2bad/bitrix': 2.5.0
|
||||
'@types/qs': 6.9.7
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
eslint-plugin-svelte3: 4.0.0_eslint@8.27.0+svelte@3.53.1
|
||||
fast-equals: 2.0.4
|
||||
p-queue: 7.3.0
|
||||
prettier: 2.7.1
|
||||
prettier-plugin-svelte: 2.8.0_prettier@2.7.1+svelte@3.53.1
|
||||
qs: 6.11.0
|
||||
sass: 1.56.1
|
||||
svelte: 3.53.1
|
||||
svelte-check: 2.9.2_911be9570a99ae2f9e069a41b9f14a00
|
||||
svelte-loader: 3.1.4_svelte@3.53.1
|
||||
svelte-preprocess: 4.10.7_1cd24d71cb02643c0a6ca17ff2edd158
|
||||
typescript: 4.8.4
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- coffeescript
|
||||
- less
|
||||
- node-sass
|
||||
- postcss
|
||||
- postcss-load-config
|
||||
- pug
|
||||
- stylus
|
||||
- sugarss
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/bitrix.tgz:
|
||||
resolution: {integrity: sha512-fLYL3iRRLeMJbRIjfkylukQ/uDZ/OfDuzAvrVHW/dCu2RIEFSpbYDvTlH4Cu0CR6adruVNSP4E3F+yAZSIHmQg==, tarball: file:projects/bitrix.tgz}
|
||||
name: '@rush-temp/bitrix'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@2bad/bitrix': 2.5.0
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
prettier: 2.7.1
|
||||
typescript: 4.8.4
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
file:projects/board-assets.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-rjdJ/U7sW/VdIPr31Kf1TCA9NxfStFMcypFbhhP13V00V4KM15ZzjdS9dcVhR0S1mqrXSomEZqzUeHpjkyfGgA==, tarball: file:projects/board-assets.tgz}
|
||||
id: file:projects/board-assets.tgz
|
||||
@ -11841,7 +12037,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/dev-client-resources.tgz:
|
||||
resolution: {integrity: sha512-Bf1K9DyTyiKCNSXwfDfnMEQvyTpwTKLxtermBHCAtH/X07jkRTQR78TityPmMY13qEP29GzVbiLdcYwMStLuoA==, tarball: file:projects/dev-client-resources.tgz}
|
||||
resolution: {integrity: sha512-aBdWjVRiHsvyjb4ZVwV3CcoYF+LnWbnBtZjvQh0JbVTBZOZfOcXUiDjw73dugHlSu0EqemahiIpKl0B8H40f2g==, tarball: file:projects/dev-client-resources.tgz}
|
||||
name: '@rush-temp/dev-client-resources'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -11907,7 +12103,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/devmodel-resources.tgz_5536a2d3219f8677582bfc0330dae14a:
|
||||
resolution: {integrity: sha512-ipNVydhF5HfLSsx/TEb3G3Zo1Cag6f/yyDcWDXZfI73extFwzmdObyiPFPvXeSm5yjdu0EsEJH/TSSyxk3QJtA==, tarball: file:projects/devmodel-resources.tgz}
|
||||
resolution: {integrity: sha512-4ZrlsE0DJ62seKCcvfHeMqCc/JI7gix7sw7G31SBZ5oJbm/4Uz62BplxJ+Gt57wbAzC6X1wTozIPHQI+ksaaZg==, tarball: file:projects/devmodel-resources.tgz}
|
||||
id: file:projects/devmodel-resources.tgz
|
||||
name: '@rush-temp/devmodel-resources'
|
||||
version: 0.0.0
|
||||
@ -11942,7 +12138,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/devmodel.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-PgNRY82kL7qujBpgY/LHg52igSugdIOfpunDxUE7zq7R9TGU5rAyCdnZvTvfnQ4ZztcC0ICzJ9z0YoqpFjctRQ==, tarball: file:projects/devmodel.tgz}
|
||||
resolution: {integrity: sha512-eQP4GWVA+cCkwjFfcFy0r+/m/u8acg6ts/YBxVDX36+FmiOuHVvHE7aAciR7uZ0Zhz/SS3rEcW19L7ol9/BVnQ==, tarball: file:projects/devmodel.tgz}
|
||||
id: file:projects/devmodel.tgz
|
||||
name: '@rush-temp/devmodel'
|
||||
version: 0.0.0
|
||||
@ -12107,7 +12303,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/generator.tgz:
|
||||
resolution: {integrity: sha512-9WrSXJKYs18Mv/42PcUJ93qVi9iOCFRVwm6/WMtgeSiZHUwE7EExXtlnMlB7RcpH6H6F8LRLBQ28xTC59duCMg==, tarball: file:projects/generator.tgz}
|
||||
resolution: {integrity: sha512-MBL1q/ENOolYC7S4zK/F93ToETBseT2NO4sb2uvmw+hVok93zZdGt+L3sX6WZvQSGkIv/GV/Rd5ochzNYvYPcg==, tarball: file:projects/generator.tgz}
|
||||
name: '@rush-temp/generator'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -12683,7 +12879,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/model-all.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-TZENWGRR6McuiodV3kw5TsRolcDNGyj9/oe7KaS98cElBPKtK5+0xmVMsQjCkxkelSS5XU5acxy0F13IVKS+LQ==, tarball: file:projects/model-all.tgz}
|
||||
resolution: {integrity: sha512-ypjI0QqQeCCDYXOlHjJqJ4Xpq/KaUIvpYqHoCz3kjRHqR3IuW+nOgxCNYfk5oB5kY2MDhWym0KQ5m05WjVfyGA==, tarball: file:projects/model-all.tgz}
|
||||
id: file:projects/model-all.tgz
|
||||
name: '@rush-temp/model-all'
|
||||
version: 0.0.0
|
||||
@ -12749,6 +12945,27 @@ packages:
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
file:projects/model-bitrix.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-8dZO37adBNzvRYUR8UsawVY1GPRN9F4g4IZj2viQyCZBx0z5vtY+Y+xfoDbHhY23D6pFSBMEz7O0vSCrH2jPSQ==, tarball: file:projects/model-bitrix.tgz}
|
||||
id: file:projects/model-bitrix.tgz
|
||||
name: '@rush-temp/model-bitrix'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
'@rushstack/heft': 0.47.11
|
||||
'@types/heft-jest': 1.0.3
|
||||
'@typescript-eslint/eslint-plugin': 5.42.1_d506b9be61cb4ac2646ecbc6e0680464
|
||||
'@typescript-eslint/parser': 5.42.1_eslint@8.27.0+typescript@4.8.4
|
||||
eslint: 8.27.0
|
||||
eslint-config-standard-with-typescript: 23.0.0_c9fe9619f50f4e82337a86c3af25e566
|
||||
eslint-plugin-import: 2.26.0_eslint@8.27.0
|
||||
eslint-plugin-n: 15.5.1_eslint@8.27.0
|
||||
eslint-plugin-promise: 6.1.1_eslint@8.27.0
|
||||
prettier: 2.7.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
- typescript
|
||||
dev: false
|
||||
|
||||
file:projects/model-board.tgz_typescript@4.8.4:
|
||||
resolution: {integrity: sha512-5I0fthDx1r5yySLmftHGUUPgvFiWEtDZO0M7nLwmfT34wPqhE7EgwsGJHSatDYm6P8e/XCbhl5L55MY58tGrJQ==, tarball: file:projects/model-board.tgz}
|
||||
id: file:projects/model-board.tgz
|
||||
@ -13869,7 +14086,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/pod-backup.tgz:
|
||||
resolution: {integrity: sha512-aYuP6b79VLRNZxzds3pAkI3pU7nnflvilszNFThWoIUxrMF6UToUJJZsYWH41PIXprpXJqRIsYGBZhMTQobg0w==, tarball: file:projects/pod-backup.tgz}
|
||||
resolution: {integrity: sha512-HE0Z8soW6YkoYXsUW6c/sx2ZE9I7T7KHfXtxzNuAbyokYYfaH0vGRUE4y8NZt+bDDAfzzzDOivhu2TPeJUQCYw==, tarball: file:projects/pod-backup.tgz}
|
||||
name: '@rush-temp/pod-backup'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14050,7 +14267,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/presentation.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-Nry3q2egydTo0b3dfhd/ejmLBKBROZ6rpyn7943+PJe5vcriJMOazi/OQur0J3LmFl8ozg2IVqeW+A2STDOt+Q==, tarball: file:projects/presentation.tgz}
|
||||
resolution: {integrity: sha512-s/CjB7oBP/pwiNFtVjC7R1Rs0Ejth0XA+1QgbgYZ3qDqnybD0jbwbXFwnZK8xyCWAK+HSvnzrxI9+SbqGE0p3g==, tarball: file:projects/presentation.tgz}
|
||||
id: file:projects/presentation.tgz
|
||||
name: '@rush-temp/presentation'
|
||||
version: 0.0.0
|
||||
@ -14086,7 +14303,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/prod.tgz_b3a81ceaadec606c0eb174aef12a6049:
|
||||
resolution: {integrity: sha512-xW0K2fEEkHycO18ZB6wSk6u5Qm5X8VLVss47tsS2KHIEwWgLHAIasxhL/SA1DJClWW+kS0o42U2fj76DjLhWJw==, tarball: file:projects/prod.tgz}
|
||||
resolution: {integrity: sha512-HF6aMWOyjHsQ6STxaZz0/dFY3zb6o4J+tz/NZYcSTdspxgHb+yuyqvfm+/RPItDiTTDO6GHVogJ8i/WYIkSyhQ==, tarball: file:projects/prod.tgz}
|
||||
id: file:projects/prod.tgz
|
||||
name: '@rush-temp/prod'
|
||||
version: 0.0.0
|
||||
@ -14297,7 +14514,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-backup.tgz:
|
||||
resolution: {integrity: sha512-+Sxk2DeLDmhbuG/OMQMwfEJRl/atZE9ZXzdiZotTpd+Et1q9w4WH3h34RWdiCOTiInZyJZXjXF5xDF375VAzpQ==, tarball: file:projects/server-backup.tgz}
|
||||
resolution: {integrity: sha512-mOaBUJaifcxXZ6AqSpdIiI19QTCADyT/565qkkfja5HI02gLLNdrmyRKoc65YN3UAZbM2C1l+JXMBU8r58St1A==, tarball: file:projects/server-backup.tgz}
|
||||
name: '@rush-temp/server-backup'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -14923,7 +15140,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/server-tool.tgz:
|
||||
resolution: {integrity: sha512-sdJKsx2tdPWOmolw3blM7QpXlkN+WUikx/uRmofKYuS/Y1HZkY8UHCYLYzWoDmf2/VplseccWe6HatjkLG6c8Q==, tarball: file:projects/server-tool.tgz}
|
||||
resolution: {integrity: sha512-5WEO5W7xOR+BMCgmj9G6+cfEtU+FSGcz7Mfb+apZ24IXhwCrn2eVh/YgDGwSRzQV4W3yeIcO0sUbvowKwLhIDQ==, tarball: file:projects/server-tool.tgz}
|
||||
name: '@rush-temp/server-tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15556,7 +15773,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tool.tgz:
|
||||
resolution: {integrity: sha512-otLw170db0pwKQJWhEYqLlU29LqXHyjuS6V/oYcOPxYCvWXe7Nu1NT82TtVD4DgVE4ecgsS1iR1tVfyPwWjeYQ==, tarball: file:projects/tool.tgz}
|
||||
resolution: {integrity: sha512-eV5BJ5aZRslw3lgvX7imSH6m5EBU8wCOPmZUl4tuKxZAnpjSJA9pHyUivn6EFaJ3r+pdu+ZVSjZ3D/DEbwzAKw==, tarball: file:projects/tool.tgz}
|
||||
name: '@rush-temp/tool'
|
||||
version: 0.0.0
|
||||
dependencies:
|
||||
@ -15624,7 +15841,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/tracker-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-IcoVWcO8DdlZyXbAml4GE9UlmPBzihwEzKWq9+Xs8H1U7cDcHhNwGgF+qv8N+gH5mgBHSziEBQrKfnvF+iz5vw==, tarball: file:projects/tracker-resources.tgz}
|
||||
resolution: {integrity: sha512-z547GvQvHWfDkFJs1VvW4xJKZEzr4O7cICzIfB/lbxZt8uVrbXMVWHxWqfLHrlHxGEalqDeSRdkZ59EQ64cveA==, tarball: file:projects/tracker-resources.tgz}
|
||||
id: file:projects/tracker-resources.tgz
|
||||
name: '@rush-temp/tracker-resources'
|
||||
version: 0.0.0
|
||||
@ -15817,7 +16034,7 @@ packages:
|
||||
dev: false
|
||||
|
||||
file:projects/workbench-resources.tgz_49b4785992daa3b61a639b2b31601e76:
|
||||
resolution: {integrity: sha512-aZ25sMEnPTO2r84j+DL2Ej0KWk71wnvPdmJSntkdrWZH3HhLYVEd+xY0kYoDB+zm4yxLPvr94hrkoo+IG+LCFA==, tarball: file:projects/workbench-resources.tgz}
|
||||
resolution: {integrity: sha512-hyxyGITfGafwcakPTdjJ7iQkwz25QiBIWF/QK0/sdPYTqSlxDHxkzvwBh2bKNXGuAPuqKS7aykvhrhHXqZsFXQ==, tarball: file:projects/workbench-resources.tgz}
|
||||
id: file:projects/workbench-resources.tgz
|
||||
name: '@rush-temp/workbench-resources'
|
||||
version: 0.0.0
|
||||
|
@ -40,6 +40,7 @@ const fs = __importStar(require("fs"));
|
||||
const install_run_1 = require("./install-run");
|
||||
const PACKAGE_NAME = '@microsoft/rush';
|
||||
const RUSH_PREVIEW_VERSION = 'RUSH_PREVIEW_VERSION';
|
||||
const INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE = 'INSTALL_RUN_RUSH_LOCKFILE_PATH';
|
||||
function _getRushVersion(logger) {
|
||||
const rushPreviewVersion = process.env[RUSH_PREVIEW_VERSION];
|
||||
if (rushPreviewVersion !== undefined) {
|
||||
@ -104,7 +105,11 @@ function _run() {
|
||||
(0, install_run_1.runWithErrorAndStatusCode)(logger, () => {
|
||||
const version = _getRushVersion(logger);
|
||||
logger.info(`The rush.json configuration requests Rush version ${version}`);
|
||||
return (0, install_run_1.installAndRun)(logger, PACKAGE_NAME, version, bin, packageBinArgs);
|
||||
const lockFilePath = process.env[INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE];
|
||||
if (lockFilePath) {
|
||||
logger.info(`Found ${INSTALL_RUN_RUSH_LOCKFILE_PATH_VARIABLE}="${lockFilePath}", installing with lockfile.`);
|
||||
}
|
||||
return (0, install_run_1.installAndRun)(logger, PACKAGE_NAME, version, bin, packageBinArgs, lockFilePath);
|
||||
});
|
||||
}
|
||||
_run();
|
||||
|
@ -42,6 +42,7 @@ const os = __importStar(require("os"));
|
||||
const path = __importStar(require("path"));
|
||||
exports.RUSH_JSON_FILENAME = 'rush.json';
|
||||
const RUSH_TEMP_FOLDER_ENV_VARIABLE_NAME = 'RUSH_TEMP_FOLDER';
|
||||
const INSTALL_RUN_LOCKFILE_PATH_VARIABLE = 'INSTALL_RUN_LOCKFILE_PATH';
|
||||
const INSTALLED_FLAG_FILENAME = 'installed.flag';
|
||||
const NODE_MODULES_FOLDER_NAME = 'node_modules';
|
||||
const PACKAGE_JSON_FILENAME = 'package.json';
|
||||
@ -82,6 +83,9 @@ function _parsePackageSpecifier(rawPackageSpecifier) {
|
||||
* home directory.
|
||||
*
|
||||
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities.copyAndTrimNpmrcFile()
|
||||
*
|
||||
* @returns
|
||||
* The text of the the .npmrc.
|
||||
*/
|
||||
function _copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath) {
|
||||
logger.info(`Transforming ${sourceNpmrcPath}`); // Verbose
|
||||
@ -121,20 +125,25 @@ function _copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath) {
|
||||
resultLines.push(line);
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL));
|
||||
const combinedNpmrc = resultLines.join(os.EOL);
|
||||
fs.writeFileSync(targetNpmrcPath, combinedNpmrc);
|
||||
return combinedNpmrc;
|
||||
}
|
||||
/**
|
||||
* syncNpmrc() copies the .npmrc file to the target folder, and also trims unusable lines from the .npmrc file.
|
||||
* If the source .npmrc file not exist, then syncNpmrc() will delete an .npmrc that is found in the target folder.
|
||||
*
|
||||
* IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc()
|
||||
*
|
||||
* @returns
|
||||
* The text of the the synced .npmrc, if one exists. If one does not exist, then undefined is returned.
|
||||
*/
|
||||
function _syncNpmrc(logger, sourceNpmrcFolder, targetNpmrcFolder, useNpmrcPublish) {
|
||||
const sourceNpmrcPath = path.join(sourceNpmrcFolder, !useNpmrcPublish ? '.npmrc' : '.npmrc-publish');
|
||||
const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc');
|
||||
try {
|
||||
if (fs.existsSync(sourceNpmrcPath)) {
|
||||
_copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath);
|
||||
return _copyAndTrimNpmrcFile(logger, sourceNpmrcPath, targetNpmrcPath);
|
||||
}
|
||||
else if (fs.existsSync(targetNpmrcPath)) {
|
||||
// If the source .npmrc doesn't exist and there is one in the target, delete the one in the target
|
||||
@ -306,26 +315,41 @@ function _isPackageAlreadyInstalled(packageInstallFolder) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Delete a file. Fail silently if it does not exist.
|
||||
*/
|
||||
function _deleteFile(file) {
|
||||
try {
|
||||
fs.unlinkSync(file);
|
||||
}
|
||||
catch (err) {
|
||||
if (err.code !== 'ENOENT' && err.code !== 'ENOTDIR') {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Removes the following files and directories under the specified folder path:
|
||||
* - installed.flag
|
||||
* -
|
||||
* - node_modules
|
||||
*/
|
||||
function _cleanInstallFolder(rushTempFolder, packageInstallFolder) {
|
||||
function _cleanInstallFolder(rushTempFolder, packageInstallFolder, lockFilePath) {
|
||||
try {
|
||||
const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME);
|
||||
if (fs.existsSync(flagFile)) {
|
||||
fs.unlinkSync(flagFile);
|
||||
}
|
||||
_deleteFile(flagFile);
|
||||
const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json');
|
||||
if (fs.existsSync(packageLockFile)) {
|
||||
fs.unlinkSync(packageLockFile);
|
||||
if (lockFilePath) {
|
||||
fs.copyFileSync(lockFilePath, packageLockFile);
|
||||
}
|
||||
const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
|
||||
if (fs.existsSync(nodeModulesFolder)) {
|
||||
const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');
|
||||
fs.renameSync(nodeModulesFolder, path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`));
|
||||
else {
|
||||
// Not running `npm ci`, so need to cleanup
|
||||
_deleteFile(packageLockFile);
|
||||
const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME);
|
||||
if (fs.existsSync(nodeModulesFolder)) {
|
||||
const rushRecyclerFolder = _ensureAndJoinPath(rushTempFolder, 'rush-recycler');
|
||||
fs.renameSync(nodeModulesFolder, path.join(rushRecyclerFolder, `install-run-${Date.now().toString()}`));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
@ -354,17 +378,17 @@ function _createPackageJson(packageInstallFolder, name, version) {
|
||||
/**
|
||||
* Run "npm install" in the package install folder.
|
||||
*/
|
||||
function _installPackage(logger, packageInstallFolder, name, version) {
|
||||
function _installPackage(logger, packageInstallFolder, name, version, command) {
|
||||
try {
|
||||
logger.info(`Installing ${name}...`);
|
||||
const npmPath = getNpmPath();
|
||||
const result = childProcess.spawnSync(npmPath, ['install'], {
|
||||
const result = childProcess.spawnSync(npmPath, [command], {
|
||||
stdio: 'inherit',
|
||||
cwd: packageInstallFolder,
|
||||
env: process.env
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
throw new Error('"npm install" encountered an error');
|
||||
throw new Error(`"npm ${command}" encountered an error`);
|
||||
}
|
||||
logger.info(`Successfully installed ${name}@${version}`);
|
||||
}
|
||||
@ -392,18 +416,19 @@ function _writeFlagFile(packageInstallFolder) {
|
||||
throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`);
|
||||
}
|
||||
}
|
||||
function installAndRun(logger, packageName, packageVersion, packageBinName, packageBinArgs) {
|
||||
function installAndRun(logger, packageName, packageVersion, packageBinName, packageBinArgs, lockFilePath = process.env[INSTALL_RUN_LOCKFILE_PATH_VARIABLE]) {
|
||||
const rushJsonFolder = findRushJsonFolder();
|
||||
const rushCommonFolder = path.join(rushJsonFolder, 'common');
|
||||
const rushTempFolder = _getRushTempFolder(rushCommonFolder);
|
||||
const packageInstallFolder = _ensureAndJoinPath(rushTempFolder, 'install-run', `${packageName}@${packageVersion}`);
|
||||
if (!_isPackageAlreadyInstalled(packageInstallFolder)) {
|
||||
// The package isn't already installed
|
||||
_cleanInstallFolder(rushTempFolder, packageInstallFolder);
|
||||
_cleanInstallFolder(rushTempFolder, packageInstallFolder, lockFilePath);
|
||||
const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush');
|
||||
_syncNpmrc(logger, sourceNpmrcFolder, packageInstallFolder);
|
||||
_createPackageJson(packageInstallFolder, packageName, packageVersion);
|
||||
_installPackage(logger, packageInstallFolder, packageName, packageVersion);
|
||||
const command = lockFilePath ? 'ci' : 'install';
|
||||
_installPackage(logger, packageInstallFolder, packageName, packageVersion, command);
|
||||
_writeFlagFile(packageInstallFolder);
|
||||
}
|
||||
const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`;
|
||||
|
@ -154,6 +154,10 @@
|
||||
"@hcengineering/server-hr-resources": "~0.6.0",
|
||||
"@hcengineering/document": "^0.6.0",
|
||||
"@hcengineering/document-assets": "^0.6.0",
|
||||
"@hcengineering/document-resources": "^0.6.0"
|
||||
"@hcengineering/document-resources": "^0.6.0",
|
||||
|
||||
"@hcengineering/bitrix": "^0.6.0",
|
||||
"@hcengineering/bitrix-assets": "^0.6.0",
|
||||
"@hcengineering/bitrix-resources": "^0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,8 @@ import { hrId } from '@hcengineering/hr'
|
||||
import rekoni from '@hcengineering/rekoni'
|
||||
import document, { documentId } from '@hcengineering/document'
|
||||
|
||||
import bitrix, { bitrixId } from '@hcengineering/bitrix'
|
||||
|
||||
import '@hcengineering/login-assets'
|
||||
import '@hcengineering/task-assets'
|
||||
import '@hcengineering/view-assets'
|
||||
@ -68,6 +70,7 @@ import '@hcengineering/board-assets'
|
||||
import '@hcengineering/preference-assets'
|
||||
import '@hcengineering/hr-assets'
|
||||
import '@hcengineering/document-assets'
|
||||
import '@hcengineering/bitrix-assets'
|
||||
|
||||
import presentation, { presentationId } from '@hcengineering/presentation'
|
||||
import { coreId } from '@hcengineering/core'
|
||||
@ -134,6 +137,7 @@ export async function configurePlatform() {
|
||||
addLocation(automationId, () => import(/* webpackChunkName: "automation" */ '@hcengineering/automation-resources'))
|
||||
addLocation(hrId, () => import(/* webpackChunkName: "hr" */ '@hcengineering/hr-resources'))
|
||||
addLocation(documentId, () => import(/* webpackChunkName: "hr" */ '@hcengineering/document-resources'))
|
||||
addLocation(bitrixId, () => import(/* webpackChunkName: "bitrix" */ '@hcengineering/bitrix-resources'))
|
||||
|
||||
setMetadata(workbench.metadata.PlatformTitle, 'Platform')
|
||||
}
|
||||
|
@ -74,6 +74,7 @@
|
||||
"@hcengineering/model-preference": "~0.6.0",
|
||||
"@hcengineering/model-hr": "~0.6.0",
|
||||
"@hcengineering/model-server-hr": "~0.6.0",
|
||||
"@hcengineering/model-document": "~0.6.0"
|
||||
"@hcengineering/model-document": "~0.6.0",
|
||||
"@hcengineering/model-bitrix": "~0.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ import { createModel as preferenceModel } from '@hcengineering/model-preference'
|
||||
import { createModel as hrModel } from '@hcengineering/model-hr'
|
||||
import { createModel as serverHrModel } from '@hcengineering/model-server-hr'
|
||||
import { createModel as documentModel } from '@hcengineering/model-document'
|
||||
import { createModel as bitrixModel } from '@hcengineering/model-bitrix'
|
||||
|
||||
export const version: Data<Version> = jsonVersion as Data<Version>
|
||||
|
||||
@ -90,6 +91,7 @@ const builders: [(b: Builder) => void, string][] = [
|
||||
[trackerModel, 'tracker'],
|
||||
[boardModel, 'board'],
|
||||
[calendarModel, 'calendar'],
|
||||
[bitrixModel, 'bitrix'],
|
||||
|
||||
[serverCoreModel, 'server-core'],
|
||||
[serverAttachmentModel, 'server-attachment'],
|
||||
|
@ -35,6 +35,7 @@ import { boardOperation } from '@hcengineering/model-board'
|
||||
import { demoOperation } from '@hcengineering/model-demo'
|
||||
import { hrOperation } from '@hcengineering/model-hr'
|
||||
import { documentOperation } from '@hcengineering/model-document'
|
||||
import { bitrixOperation } from '@hcengineering/model-bitrix'
|
||||
|
||||
export const migrateOperations: MigrateOperation[] = [
|
||||
coreOperation,
|
||||
@ -56,5 +57,6 @@ export const migrateOperations: MigrateOperation[] = [
|
||||
trackerOperation,
|
||||
boardOperation,
|
||||
hrOperation,
|
||||
documentOperation
|
||||
documentOperation,
|
||||
bitrixOperation
|
||||
]
|
||||
|
7
models/bitrix/.eslintrc.js
Normal file
7
models/bitrix/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/model-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
4
models/bitrix/.npmignore
Normal file
4
models/bitrix/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
18
models/bitrix/config/rig.json
Normal file
18
models/bitrix/config/rig.json
Normal file
@ -0,0 +1,18 @@
|
||||
// The "rig.json" file directs tools to look for their config files in an external package.
|
||||
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
|
||||
/**
|
||||
* (Required) The name of the rig package to inherit from.
|
||||
* It should be an NPM package name with the "-rig" suffix.
|
||||
*/
|
||||
"rigPackageName": "@hcengineering/model-rig"
|
||||
|
||||
/**
|
||||
* (Optional) Selects a config profile from the rig package. The name must consist of
|
||||
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
|
||||
* If omitted, then the "default" profile will be used."
|
||||
*/
|
||||
// "rigProfile": "your-profile-name"
|
||||
}
|
45
models/bitrix/package.json
Normal file
45
models/bitrix/package.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@hcengineering/model-bitrix",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"build:watch": "tsc",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/model-rig": "~0.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.26.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/activity": "~0.6.0",
|
||||
"@hcengineering/model": "~0.6.0",
|
||||
"@hcengineering/core": "^0.6.19",
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/model-core": "~0.6.0",
|
||||
"@hcengineering/model-attachment": "~0.6.0",
|
||||
"@hcengineering/model-contact": "~0.6.1",
|
||||
"@hcengineering/view": "^0.6.1",
|
||||
"@hcengineering/model-view": "~0.6.0",
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"@hcengineering/bitrix": "^0.6.0",
|
||||
"@hcengineering/bitrix-resources": "^0.6.0",
|
||||
"@hcengineering/preference": "^0.6.1",
|
||||
"@hcengineering/model-preference": "~0.6.0",
|
||||
"@hcengineering/setting": "~0.6.1",
|
||||
"@hcengineering/ui": "^0.6.2"
|
||||
}
|
||||
}
|
99
models/bitrix/src/index.ts
Normal file
99
models/bitrix/src/index.ts
Normal file
@ -0,0 +1,99 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Builder, Collection, Mixin, Model, Prop, TypeRef, TypeString } from '@hcengineering/model'
|
||||
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
|
||||
import bitrix from './plugin'
|
||||
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, BitrixSyncDoc, Fields } from '@hcengineering/bitrix'
|
||||
import { AnyAttribute, Class, Doc, Domain, Ref } from '@hcengineering/core'
|
||||
|
||||
import view, { createAction } from '@hcengineering/model-view'
|
||||
|
||||
import setting from '@hcengineering/setting'
|
||||
|
||||
const DOMAIN_BITRIX = 'bitrix' as Domain
|
||||
|
||||
@Mixin(bitrix.mixin.BitrixSyncDoc, core.class.Doc)
|
||||
export class TBitrixSyncDoc extends TDoc implements BitrixSyncDoc {
|
||||
type!: string
|
||||
bitrixId!: string
|
||||
}
|
||||
|
||||
@Model(bitrix.class.EntityMapping, core.class.Doc, DOMAIN_BITRIX)
|
||||
export class TBitrixEntityMapping extends TDoc implements BitrixEntityMapping {
|
||||
@Prop(TypeRef(core.class.Attribute), core.string.ClassPropertyLabel)
|
||||
ofClass!: Ref<Class<Doc>>
|
||||
|
||||
type!: string
|
||||
|
||||
@Prop(Collection(bitrix.class.FieldMapping), bitrix.string.FieldMapping)
|
||||
fields!: number
|
||||
|
||||
bitrixFields!: Fields
|
||||
comments!: boolean
|
||||
activity!: boolean
|
||||
attachments!: boolean
|
||||
}
|
||||
|
||||
@Model(bitrix.class.FieldMapping, core.class.Doc, DOMAIN_BITRIX)
|
||||
export class TBitrixFieldMapping extends TAttachedDoc implements BitrixFieldMapping {
|
||||
@Prop(TypeRef(core.class.Attribute), core.string.ClassPropertyLabel)
|
||||
ofClass!: Ref<Class<Doc>>
|
||||
|
||||
@Prop(TypeString(), core.string.ClassPropertyLabel)
|
||||
attributeName!: Ref<AnyAttribute>
|
||||
|
||||
bitrixTitle!: string
|
||||
bitrixType!: string
|
||||
operation!: any
|
||||
}
|
||||
|
||||
export function createModel (builder: Builder): void {
|
||||
builder.createModel(TBitrixEntityMapping, TBitrixFieldMapping, TBitrixSyncDoc)
|
||||
|
||||
builder.createDoc(
|
||||
setting.class.IntegrationType,
|
||||
core.space.Model,
|
||||
{
|
||||
label: bitrix.string.Bitrix,
|
||||
description: bitrix.string.BitrixDesc,
|
||||
icon: bitrix.component.BitrixIcon,
|
||||
createComponent: bitrix.component.BitrixConnect,
|
||||
configureComponent: bitrix.component.BitrixConfigure
|
||||
},
|
||||
bitrix.integrationType.Bitrix
|
||||
)
|
||||
|
||||
createAction(
|
||||
builder,
|
||||
{
|
||||
action: view.actionImpl.ShowPopup,
|
||||
actionProps: {
|
||||
component: bitrix.component.BitrixImport
|
||||
},
|
||||
label: bitrix.string.BitrixImport,
|
||||
icon: bitrix.icon.Bitrix,
|
||||
input: 'any',
|
||||
category: view.category.General,
|
||||
target: core.class.Doc,
|
||||
context: { mode: ['workbench', 'browser', 'editor', 'panel', 'popup'], group: 'create' }
|
||||
},
|
||||
bitrix.action.BitrixImport
|
||||
)
|
||||
}
|
||||
|
||||
export { bitrixOperation } from './migration'
|
21
models/bitrix/src/migration.ts
Normal file
21
models/bitrix/src/migration.ts
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
|
||||
|
||||
export const bitrixOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
|
||||
}
|
37
models/bitrix/src/plugin.ts
Normal file
37
models/bitrix/src/plugin.ts
Normal file
@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { bitrixId } from '@hcengineering/bitrix'
|
||||
import bitrix from '@hcengineering/bitrix-resources/src/plugin'
|
||||
import { IntlString, mergeIds } from '@hcengineering/platform'
|
||||
import { Action } from '@hcengineering/view'
|
||||
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import type { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
export default mergeIds(bitrixId, bitrix, {
|
||||
component: {
|
||||
BitrixConnect: '' as AnyComponent,
|
||||
BitrixConfigure: '' as AnyComponent,
|
||||
BitrixImport: '' as AnyComponent
|
||||
},
|
||||
string: {
|
||||
BitrixImport: '' as IntlString
|
||||
},
|
||||
action: {
|
||||
BitrixImport: '' as Ref<Action>
|
||||
}
|
||||
})
|
8
models/bitrix/tsconfig.json
Normal file
8
models/bitrix/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/model-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { DocumentUpdate, Hierarchy, MixinData, MixinUpdate, ModelDb } from '.'
|
||||
import type { Account, AttachedData, AttachedDoc, Class, Data, Doc, Mixin, Ref, Space } from './classes'
|
||||
import type { Account, AttachedData, AttachedDoc, Class, Data, Doc, Mixin, Ref, Space, Timestamp } from './classes'
|
||||
import { Client } from './client'
|
||||
import core from './component'
|
||||
import type { DocumentQuery, FindOptions, FindResult, TxResult, WithLookup } from './storage'
|
||||
@ -55,9 +55,11 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
attributes: Data<T>,
|
||||
id?: Ref<T>
|
||||
id?: Ref<T>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<Ref<T>> {
|
||||
const tx = this.txFactory.createTxCreateDoc(_class, space, attributes, id)
|
||||
const tx = this.txFactory.createTxCreateDoc(_class, space, attributes, id, modifiedOn, modifiedBy)
|
||||
await this.client.tx(tx)
|
||||
return tx.objectId
|
||||
}
|
||||
@ -69,14 +71,16 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
attachedToClass: Ref<Class<T>>,
|
||||
collection: string,
|
||||
attributes: AttachedData<P>,
|
||||
id?: Ref<P>
|
||||
id?: Ref<P>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<Ref<P>> {
|
||||
const tx = this.txFactory.createTxCollectionCUD<T, P>(
|
||||
attachedToClass,
|
||||
attachedTo,
|
||||
space,
|
||||
collection,
|
||||
this.txFactory.createTxCreateDoc<P>(_class, space, attributes as unknown as Data<P>, id)
|
||||
this.txFactory.createTxCreateDoc<P>(_class, space, attributes as unknown as Data<P>, id, modifiedOn, modifiedBy)
|
||||
)
|
||||
await this.client.tx(tx)
|
||||
return tx.tx.objectId as unknown as Ref<P>
|
||||
@ -90,14 +94,16 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
attachedToClass: Ref<Class<T>>,
|
||||
collection: string,
|
||||
operations: DocumentUpdate<P>,
|
||||
retrieve?: boolean
|
||||
retrieve?: boolean,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<Ref<T>> {
|
||||
const tx = this.txFactory.createTxCollectionCUD(
|
||||
attachedToClass,
|
||||
attachedTo,
|
||||
space,
|
||||
collection,
|
||||
this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve)
|
||||
this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy)
|
||||
)
|
||||
await this.client.tx(tx)
|
||||
return tx.objectId
|
||||
@ -109,14 +115,16 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
objectId: Ref<P>,
|
||||
attachedTo: Ref<T>,
|
||||
attachedToClass: Ref<Class<T>>,
|
||||
collection: string
|
||||
collection: string,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<Ref<T>> {
|
||||
const tx = this.txFactory.createTxCollectionCUD(
|
||||
attachedToClass,
|
||||
attachedTo,
|
||||
space,
|
||||
collection,
|
||||
this.txFactory.createTxRemoveDoc(_class, space, objectId)
|
||||
this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy)
|
||||
)
|
||||
await this.client.tx(tx)
|
||||
return tx.objectId
|
||||
@ -127,14 +135,22 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
operations: DocumentUpdate<T>,
|
||||
retrieve?: boolean
|
||||
retrieve?: boolean,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve)
|
||||
const tx = this.txFactory.createTxUpdateDoc(_class, space, objectId, operations, retrieve, modifiedOn, modifiedBy)
|
||||
return this.client.tx(tx)
|
||||
}
|
||||
|
||||
removeDoc<T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, objectId: Ref<T>): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId)
|
||||
removeDoc<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxRemoveDoc(_class, space, objectId, modifiedOn, modifiedBy)
|
||||
return this.client.tx(tx)
|
||||
}
|
||||
|
||||
@ -143,9 +159,19 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
objectClass: Ref<Class<D>>,
|
||||
objectSpace: Ref<Space>,
|
||||
mixin: Ref<Mixin<M>>,
|
||||
attributes: MixinData<D, M>
|
||||
attributes: MixinData<D, M>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxMixin(objectId, objectClass, objectSpace, mixin, attributes)
|
||||
const tx = this.txFactory.createTxMixin(
|
||||
objectId,
|
||||
objectClass,
|
||||
objectSpace,
|
||||
mixin,
|
||||
attributes,
|
||||
modifiedOn,
|
||||
modifiedBy
|
||||
)
|
||||
return this.client.tx(tx)
|
||||
}
|
||||
|
||||
@ -154,17 +180,33 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
objectClass: Ref<Class<D>>,
|
||||
objectSpace: Ref<Space>,
|
||||
mixin: Ref<Mixin<M>>,
|
||||
attributes: MixinUpdate<D, M>
|
||||
attributes: MixinUpdate<D, M>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<TxResult> {
|
||||
const tx = this.txFactory.createTxMixin(objectId, objectClass, objectSpace, mixin, attributes)
|
||||
const tx = this.txFactory.createTxMixin(
|
||||
objectId,
|
||||
objectClass,
|
||||
objectSpace,
|
||||
mixin,
|
||||
attributes,
|
||||
modifiedOn,
|
||||
modifiedBy
|
||||
)
|
||||
return this.client.tx(tx)
|
||||
}
|
||||
|
||||
update<T extends Doc>(doc: T, update: DocumentUpdate<T>, retrieve?: boolean): Promise<TxResult> {
|
||||
update<T extends Doc>(
|
||||
doc: T,
|
||||
update: DocumentUpdate<T>,
|
||||
retrieve?: boolean,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): Promise<TxResult> {
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
if (hierarchy.isMixin(doc._class)) {
|
||||
const baseClass = hierarchy.getBaseClass(doc._class)
|
||||
return this.updateMixin(doc._id, baseClass, doc.space, doc._class, update)
|
||||
return this.updateMixin(doc._id, baseClass, doc.space, doc._class, update, modifiedOn, modifiedBy)
|
||||
}
|
||||
if (hierarchy.isDerived(doc._class, core.class.AttachedDoc)) {
|
||||
const adoc = doc as unknown as AttachedDoc
|
||||
@ -176,13 +218,15 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
adoc.attachedToClass,
|
||||
adoc.collection,
|
||||
update,
|
||||
retrieve
|
||||
retrieve,
|
||||
modifiedOn,
|
||||
modifiedBy
|
||||
)
|
||||
}
|
||||
return this.updateDoc(doc._class, doc.space, doc._id, update, retrieve)
|
||||
return this.updateDoc(doc._class, doc.space, doc._id, update, retrieve, modifiedOn, modifiedBy)
|
||||
}
|
||||
|
||||
remove<T extends Doc>(doc: T): Promise<TxResult> {
|
||||
remove<T extends Doc>(doc: T, modifiedOn?: Timestamp, modifiedBy?: Ref<Account>): Promise<TxResult> {
|
||||
if (this.client.getHierarchy().isDerived(doc._class, core.class.AttachedDoc)) {
|
||||
const adoc = doc as unknown as AttachedDoc
|
||||
return this.removeCollection(
|
||||
@ -191,7 +235,9 @@ export class TxOperations implements Omit<Client, 'notify'> {
|
||||
adoc._id,
|
||||
adoc.attachedTo,
|
||||
adoc.attachedToClass,
|
||||
adoc.collection
|
||||
adoc.collection,
|
||||
modifiedOn,
|
||||
modifiedBy
|
||||
)
|
||||
}
|
||||
return this.removeDoc(doc._class, doc.space, doc._id)
|
||||
|
@ -14,7 +14,20 @@
|
||||
//
|
||||
|
||||
import type { KeysByType } from 'simplytyped'
|
||||
import type { Account, Arr, AttachedDoc, Class, Data, Doc, Domain, Mixin, PropertyType, Ref, Space } from './classes'
|
||||
import type {
|
||||
Account,
|
||||
Arr,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Data,
|
||||
Doc,
|
||||
Domain,
|
||||
Mixin,
|
||||
PropertyType,
|
||||
Ref,
|
||||
Space,
|
||||
Timestamp
|
||||
} from './classes'
|
||||
import core from './component'
|
||||
import { setObjectValue } from './objvalue'
|
||||
import { _getOperator } from './operator'
|
||||
@ -398,7 +411,9 @@ export class TxFactory {
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
attributes: Data<T>,
|
||||
objectId?: Ref<T>
|
||||
objectId?: Ref<T>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxCreateDoc<T> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
@ -407,8 +422,8 @@ export class TxFactory {
|
||||
objectId: objectId ?? generateId(),
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
attributes
|
||||
}
|
||||
}
|
||||
@ -418,7 +433,9 @@ export class TxFactory {
|
||||
objectId: Ref<T>,
|
||||
space: Ref<Space>,
|
||||
collection: string,
|
||||
tx: TxCUD<P>
|
||||
tx: TxCUD<P>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxCollectionCUD<T, P> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
@ -427,8 +444,8 @@ export class TxFactory {
|
||||
objectId,
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
collection,
|
||||
tx
|
||||
}
|
||||
@ -439,14 +456,16 @@ export class TxFactory {
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
operations: DocumentUpdate<T>,
|
||||
retrieve?: boolean
|
||||
retrieve?: boolean,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxUpdateDoc<T> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxUpdateDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
objectId,
|
||||
objectClass: _class,
|
||||
objectSpace: space,
|
||||
@ -455,13 +474,19 @@ export class TxFactory {
|
||||
}
|
||||
}
|
||||
|
||||
createTxRemoveDoc<T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, objectId: Ref<T>): TxRemoveDoc<T> {
|
||||
createTxRemoveDoc<T extends Doc>(
|
||||
_class: Ref<Class<T>>,
|
||||
space: Ref<Space>,
|
||||
objectId: Ref<T>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxRemoveDoc<T> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxRemoveDoc,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
objectId,
|
||||
objectClass: _class,
|
||||
objectSpace: space
|
||||
@ -473,14 +498,16 @@ export class TxFactory {
|
||||
objectClass: Ref<Class<D>>,
|
||||
objectSpace: Ref<Space>,
|
||||
mixin: Ref<Mixin<M>>,
|
||||
attributes: MixinUpdate<D, M>
|
||||
attributes: MixinUpdate<D, M>,
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxMixin<D, M> {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxMixin,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
objectId,
|
||||
objectClass,
|
||||
objectSpace,
|
||||
@ -489,13 +516,20 @@ export class TxFactory {
|
||||
}
|
||||
}
|
||||
|
||||
createTxApplyIf (space: Ref<Space>, scope: string, match: DocumentClassQuery<Doc>[], txes: TxCUD<Doc>[]): TxApplyIf {
|
||||
createTxApplyIf (
|
||||
space: Ref<Space>,
|
||||
scope: string,
|
||||
match: DocumentClassQuery<Doc>[],
|
||||
txes: TxCUD<Doc>[],
|
||||
modifiedOn?: Timestamp,
|
||||
modifiedBy?: Ref<Account>
|
||||
): TxApplyIf {
|
||||
return {
|
||||
_id: generateId(),
|
||||
_class: core.class.TxApplyIf,
|
||||
space: core.space.Tx,
|
||||
modifiedBy: this.account,
|
||||
modifiedOn: Date.now(),
|
||||
modifiedBy: modifiedBy ?? this.account,
|
||||
modifiedOn: modifiedOn ?? Date.now(),
|
||||
objectSpace: space,
|
||||
scope,
|
||||
match,
|
||||
|
@ -27,6 +27,7 @@
|
||||
export let createMore: boolean | undefined = undefined
|
||||
export let okLabel: IntlString = presentation.string.Create
|
||||
export let onCancel: Function | undefined = undefined
|
||||
export let fullSize = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -36,6 +37,7 @@
|
||||
<form
|
||||
id={label}
|
||||
class="antiCard {$deviceInfo.isMobile ? 'mobile' : 'dialog'}"
|
||||
class:full={fullSize}
|
||||
on:submit|preventDefault={() => {}}
|
||||
use:resizeObserver={() => {
|
||||
dispatch('changeContent')
|
||||
|
@ -64,12 +64,14 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const query = createQuery()
|
||||
|
||||
$: _idExtra = typeof docQuery?._id === 'object' ? docQuery?._id : {}
|
||||
$: query.query<Doc>(
|
||||
_class,
|
||||
{
|
||||
...(docQuery ?? {}),
|
||||
[searchField]: { $like: '%' + search + '%' },
|
||||
_id: { $nin: ignoreObjects }
|
||||
_id: { $nin: ignoreObjects, ..._idExtra }
|
||||
},
|
||||
(result) => {
|
||||
result.sort((a, b) => {
|
||||
|
@ -214,13 +214,19 @@
|
||||
margin: 2.5rem;
|
||||
}
|
||||
}
|
||||
&.vScroll {
|
||||
overflow-y: auto;
|
||||
&::-webkit-scrollbar-track {
|
||||
margin: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ac-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
padding: 1.75rem 2.5rem;
|
||||
min-width: 25rem;
|
||||
padding: 0.75rem 0.5rem;
|
||||
min-width: 15rem;
|
||||
max-width: 25rem;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--theme-menu-divider);
|
||||
|
@ -139,6 +139,10 @@
|
||||
height: auto;
|
||||
max-width: 60rem;
|
||||
max-height: inherit;
|
||||
&.full {
|
||||
width: max-content;
|
||||
// max-width: 100%;
|
||||
}
|
||||
}
|
||||
&.mobile {
|
||||
width: 90vw;
|
||||
|
@ -37,6 +37,7 @@
|
||||
export let focusIndex = -1
|
||||
export let autoSelect: boolean = true
|
||||
export let useFlexGrow = false
|
||||
export let minW0 = true
|
||||
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
@ -53,7 +54,7 @@
|
||||
const mgr = getFocusManager()
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="min-w-0" class:flex-grow={useFlexGrow}>
|
||||
<div bind:this={container} class:min-w-0={minW0} class:flex-grow={useFlexGrow}>
|
||||
<Button
|
||||
{focusIndex}
|
||||
{icon}
|
||||
|
@ -35,6 +35,7 @@
|
||||
export let width: string | undefined = undefined
|
||||
export let labelDirection: TooltipAlignment | undefined = undefined
|
||||
export let shouldUpdateUndefined: boolean = true
|
||||
export let minW0 = true
|
||||
|
||||
let container: HTMLElement
|
||||
let opened: boolean = false
|
||||
@ -72,7 +73,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div bind:this={container} class="min-w-0">
|
||||
<div bind:this={container} class:min-w-0={minW0}>
|
||||
<Button
|
||||
{icon}
|
||||
width={width ?? 'min-content'}
|
||||
|
@ -36,6 +36,7 @@
|
||||
export let focus: boolean = false
|
||||
export let focusable: boolean = false
|
||||
export let disabled: boolean = false
|
||||
export let fullSize = false
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@ -74,7 +75,7 @@
|
||||
} else if (kind === 'underline') {
|
||||
target.style.width = `calc(${text.clientWidth}px + 1.125rem)`
|
||||
} else {
|
||||
target.style.width = text.clientWidth + 'px'
|
||||
target.style.width = Math.max(text.clientWidth, 50) + 'px'
|
||||
}
|
||||
dispatch('input')
|
||||
}
|
||||
@ -118,7 +119,8 @@
|
||||
|
||||
<div
|
||||
class="editbox-container"
|
||||
class:w-full={focusable}
|
||||
class:flex-grow={fullSize}
|
||||
class:w-full={focusable || fullSize}
|
||||
on:click={() => {
|
||||
input.focus()
|
||||
}}
|
||||
|
66
packages/ui/src/components/Expandable.svelte
Normal file
66
packages/ui/src/components/Expandable.svelte
Normal file
@ -0,0 +1,66 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Asset, IntlString } from '@hcengineering/platform'
|
||||
import ExpandCollapse from './ExpandCollapse.svelte'
|
||||
import Icon from './Icon.svelte'
|
||||
import Label from './Label.svelte'
|
||||
|
||||
export let icon: Asset | undefined = undefined
|
||||
export let label: IntlString | undefined = undefined
|
||||
export let expanded: boolean = false
|
||||
export let expandable = true
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="flex-grow flex" class:expanded class:expandable>
|
||||
<div
|
||||
class="fs-title flex-row-center mr-4"
|
||||
on:click|stopPropagation={() => {
|
||||
expanded = !expanded
|
||||
}}
|
||||
>
|
||||
<div class="chevron" class:expanded>▶</div>
|
||||
<div class="an-element__icon">
|
||||
{#if icon}
|
||||
<Icon {icon} size={'small'} />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="an-element__label title">
|
||||
{#if label}<Label {label} />{/if}
|
||||
</span>
|
||||
</div>
|
||||
<slot name="tools" />
|
||||
</div>
|
||||
<ExpandCollapse isExpanded={expanded}>
|
||||
<div class="antiComponent p-2">
|
||||
<slot />
|
||||
</div>
|
||||
</ExpandCollapse>
|
||||
|
||||
<style lang="scss">
|
||||
.expandable {
|
||||
.chevron {
|
||||
content: '▶';
|
||||
margin-right: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--dark-color);
|
||||
&.expanded {
|
||||
transform: rotateZ(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -21,15 +21,17 @@
|
||||
<slot name="popup-header" />
|
||||
{/if}
|
||||
{#each $modal as popup, i}
|
||||
<PopupInstance
|
||||
is={popup.is}
|
||||
props={popup.props}
|
||||
element={popup.element}
|
||||
onClose={popup.onClose}
|
||||
onUpdate={popup.onUpdate}
|
||||
zIndex={(i + 1) * 500}
|
||||
top={$modal.length - 1 === i}
|
||||
close={popup.close}
|
||||
overlay={popup.options.overlay}
|
||||
/>
|
||||
{#key popup.id}
|
||||
<PopupInstance
|
||||
is={popup.is}
|
||||
props={popup.props}
|
||||
element={popup.element}
|
||||
onClose={popup.onClose}
|
||||
onUpdate={popup.onUpdate}
|
||||
zIndex={(i + 1) * 500}
|
||||
top={$modal.length - 1 === i}
|
||||
close={popup.close}
|
||||
overlay={popup.options.overlay}
|
||||
/>
|
||||
{/key}
|
||||
{/each}
|
||||
|
@ -150,6 +150,7 @@ export { default as FocusHandler } from './components/FocusHandler.svelte'
|
||||
export { default as ListView } from './components/ListView.svelte'
|
||||
export { default as ToggleButton } from './components/ToggleButton.svelte'
|
||||
export { default as ExpandCollapse } from './components/ExpandCollapse.svelte'
|
||||
export { default as Expandable } from './components/Expandable.svelte'
|
||||
export { default as BarDashboard } from './components/BarDashboard.svelte'
|
||||
export { default as Notifications } from './components/notifications/Notifications.svelte'
|
||||
export { default as notificationsStore } from './components/notifications/store'
|
||||
|
@ -229,6 +229,15 @@ async function getAllRealValues (client: TxOperations, values: any[], _class: Re
|
||||
if (values.some((value) => typeof value !== 'string')) {
|
||||
return values
|
||||
}
|
||||
if (
|
||||
_class === core.class.TypeString ||
|
||||
_class === core.class.EnumOf ||
|
||||
_class === core.class.TypeNumber ||
|
||||
_class === core.class.TypeDate
|
||||
) {
|
||||
return values
|
||||
}
|
||||
|
||||
const realValues = await client.findAll(_class, { _id: { $in: values } })
|
||||
const realValuesIds = realValues.map(({ _id }) => _id)
|
||||
return [
|
||||
|
7
plugins/bitrix-assets/.eslintrc.js
Normal file
7
plugins/bitrix-assets/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
16
plugins/bitrix-assets/assets/icons.svg
Normal file
16
plugins/bitrix-assets/assets/icons.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<!-- ALL HASHTAGs -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="bitrix" viewBox="0 0 200.9 201.2">
|
||||
<style>
|
||||
.st0 {
|
||||
fill: #c60c30;
|
||||
}
|
||||
</style>
|
||||
<g id="layer1" transform="translate(-76.221 -152.77)">
|
||||
<path
|
||||
id="path821"
|
||||
class="st0"
|
||||
d="M157.3 352.8c-17.6-3.6-34-11.8-46.8-23.2-33-29.3-43.3-75.4-25.8-116.5C89 203 96 192.8 104.6 183.9l7.6-7.8.5 45.8c.5 43.9.6 46.1 2.7 51.6 4.2 11.1 8.6 18.1 16.3 25.7 19.8 19.7 48.8 24.6 73.5 12.5 23.2-11.4 37.8-36.3 36.1-61.5-1.1-16.9-6.8-29.3-19.1-41.6-9.3-9.3-20.2-15.4-32-17.9-12.1-2.5-11.5-2.9-11.5 7.3v8.8l6.6 1.2c25.6 4.7 42.6 29.9 37.3 55.3-1.7 8.1-5.5 15.3-11.6 21.9-9.3 10-22.4 14.8-33.8 14.8-18.9 0-34.6-10.8-42.3-26.6 0 0-2-4.5-2.6-7.3-3.8-16.5-1.4-51.1-1.4-51.1l-.3-50.7 3-1.5c1.6-.9 3.2-1.5 3.5-1.5s.6 23.3.8 51.8c0 0-3.6 37.3.9 51.6.6 2.1 1.9 5.6 1.9 5.6 3.9 8 10.2 15.1 18.4 19 6.2 2.9 9.4 4.3 18.6 4.3 6 0 11.3-1.9 17.5-4.7 7.2-3.3 15.5-12.1 18.6-20.2 8.4-22.2-3.3-46-26-52.7-9.3-2.7-9.1-2.9-9.1 7.2v8.8l4.4 1.2c16 4.3 20.9 25.8 8.3 36.8-4.4 3.9-8.4 5.6-15.4 5.6-7.5 0-14.7-4.5-18.4-11.5-2.2-4.2-2.4-8.1-2.4-9.9 0-16.7-.4-46.9-.4-46.9l-.3-51.7 2.3-.6c1.3-.3 6.4-1.1 11.4-1.7 16.9-2.2 41.9 3.4 58.7 13 22.9 13.1 40.2 35.6 47.4 61.6 2.5 8.9 2.8 11.6 2.8 25.9 0 14.3-.3 17-2.7 25.8-9.8 35.4-34.8 60.6-70.5 71.4-7.5 2.3-11 2.7-24.3 3-10.7.1-17.6-.2-22.3-1.2z"
|
||||
/></g>
|
||||
</symbol>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
5
plugins/bitrix-assets/config/rig.json
Normal file
5
plugins/bitrix-assets/config/rig.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "assets"
|
||||
}
|
15
plugins/bitrix-assets/lang/en.json
Normal file
15
plugins/bitrix-assets/lang/en.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"string": {
|
||||
"BitrixTokenUrl": "Bitrix24 Token Url",
|
||||
"Bitrix": "",
|
||||
"BitrixDesc": "Bitrix 24 integration",
|
||||
"Settings": "Configure",
|
||||
"NotAllowed": "Not allowed without administrative rights",
|
||||
"BitrixEntityType": "Entity mappping",
|
||||
"FieldMapping": "Field mapping",
|
||||
"AddField": "Add mapping",
|
||||
"Attribute": "Attribute",
|
||||
"MapField": "Map...",
|
||||
"BitrixImport": "Synchronize with Bitrix"
|
||||
}
|
||||
}
|
15
plugins/bitrix-assets/lang/ru.json
Normal file
15
plugins/bitrix-assets/lang/ru.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"string": {
|
||||
"BitrixTokenUrl": "Bitrix24 Token Url",
|
||||
"Bitrix": "",
|
||||
"BitrixDesc": "Bitrix 24 integration",
|
||||
"Settings": "Configure",
|
||||
"NotAllowed": "Not allowed without administrative rights",
|
||||
"BitrixEntityType": "Entity mappping",
|
||||
"FieldMapping": "Field mapping",
|
||||
"AddField": "Add mapping",
|
||||
"Attribute": "Attribute",
|
||||
"MapField": "Map...",
|
||||
"BitrixImport": "Synchronize with Bitrix"
|
||||
}
|
||||
}
|
35
plugins/bitrix-assets/package.json
Normal file
35
plugins/bitrix-assets/package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "@hcengineering/bitrix-assets",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"template": "@hcengineering/assets-package",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"test": "heft test",
|
||||
"build:docs": "",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"format": "prettier --write src && eslint --fix src",
|
||||
"build:watch": "tsc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint": "^8.26.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"@types/node": "~16.11.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/bitrix": "^0.6.0"
|
||||
}
|
||||
}
|
6
plugins/bitrix-assets/src/__tests__/lang.test.ts
Normal file
6
plugins/bitrix-assets/src/__tests__/lang.test.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { makeLocalesTest } from '@hcengineering/platform'
|
||||
|
||||
it(
|
||||
'Locales are equale',
|
||||
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
|
||||
)
|
24
plugins/bitrix-assets/src/index.ts
Normal file
24
plugins/bitrix-assets/src/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { addStringsLoader, loadMetadata } from '@hcengineering/platform'
|
||||
import bitrix, { bitrixId } from '@hcengineering/bitrix'
|
||||
|
||||
const icons = require('../assets/icons.svg') as string // eslint-disable-line
|
||||
loadMetadata(bitrix.icon, {
|
||||
Bitrix: `${icons}#bitrix`
|
||||
})
|
||||
|
||||
addStringsLoader(bitrixId, async (lang: string) => await import(`../lang/${lang}.json`))
|
18
plugins/bitrix-assets/tsconfig.json
Normal file
18
plugins/bitrix-assets/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"sourceMap": true,
|
||||
"types": ["node", "heft-jest"],
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
7
plugins/bitrix-resources/.eslintrc.js
Normal file
7
plugins/bitrix-resources/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/config/eslint.config.json'],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
settings: {
|
||||
'svelte3/ignore-styles': () => true
|
||||
}
|
||||
}
|
5
plugins/bitrix-resources/config/rig.json
Normal file
5
plugins/bitrix-resources/config/rig.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
"rigPackageName": "@hcengineering/platform-rig",
|
||||
"rigProfile": "ui"
|
||||
}
|
56
plugins/bitrix-resources/package.json
Normal file
56
plugins/bitrix-resources/package.json
Normal file
@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@hcengineering/bitrix-resources",
|
||||
"version": "0.6.0",
|
||||
"main": "src/index.ts",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "tsc --incremental --noEmit --outDir ./dist_cache && echo build",
|
||||
"build:docs": "api-extractor run --local",
|
||||
"lint": "svelte-check && eslint",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"format": "prettier --write --plugin-search-dir=. src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"svelte-loader": "^3.1.3",
|
||||
"sass": "^1.53.0",
|
||||
"svelte-preprocess": "^4.10.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.26.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier-plugin-svelte": "^2.8.0",
|
||||
"prettier": "^2.7.1",
|
||||
"svelte-check": "^2.8.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"svelte": "^3.47",
|
||||
"@hcengineering/bitrix": "^0.6.0",
|
||||
"@hcengineering/ui": "^0.6.2",
|
||||
"@hcengineering/presentation": "~0.6.2",
|
||||
"@hcengineering/text-editor": "~0.6.0",
|
||||
"@hcengineering/contact": "~0.6.8",
|
||||
"@hcengineering/lead": "~0.6.0",
|
||||
"@hcengineering/setting": "~0.6.1",
|
||||
"@hcengineering/core": "^0.6.19",
|
||||
"@hcengineering/attachment": "~0.6.1",
|
||||
"@hcengineering/attachment-resources": "~0.6.0",
|
||||
"@hcengineering/view": "^0.6.1",
|
||||
"@hcengineering/view-resources": "~0.6.0",
|
||||
"@hcengineering/setting-resources": "~0.6.0",
|
||||
"@hcengineering/chunter": "~0.6.1",
|
||||
"p-queue": "~7.3.0",
|
||||
"qs": "~6.11.0",
|
||||
"@types/qs": "~6.9.7",
|
||||
"@hcengineering/tags": "~0.6.2",
|
||||
"@hcengineering/tags-resources": "~0.6.0",
|
||||
"fast-equals": "^2.0.3"
|
||||
}
|
||||
}
|
5
plugins/bitrix-resources/postcss.config.js
Normal file
5
plugins/bitrix-resources/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('autoprefixer')
|
||||
]
|
||||
}
|
28
plugins/bitrix-resources/src/client.ts
Normal file
28
plugins/bitrix-resources/src/client.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import PQueue from 'p-queue'
|
||||
import { stringify as toQuery } from 'qs'
|
||||
import { BitrixResult } from './types'
|
||||
|
||||
const queue = new PQueue({
|
||||
intervalCap: 1,
|
||||
interval: 1000
|
||||
})
|
||||
export class BitrixClient {
|
||||
constructor (readonly url: string) {}
|
||||
|
||||
async call (method: string, params: any): Promise<BitrixResult> {
|
||||
return await queue.add(async () => {
|
||||
let query: string = toQuery(params)
|
||||
if (query.length > 0) {
|
||||
query = `?${query}`
|
||||
}
|
||||
return await (
|
||||
await fetch(`${this.url}/${method}${query}`, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'user-agent': 'anticrm'
|
||||
}
|
||||
})
|
||||
).json()
|
||||
})
|
||||
}
|
||||
}
|
102
plugins/bitrix-resources/src/components/AttributeMapper.svelte
Normal file
102
plugins/bitrix-resources/src/components/AttributeMapper.svelte
Normal file
@ -0,0 +1,102 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, MappingOperation, Fields } from '@hcengineering/bitrix'
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, eventToHTMLElement, IconDelete, Menu, showPopup, Action, IconEdit } from '@hcengineering/ui'
|
||||
import bitrix from '../plugin'
|
||||
import CreateMappingAttribute from './CreateMappingAttribute.svelte'
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let fieldMapping: BitrixFieldMapping[]
|
||||
export let fields: Fields
|
||||
export let attribute: AnyAttribute
|
||||
|
||||
$: mappedField = fieldMapping.find((it) => it.attributeName === attribute.name)
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const allowed = new Set([
|
||||
core.class.TypeString,
|
||||
core.class.TypeMarkup,
|
||||
core.class.TypeBoolean,
|
||||
core.class.TypeDate,
|
||||
core.class.TypeNumber,
|
||||
core.class.EnumOf,
|
||||
core.class.Collection
|
||||
])
|
||||
|
||||
function addMapping (evt: MouseEvent, kind: MappingOperation): void {
|
||||
showPopup(
|
||||
CreateMappingAttribute,
|
||||
{
|
||||
mapping,
|
||||
attribute,
|
||||
fields,
|
||||
kind
|
||||
},
|
||||
'middle'
|
||||
)
|
||||
}
|
||||
|
||||
$: actions = [
|
||||
{
|
||||
label: getEmbeddedLabel('Add Copy mapping'),
|
||||
action: (_: any, evt: MouseEvent) => {
|
||||
addMapping(evt, MappingOperation.CopyValue)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: getEmbeddedLabel('Add Tag mapping'),
|
||||
action: (_: any, evt: MouseEvent) => {
|
||||
addMapping(evt, MappingOperation.CreateTag)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: getEmbeddedLabel('Add Channel mapping'),
|
||||
action: (_: any, evt: MouseEvent) => {
|
||||
addMapping(evt, MappingOperation.CreateChannel)
|
||||
}
|
||||
}
|
||||
] as Action[]
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center">
|
||||
{#if mappedField !== undefined}
|
||||
<Button
|
||||
icon={IconEdit}
|
||||
on:click={(evt) => {
|
||||
showPopup(
|
||||
CreateMappingAttribute,
|
||||
{
|
||||
mapping,
|
||||
attribute,
|
||||
fields,
|
||||
field: mappedField
|
||||
},
|
||||
'middle'
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
on:click={() => {
|
||||
if (mappedField) {
|
||||
client.remove(mappedField)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else if allowed.has(attribute.type._class)}
|
||||
<Button
|
||||
label={bitrix.string.MapField}
|
||||
on:click={(evt) => {
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
actions
|
||||
},
|
||||
eventToHTMLElement(evt)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
@ -0,0 +1,96 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import presentation, { Card, createQuery } from '@hcengineering/presentation'
|
||||
import { Integration } from '@hcengineering/setting'
|
||||
import { createEventDispatcher, onMount } from 'svelte'
|
||||
import bitrix from '../plugin'
|
||||
|
||||
import { BitrixEntityMapping } from '@hcengineering/bitrix'
|
||||
import { Button, eventToHTMLElement, IconAdd, Label, showPopup } from '@hcengineering/ui'
|
||||
import { BitrixClient } from '../client'
|
||||
import { BitrixProfile, StatusValue } from '../types'
|
||||
import CreateMapping from './CreateMapping.svelte'
|
||||
import EntiryMapping from './EntityMapping.svelte'
|
||||
|
||||
export let integration: Integration
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const bitrixClient = new BitrixClient(integration.value)
|
||||
|
||||
let profile: BitrixProfile | undefined
|
||||
|
||||
let statusList: StatusValue[] = []
|
||||
|
||||
onMount(() => {
|
||||
bitrixClient.call('profile', {}).then((res: any) => {
|
||||
profile = res.result
|
||||
})
|
||||
|
||||
bitrixClient.call('crm.status.list', {}).then((res) => {
|
||||
statusList = res.result
|
||||
})
|
||||
})
|
||||
|
||||
const mQuery = createQuery()
|
||||
|
||||
let mappings: BitrixEntityMapping[] = []
|
||||
$: mQuery.query(bitrix.class.EntityMapping, {}, (res) => {
|
||||
mappings = res
|
||||
})
|
||||
|
||||
function addMapping (evt: MouseEvent): void {
|
||||
showPopup(CreateMapping, { integration, mappings, bitrixClient }, eventToHTMLElement(evt))
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={bitrix.string.BitrixDesc}
|
||||
okAction={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
canSave={true}
|
||||
okLabel={presentation.string.Ok}
|
||||
on:close={() => dispatch('close')}
|
||||
fullSize={true}
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
{#if profile}
|
||||
<div class="lines-limit-2">
|
||||
{profile.LAST_NAME}
|
||||
{profile.NAME}
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{#if profile}
|
||||
{#if !profile.ADMIN}
|
||||
<Label label={bitrix.string.NotAllowed} />
|
||||
{:else}
|
||||
<div class="flex flex-reverse flex-grab">
|
||||
<Button icon={IconAdd} label={presentation.string.Add} on:click={addMapping} />
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
{#each mappings as mapping}
|
||||
<EntiryMapping {mapping} {bitrixClient} {statusList} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- <EditBox label={bitrix.string.BitrixTokenUrl} bind:value={url} /> -->
|
||||
<svelte:fragment slot="pool" />
|
||||
</Card>
|
44
plugins/bitrix-resources/src/components/BitrixConnect.svelte
Normal file
44
plugins/bitrix-resources/src/components/BitrixConnect.svelte
Normal file
@ -0,0 +1,44 @@
|
||||
<!--
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
-->
|
||||
<script lang="ts">
|
||||
import presentation, { Card } from '@hcengineering/presentation'
|
||||
import { Integration } from '@hcengineering/setting'
|
||||
import { EditBox } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import bitrix from '../plugin'
|
||||
|
||||
export let integration: Integration
|
||||
|
||||
let url: string = integration.value
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function save (): void {
|
||||
dispatch('close', { value: url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={bitrix.string.BitrixDesc}
|
||||
okAction={save}
|
||||
canSave={true}
|
||||
okLabel={presentation.string.Ok}
|
||||
on:close={() => dispatch('close')}
|
||||
>
|
||||
<EditBox label={bitrix.string.BitrixTokenUrl} bind:value={url} />
|
||||
<svelte:fragment slot="pool" />
|
||||
</Card>
|
142
plugins/bitrix-resources/src/components/BitrixFieldLookup.svelte
Normal file
142
plugins/bitrix-resources/src/components/BitrixFieldLookup.svelte
Normal file
@ -0,0 +1,142 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, Fields, FieldValue } from '@hcengineering/bitrix'
|
||||
import core, { Enum, Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import setting from '@hcengineering/setting-resources/src/plugin'
|
||||
import {
|
||||
Button,
|
||||
DatePresenter,
|
||||
EditBox,
|
||||
eventToHTMLElement,
|
||||
getEventPositionElement,
|
||||
IconAdd,
|
||||
IconEdit,
|
||||
Menu,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import Grid from '@hcengineering/ui/src/components/Grid.svelte'
|
||||
import { BitrixClient } from '../client'
|
||||
|
||||
import EnumPopup from './EnumPopup.svelte'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let bitrixClient: BitrixClient
|
||||
export let fields: Fields = {}
|
||||
|
||||
const client = getClient()
|
||||
|
||||
let items: any[] = []
|
||||
function loadItems (order: boolean): void {
|
||||
bitrixClient
|
||||
.call(mapping.type + '.list', { select: ['*', 'UF_*'], order: { ID: order ? 'ASC' : 'DSC' } })
|
||||
.then((res) => {
|
||||
items = res.result
|
||||
})
|
||||
}
|
||||
|
||||
const enumQuery = createQuery()
|
||||
|
||||
let enums: Enum[] = []
|
||||
enumQuery.query(core.class.Enum, {}, (res) => {
|
||||
enums = res
|
||||
})
|
||||
|
||||
async function updateEnum (evt: MouseEvent, fieldId: string, field: FieldValue): Promise<void> {
|
||||
const enumId = ('bitrix_' + fieldId) as Ref<Enum>
|
||||
const existingEnum = await client.findOne(core.class.Enum, { _id: enumId })
|
||||
if (existingEnum !== undefined) {
|
||||
existingEnum.enumValues = [...existingEnum.enumValues, ...(field.items?.map((it) => it.VALUE) ?? [])]
|
||||
existingEnum.enumValues = existingEnum.enumValues.filter((it, idx) => existingEnum.enumValues.indexOf(it) === idx)
|
||||
showPopup(setting.component.EditEnum, { value: existingEnum }, 'top')
|
||||
} else {
|
||||
showPopup(
|
||||
Menu,
|
||||
{
|
||||
actions: [
|
||||
{
|
||||
label: getEmbeddedLabel('New'),
|
||||
action: (_: any, evt: MouseEvent) => {
|
||||
showPopup(
|
||||
setting.component.EditEnum,
|
||||
{
|
||||
name: 'Bitrix:' + field.formLabel ?? field.title,
|
||||
values: field.items?.map((it) => it.VALUE) ?? [],
|
||||
value: existingEnum
|
||||
},
|
||||
getEventPositionElement(evt)
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Modify existing',
|
||||
component: EnumPopup,
|
||||
props: {
|
||||
_class: core.class.Enum,
|
||||
action: async (existingEnum: Enum) => {
|
||||
if (existingEnum !== undefined) {
|
||||
existingEnum.enumValues = [
|
||||
...existingEnum.enumValues,
|
||||
...(field.items?.map((it) => it.VALUE) ?? [])
|
||||
]
|
||||
existingEnum.enumValues = existingEnum.enumValues.filter(
|
||||
(it, idx) => existingEnum.enumValues.indexOf(it) === idx
|
||||
)
|
||||
showPopup(setting.component.EditEnum, { value: existingEnum }, 'top')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
eventToHTMLElement(evt)
|
||||
)
|
||||
}
|
||||
}
|
||||
let pattern = ''
|
||||
</script>
|
||||
|
||||
<div class="top-divider">
|
||||
<div class="flex gap-2">
|
||||
<Button label={getEmbeddedLabel('Load First N values')} on:click={() => loadItems(true)} />
|
||||
<Button label={getEmbeddedLabel('Load Last N values')} on:click={() => loadItems(false)} />
|
||||
</div>
|
||||
<EditBox kind={'search-style'} bind:value={pattern} />
|
||||
</div>
|
||||
<div class="p-2 top-divider" style:overflow={'auto'} style:max-height={'40rem'}>
|
||||
<Grid column={2} rowGap={1}>
|
||||
{#each Object.entries(fields) as kv}
|
||||
{@const field = kv[1]}
|
||||
{@const title = field.formLabel ?? field.title}
|
||||
{#if title.includes(pattern) || field.type.includes(pattern) || pattern.length === 0}
|
||||
<span class="fs-title select-text flex-row-center">
|
||||
{title} : {field.type}
|
||||
<span class="select-text ml-2 mr-2">
|
||||
"{kv[0]}"
|
||||
</span>
|
||||
{#if field.type === 'enumeration' || field.type === 'crm_status'}
|
||||
<Button
|
||||
icon={enums.find((it) => it._id === 'bitrix_' + kv[0]) ? IconEdit : IconAdd}
|
||||
on:click={(evt) => updateEnum(evt, kv[0], field)}
|
||||
/>
|
||||
{/if}
|
||||
</span>
|
||||
<div class="whitespace-nowrap select-text flex-row-center gap-2">
|
||||
{#each items.map((it) => it[kv[0]]).filter((it) => it != null && it !== '') as value}
|
||||
<div class="ml-2">
|
||||
{#if value !== null && value !== undefined && value !== ''}
|
||||
{#if (field.type === 'datetime' || field.type === 'date') && value != null && value !== ''}
|
||||
<DatePresenter value={new Date(value).getTime()} withTime={field.type === 'datetime'} />
|
||||
{:else if field.type === 'enumeration'}
|
||||
{field.items?.find((it) => it.ID === value)?.VALUE}
|
||||
{:else}
|
||||
{JSON.stringify(value)}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
61
plugins/bitrix-resources/src/components/BitrixImport.svelte
Normal file
61
plugins/bitrix-resources/src/components/BitrixImport.svelte
Normal file
@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping } from '@hcengineering/bitrix'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { Card, createQuery } from '@hcengineering/presentation'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { BitrixClient } from '../client'
|
||||
import bitrix from '../plugin'
|
||||
import FieldMappingSynchronizer from './FieldMappingSynchronizer.svelte'
|
||||
|
||||
const mappingQuery = createQuery()
|
||||
|
||||
let mappings: BitrixEntityMapping[] = []
|
||||
|
||||
mappingQuery.query(
|
||||
bitrix.class.EntityMapping,
|
||||
{},
|
||||
(res) => {
|
||||
mappings = res
|
||||
},
|
||||
{
|
||||
lookup: {
|
||||
_id: {
|
||||
fields: bitrix.class.FieldMapping
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const q = createQuery()
|
||||
|
||||
let integration: Integration | undefined
|
||||
|
||||
q.query(setting.class.Integration, { type: bitrix.integrationType.Bitrix }, (res) => {
|
||||
integration = res.shift()
|
||||
})
|
||||
|
||||
$: bitrixClient = integration !== undefined ? new BitrixClient(integration.value) : undefined
|
||||
let loading = false
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={getEmbeddedLabel('Bitrix Synchronization...')}
|
||||
canSave={!loading}
|
||||
fullSize={false}
|
||||
okAction={() => {
|
||||
dispatch('close')
|
||||
}}
|
||||
okLabel={getEmbeddedLabel('Close')}
|
||||
on:close
|
||||
>
|
||||
{#if integration && bitrixClient}
|
||||
{#each mappings as mapping}
|
||||
<FieldMappingSynchronizer {mapping} {bitrixClient} bind:loading />
|
||||
{/each}
|
||||
{:else}
|
||||
<Label label={getEmbeddedLabel('No integration configured')} />
|
||||
{/if}
|
||||
</Card>
|
67
plugins/bitrix-resources/src/components/CreateMapping.svelte
Normal file
67
plugins/bitrix-resources/src/components/CreateMapping.svelte
Normal file
@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixEntityType, mappingTypes } from '@hcengineering/bitrix'
|
||||
import core, { Class, ClassifierKind, Doc, Obj, Ref } from '@hcengineering/core'
|
||||
import { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import setting, { Integration } from '@hcengineering/setting'
|
||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||
import { ObjectBox } from '@hcengineering/view-resources'
|
||||
import bitrix from '../plugin'
|
||||
|
||||
export let integration: Integration
|
||||
export let mappings: BitrixEntityMapping[]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function save (): Promise<void> {
|
||||
client.createDoc(bitrix.class.EntityMapping, bitrix.space.Mappings, {
|
||||
ofClass,
|
||||
type
|
||||
})
|
||||
}
|
||||
|
||||
$: existingTypes = new Set(mappings.map((it) => it.type))
|
||||
$: items = existingTypes !== undefined ? mappingTypes.filter((it) => !existingTypes.has(it.id)) : []
|
||||
|
||||
let type: string = BitrixEntityType.Lead
|
||||
let ofClass: Ref<Class<Doc>>
|
||||
|
||||
$: if (items.find((it) => it.id === type) === undefined) {
|
||||
type = items[0]?.id ?? ''
|
||||
}
|
||||
|
||||
const classQuery = createQuery()
|
||||
|
||||
let _classes: Ref<Class<Doc>>[] = []
|
||||
|
||||
$: classQuery.query(
|
||||
core.class.Class,
|
||||
{
|
||||
label: { $exists: true },
|
||||
kind: ClassifierKind.CLASS,
|
||||
[setting.mixin.Editable + '.value']: true
|
||||
},
|
||||
(res) => {
|
||||
const withDerived: Ref<Class<Obj>>[] = []
|
||||
for (const r of res) {
|
||||
withDerived.push(...client.getHierarchy().getDescendants(r._id))
|
||||
}
|
||||
_classes = withDerived
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<Card label={bitrix.string.AddMapping} canSave={type !== ''} okAction={save} on:close>
|
||||
<div class="flex">
|
||||
<DropdownLabels label={bitrix.string.BitrixEntityType} {items} bind:selected={type} />
|
||||
<ObjectBox
|
||||
_class={core.class.Class}
|
||||
label={core.string.Class}
|
||||
showNavigate={false}
|
||||
bind:value={ofClass}
|
||||
docQuery={{
|
||||
_id: { $in: _classes },
|
||||
kind: ClassifierKind.CLASS
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, Fields, MappingOperation } from '@hcengineering/bitrix'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import presentation, { Card } from '@hcengineering/presentation'
|
||||
import { Label } from '@hcengineering/ui'
|
||||
import bitrix from '../plugin'
|
||||
import CopyMapping from './mappings/CopyMapping.svelte'
|
||||
import CreateChannelMapping from './mappings/CreateChannelMapping.svelte'
|
||||
import CreateTagMapping from './mappings/CreateTagMapping.svelte'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let fields: Fields = {}
|
||||
export let attribute: AnyAttribute
|
||||
export let kind: MappingOperation | undefined
|
||||
export let field: BitrixFieldMapping | undefined
|
||||
|
||||
$: _kind = kind ?? field?.operation.kind
|
||||
|
||||
let op: CopyMapping | CreateTagMapping | CreateChannelMapping
|
||||
|
||||
async function save (): Promise<void> {
|
||||
op.save()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Card
|
||||
label={bitrix.string.AddField}
|
||||
canSave={attribute !== undefined}
|
||||
okAction={save}
|
||||
okLabel={presentation.string.Save}
|
||||
on:close
|
||||
>
|
||||
<svelte:fragment slot="header">
|
||||
<Label label={attribute.label} />
|
||||
</svelte:fragment>
|
||||
{#if _kind === MappingOperation.CopyValue}
|
||||
<CopyMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||
{:else if _kind === MappingOperation.CreateTag}
|
||||
<CreateTagMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||
{:else if _kind === MappingOperation.CreateChannel}
|
||||
<CreateChannelMapping {mapping} {fields} {attribute} {field} bind:this={op} />
|
||||
{/if}
|
||||
</Card>
|
167
plugins/bitrix-resources/src/components/EntityMapping.svelte
Normal file
167
plugins/bitrix-resources/src/components/EntityMapping.svelte
Normal file
@ -0,0 +1,167 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, Fields, mappingTypes } from '@hcengineering/bitrix'
|
||||
import { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { ClassSetting } from '@hcengineering/setting-resources'
|
||||
import { Button, Expandable, Icon, IconDelete, IconEdit, Label, showPopup } from '@hcengineering/ui'
|
||||
import { BitrixClient } from '../client'
|
||||
import bitrix from '../plugin'
|
||||
|
||||
import AttributeMapper from './AttributeMapper.svelte'
|
||||
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
||||
|
||||
import CheckBox from '@hcengineering/ui/src/components/CheckBox.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { StatusValue } from '../types'
|
||||
import { toClassRef } from '../utils'
|
||||
import BitrixFieldLookup from './BitrixFieldLookup.svelte'
|
||||
import CreateMappingAttribute from './CreateMappingAttribute.svelte'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let bitrixClient: BitrixClient
|
||||
export let statusList: StatusValue[] = []
|
||||
|
||||
const client = getClient()
|
||||
$: ofClass = client.getHierarchy().getClass(mapping.ofClass)
|
||||
|
||||
$: typeTitle = mappingTypes.find((it) => it.id === mapping.type)
|
||||
|
||||
let fields: Fields = {}
|
||||
bitrixClient.call(mapping.type + '.fields', {}).then((res) => {
|
||||
fields = res.result
|
||||
})
|
||||
|
||||
async function updateMappingFields (
|
||||
mapping: BitrixEntityMapping,
|
||||
fields: Fields,
|
||||
statusList: StatusValue[]
|
||||
): Promise<void> {
|
||||
if (deepEqual(fields, {})) {
|
||||
return // no value resieved yet.
|
||||
}
|
||||
// Update fields with status valies if missing.
|
||||
for (const f of Object.values(fields)) {
|
||||
if (f.type === 'crm_status') {
|
||||
f.items = statusList
|
||||
.filter((it) => it.ENTITY_ID === f.statusType)
|
||||
.map((it) => ({ ID: `${it.STATUS_ID}`, VALUE: it.NAME }))
|
||||
}
|
||||
}
|
||||
|
||||
// Store if changed.
|
||||
// TODO: We need to inform about mapping changes and remove old ones.
|
||||
if (!deepEqual(mapping.bitrixFields, fields)) {
|
||||
await client.update(mapping, { bitrixFields: fields })
|
||||
}
|
||||
}
|
||||
|
||||
$: updateMappingFields(mapping, fields, statusList)
|
||||
|
||||
const attrs = createQuery()
|
||||
let fieldMapping: BitrixFieldMapping[] = []
|
||||
|
||||
$: attrs.query(bitrix.class.FieldMapping, { attachedTo: mapping._id }, (res) => {
|
||||
fieldMapping = res
|
||||
})
|
||||
|
||||
$: fieldsByClass = fieldMapping.reduce((p, c) => {
|
||||
p[c.ofClass] = [...(p[c.ofClass] ?? []), c]
|
||||
return p
|
||||
}, {} as Record<Ref<Class<Doc>>, BitrixFieldMapping[]>)
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="antiComponent max-w-240 flex-grow p-1">
|
||||
<Expandable icon={ofClass?.icon} label={getEmbeddedLabel(typeTitle?.label ?? '')}>
|
||||
<svelte:fragment slot="tools">
|
||||
<div class="flex flex-reverse flex-grow">
|
||||
<Button icon={IconDelete} on:click={() => client.remove(mapping)} size={'small'} />
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<Expandable label={getEmbeddedLabel('Options')}>
|
||||
<div class="flex-col">
|
||||
<div class="flex-row-center">
|
||||
<CheckBox
|
||||
bind:checked={mapping.comments}
|
||||
on:value={(evt) => client.update(mapping, { comments: evt.detail })}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Label label={getEmbeddedLabel('Comments')} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-center">
|
||||
<CheckBox
|
||||
bind:checked={mapping.attachments}
|
||||
on:value={(evt) => client.update(mapping, { attachments: evt.detail })}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Label label={getEmbeddedLabel('Attachments')} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-row-center">
|
||||
<CheckBox
|
||||
bind:checked={mapping.activity}
|
||||
readonly
|
||||
on:value={(evt) => client.update(mapping, { activity: evt.detail })}
|
||||
/>
|
||||
<div class="ml-2">
|
||||
<Label label={getEmbeddedLabel('Activity')} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Expandable>
|
||||
<Expandable label={getEmbeddedLabel('Mappings')} expanded>
|
||||
<div class="flex-row flex-grow bottom-divider p-2">
|
||||
{#each Object.entries(fieldsByClass) as field, i}
|
||||
{@const cl = client.getHierarchy().getClass(toClassRef(field[0]))}
|
||||
<div class="fs-title flex-row-center">
|
||||
{#if cl.icon}
|
||||
<div class="mr-1">
|
||||
<Icon icon={cl.icon} size={'large'} />
|
||||
</div>
|
||||
{/if}
|
||||
<Label label={cl.label} />
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
{#each field[1] as cfield, i}
|
||||
<div class="fs-title flex-row-center ml-4">
|
||||
{i + 1}.
|
||||
<FieldMappingPresenter {mapping} value={cfield} />
|
||||
<Button
|
||||
icon={IconEdit}
|
||||
on:click={(evt) => {
|
||||
showPopup(
|
||||
CreateMappingAttribute,
|
||||
{
|
||||
mapping,
|
||||
attribute: client.getHierarchy().getAttribute(cfield.ofClass, cfield.attributeName),
|
||||
fields,
|
||||
field: cfield
|
||||
},
|
||||
'middle'
|
||||
)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Expandable>
|
||||
<Expandable label={getEmbeddedLabel('Class settings')}>
|
||||
<ClassSetting
|
||||
ofClass={mapping.ofClass}
|
||||
withoutHeader
|
||||
attributeMapper={{
|
||||
component: AttributeMapper,
|
||||
props: { mapping, fields, fieldMapping },
|
||||
label: bitrix.string.FieldMapping
|
||||
}}
|
||||
/>
|
||||
</Expandable>
|
||||
<Expandable label={getEmbeddedLabel('Bitrix field lookup')}>
|
||||
<BitrixFieldLookup {mapping} {bitrixClient} {fields} />
|
||||
</Expandable>
|
||||
</Expandable>
|
||||
</div>
|
46
plugins/bitrix-resources/src/components/EnumPopup.svelte
Normal file
46
plugins/bitrix-resources/src/components/EnumPopup.svelte
Normal file
@ -0,0 +1,46 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { Class, DocumentQuery, Enum, Ref } from '@hcengineering/core'
|
||||
import { ObjectPopup } from '@hcengineering/presentation'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let _class: Ref<Class<Enum>>
|
||||
export let selected: Ref<Enum> | undefined
|
||||
export let query: DocumentQuery<Enum> | undefined
|
||||
export let action: (evt: Enum) => void | Promise<void>
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<ObjectPopup
|
||||
{_class}
|
||||
{selected}
|
||||
bind:docQuery={query}
|
||||
multiSelect={false}
|
||||
allowDeselect={false}
|
||||
shadows={true}
|
||||
on:update
|
||||
on:close={(evt) => {
|
||||
if (evt.detail != null) {
|
||||
action(evt.detail)
|
||||
}
|
||||
dispatch('close')
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="item" let:item>
|
||||
{item.name}
|
||||
</svelte:fragment>
|
||||
</ObjectPopup>
|
@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, MappingOperation } from '@hcengineering/bitrix'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Icon, IconArrowLeft, Label } from '@hcengineering/ui'
|
||||
import CopyMappingPresenter from './mappings/CopyMappingPresenter.svelte'
|
||||
import CreateChannelMappingPresenter from './mappings/CreateChannelMappingPresenter.svelte'
|
||||
import CreateTagMappingPresenter from './mappings/CreateTagMappingPresenter.svelte'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let value: BitrixFieldMapping
|
||||
$: kind = value.operation.kind
|
||||
|
||||
const attr: AnyAttribute | undefined = getClient().getHierarchy().getAttribute(value.ofClass, value.attributeName)
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center top-divider">
|
||||
{#if attr}
|
||||
<div class="ml-4 fs-title mr-2 flex-row-center">
|
||||
<Label label={attr.label} />
|
||||
<Icon icon={IconArrowLeft} size={'small'} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if mapping && mapping.bitrixFields}
|
||||
{#if kind === MappingOperation.CopyValue}
|
||||
<CopyMappingPresenter {mapping} {value} />
|
||||
{:else if kind === MappingOperation.CreateTag}
|
||||
<CreateTagMappingPresenter {mapping} {value} />
|
||||
{:else if kind === MappingOperation.CreateChannel}
|
||||
<CreateChannelMappingPresenter {mapping} {value} />
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
@ -0,0 +1,389 @@
|
||||
<script lang="ts">
|
||||
import bitrix, {
|
||||
BitrixEntityMapping,
|
||||
BitrixEntityType,
|
||||
BitrixFieldMapping,
|
||||
BitrixSyncDoc
|
||||
} from '@hcengineering/bitrix'
|
||||
import chunter, { Comment } from '@hcengineering/chunter'
|
||||
import contact, { combineName, EmployeeAccount } from '@hcengineering/contact'
|
||||
import core, {
|
||||
AccountRole,
|
||||
ApplyOperations,
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Data,
|
||||
Doc,
|
||||
DocumentUpdate,
|
||||
generateId,
|
||||
Mixin,
|
||||
Ref,
|
||||
Space,
|
||||
WithLookup
|
||||
} from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getClient, SpaceSelect } from '@hcengineering/presentation'
|
||||
import { TagElement } from '@hcengineering/tags'
|
||||
import { Button, Expandable, Icon, Label } from '@hcengineering/ui'
|
||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||
import { NumberEditor } from '@hcengineering/view-resources'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { BitrixClient } from '../client'
|
||||
import { convert, ConvertResult, toClassRef } from '../utils'
|
||||
import FieldMappingPresenter from './FieldMappingPresenter.svelte'
|
||||
|
||||
export let mapping: WithLookup<BitrixEntityMapping>
|
||||
export let bitrixClient: BitrixClient
|
||||
|
||||
const client = getClient()
|
||||
|
||||
$: fieldMapping = (mapping.$lookup?.fields as BitrixFieldMapping[]) ?? []
|
||||
$: fieldsByClass = fieldMapping.reduce((p, c) => {
|
||||
p[c.ofClass] = [...(p[c.ofClass] ?? []), c]
|
||||
return p
|
||||
}, {} as Record<Ref<Class<Doc>>, BitrixFieldMapping[]>)
|
||||
|
||||
let direction: 'ASC' | 'DSC' = 'DSC'
|
||||
let limit = 200
|
||||
let space: Ref<Space> | undefined
|
||||
|
||||
export let loading = false
|
||||
let state = ''
|
||||
|
||||
async function updateDoc (client: ApplyOperations, doc: Doc, raw: Doc | Data<Doc>): Promise<void> {
|
||||
// We need to update fields if they are different.
|
||||
const documentUpdate: DocumentUpdate<Doc> = {}
|
||||
for (const [k, v] of Object.entries(raw)) {
|
||||
if (['_class', '_id', 'modifiedBy', 'modifiedOn', 'space'].includes(k)) {
|
||||
continue
|
||||
}
|
||||
if (!deepEqual((doc as any)[k], v)) {
|
||||
;(documentUpdate as any)[k] = v
|
||||
}
|
||||
}
|
||||
if (Object.keys(documentUpdate).length > 0) {
|
||||
await client.update(doc, documentUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
let docsProcessed = 0
|
||||
let total = 0
|
||||
|
||||
async function syncPlatform (documents: ConvertResult[]): Promise<void> {
|
||||
const existingDocuments = await client.findAll(mapping.ofClass, {
|
||||
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: documents.map((it) => it.document.bitrixId) }
|
||||
})
|
||||
const hierarchy = client.getHierarchy()
|
||||
for (const d of documents) {
|
||||
const existing = existingDocuments.find(
|
||||
(it) => hierarchy.as(it, bitrix.mixin.BitrixSyncDoc).bitrixId === d.document.bitrixId
|
||||
)
|
||||
const applyOp = client.apply('bitrix')
|
||||
if (existing !== undefined) {
|
||||
// We need to update fields if they are different.
|
||||
await updateDoc(applyOp, existing, d.document)
|
||||
|
||||
// Check and update mixins
|
||||
for (const [m, mv] of Object.entries(d.mixins)) {
|
||||
const mRef = m as Ref<Mixin<Doc>>
|
||||
if (hierarchy.hasMixin(existing, mRef)) {
|
||||
await applyOp.createMixin(
|
||||
d.document._id,
|
||||
d.document._class,
|
||||
d.document.space,
|
||||
m as Ref<Mixin<Doc>>,
|
||||
mv,
|
||||
d.document.modifiedOn,
|
||||
d.document.modifiedBy
|
||||
)
|
||||
} else {
|
||||
const existingM = hierarchy.as(existing, mRef)
|
||||
await updateDoc(applyOp, existingM, mv)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
await applyOp.createDoc(
|
||||
d.document._class,
|
||||
d.document.space,
|
||||
d.document,
|
||||
d.document._id,
|
||||
d.document.modifiedOn,
|
||||
d.document.modifiedBy
|
||||
)
|
||||
|
||||
await applyOp.createMixin<Doc, BitrixSyncDoc>(
|
||||
d.document._id,
|
||||
d.document._class,
|
||||
d.document.space,
|
||||
bitrix.mixin.BitrixSyncDoc,
|
||||
{
|
||||
type: d.document.type,
|
||||
bitrixId: d.document.bitrixId
|
||||
},
|
||||
d.document.modifiedOn,
|
||||
d.document.modifiedBy
|
||||
)
|
||||
for (const [m, mv] of Object.entries(d.mixins)) {
|
||||
await applyOp.createMixin(
|
||||
d.document._id,
|
||||
d.document._class,
|
||||
d.document.space,
|
||||
m as Ref<Mixin<Doc>>,
|
||||
mv,
|
||||
d.document.modifiedOn,
|
||||
d.document.modifiedBy
|
||||
)
|
||||
}
|
||||
for (const ed of d.extraDocs) {
|
||||
if (applyOp.getHierarchy().isDerived(ed._class, core.class.AttachedDoc)) {
|
||||
const adoc = ed as AttachedDoc
|
||||
await applyOp.addCollection(
|
||||
adoc._class,
|
||||
adoc.space,
|
||||
adoc.attachedTo,
|
||||
adoc.attachedToClass,
|
||||
adoc.collection,
|
||||
adoc,
|
||||
adoc._id,
|
||||
d.document.modifiedOn,
|
||||
d.document.modifiedBy
|
||||
)
|
||||
} else {
|
||||
await applyOp.createDoc(ed._class, ed.space, ed, ed._id, d.document.modifiedOn, d.document.modifiedBy)
|
||||
}
|
||||
}
|
||||
|
||||
if (d.comments !== undefined) {
|
||||
const comments = await d.comments
|
||||
if (comments !== undefined && comments.length > 0) {
|
||||
const existingComments = await client.findAll(chunter.class.Comment, {
|
||||
attachedTo: d.document._id,
|
||||
[bitrix.mixin.BitrixSyncDoc + '.bitrixId']: { $in: comments.map((it) => it.bitrixId) }
|
||||
})
|
||||
|
||||
for (const comment of comments) {
|
||||
const existing = existingComments.find(
|
||||
(it) => hierarchy.as<Doc, BitrixSyncDoc>(it, bitrix.mixin.BitrixSyncDoc).bitrixId === comment.bitrixId
|
||||
)
|
||||
if (existing !== undefined) {
|
||||
// We need to update fields if they are different.
|
||||
await updateDoc(applyOp, existing, comment)
|
||||
} else {
|
||||
await applyOp.addCollection(
|
||||
comment._class,
|
||||
comment.space,
|
||||
comment.attachedTo,
|
||||
comment.attachedToClass,
|
||||
comment.collection,
|
||||
comment,
|
||||
comment._id,
|
||||
comment.modifiedOn,
|
||||
comment.modifiedBy
|
||||
)
|
||||
|
||||
await applyOp.createMixin<Doc, BitrixSyncDoc>(
|
||||
comment._id,
|
||||
comment._class,
|
||||
comment.space,
|
||||
bitrix.mixin.BitrixSyncDoc,
|
||||
{
|
||||
type: d.document.type,
|
||||
bitrixId: d.document.bitrixId
|
||||
},
|
||||
comment.modifiedOn,
|
||||
comment.modifiedBy
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await applyOp.commit()
|
||||
docsProcessed++
|
||||
state = `processed: ${docsProcessed}/${total}`
|
||||
}
|
||||
}
|
||||
|
||||
async function doSync (): Promise<void> {
|
||||
loading = true
|
||||
|
||||
const commentFields = await bitrixClient.call(BitrixEntityType.Comment + '.fields', {})
|
||||
|
||||
const commentFieldKeys = Object.keys(commentFields.result)
|
||||
|
||||
const allEmployee = await client.findAll(contact.class.EmployeeAccount, {})
|
||||
|
||||
const userList = new Map<string, Ref<EmployeeAccount>>()
|
||||
|
||||
// Fill all users and create new ones, if required.
|
||||
let totalUsers = 1
|
||||
let next = 0
|
||||
while (userList.size < totalUsers) {
|
||||
const users = await bitrixClient.call('user.search', { start: next })
|
||||
next = users.next
|
||||
totalUsers = users.total
|
||||
for (const u of users.result) {
|
||||
let accountId = allEmployee.find((it) => it.email === u.EMAIL)?._id
|
||||
if (accountId === undefined) {
|
||||
const employeeId = await client.createDoc(contact.class.Employee, contact.space.Contacts, {
|
||||
name: combineName(u.NAME, u.LAST_NAME),
|
||||
avatar: u.PERSONAL_PHOTO,
|
||||
active: u.ACTIVE,
|
||||
city: u.PERSONAL_CITY
|
||||
})
|
||||
accountId = await client.createDoc(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: u.EMAIL,
|
||||
name: combineName(u.NAME, u.LAST_NAME),
|
||||
employee: employeeId,
|
||||
role: AccountRole.User
|
||||
})
|
||||
}
|
||||
userList.set(u.ID, accountId)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (space === undefined || mapping.$lookup?.fields === undefined) {
|
||||
return
|
||||
}
|
||||
let processed = 0
|
||||
const tagElements: Map<Ref<Class<Doc>>, TagElement[]> = new Map()
|
||||
|
||||
let added = 0
|
||||
|
||||
while (added <= limit) {
|
||||
const result = await bitrixClient.call(mapping.type + '.list', {
|
||||
select: ['*', 'UF_*'],
|
||||
order: { ID: direction },
|
||||
start: processed
|
||||
})
|
||||
|
||||
const extraDocs: Doc[] = []
|
||||
|
||||
const convertResults: ConvertResult[] = []
|
||||
for (const r of result.result) {
|
||||
// Convert documents.
|
||||
const res = await convert(
|
||||
client,
|
||||
mapping,
|
||||
space,
|
||||
mapping.$lookup?.fields as BitrixFieldMapping[],
|
||||
r,
|
||||
extraDocs,
|
||||
tagElements,
|
||||
userList
|
||||
)
|
||||
if (mapping.comments) {
|
||||
res.comments = bitrixClient
|
||||
.call(BitrixEntityType.Comment + '.list', {
|
||||
filter: {
|
||||
ENTITY_ID: res.document.bitrixId,
|
||||
ENTITY_TYPE: mapping.type.replace('crm.', '')
|
||||
},
|
||||
select: commentFieldKeys,
|
||||
order: { ID: direction }
|
||||
})
|
||||
.then((comments) => {
|
||||
return comments.result.map(
|
||||
(it: any) =>
|
||||
({
|
||||
_id: generateId(),
|
||||
_class: chunter.class.Comment,
|
||||
message: it.COMMENT,
|
||||
bitrixId: it.ID,
|
||||
type: it.ENTITY_TYPE,
|
||||
attachedTo: res.document._id,
|
||||
attachedToClass: res.document._class,
|
||||
collection: 'comments',
|
||||
space: res.document.space,
|
||||
modifiedBy: userList.get(it.AUTHOR_ID) ?? core.account.System,
|
||||
modifiedOn: new Date(userList.get(it.CREATED) ?? new Date().toString()).getTime()
|
||||
} as Comment)
|
||||
)
|
||||
})
|
||||
}
|
||||
convertResults.push(res)
|
||||
extraDocs.push(...res.extraDocs)
|
||||
added++
|
||||
if (added > limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
total = result.total
|
||||
await syncPlatform(convertResults)
|
||||
|
||||
processed = result.next
|
||||
}
|
||||
} catch (err: any) {
|
||||
state = err.message
|
||||
console.error(err)
|
||||
} finally {
|
||||
loading = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Expandable label={getEmbeddedLabel(mapping.type)}>
|
||||
<svelte:fragment slot="tools">
|
||||
<div class="flex-row-center">
|
||||
<SpaceSelect
|
||||
_class={core.class.Space}
|
||||
label={core.string.Space}
|
||||
bind:value={space}
|
||||
on:change={(evt) => {
|
||||
space = evt.detail
|
||||
}}
|
||||
autoSelect
|
||||
spaceQuery={{ _id: { $in: [contact.space.Contacts] } }}
|
||||
/>
|
||||
<DropdownLabels
|
||||
label={getEmbeddedLabel('Direction')}
|
||||
items={[
|
||||
{ id: 'ASC', label: 'Ascending' },
|
||||
{ id: 'DSC', label: 'Descending' }
|
||||
]}
|
||||
bind:selected={direction}
|
||||
/>
|
||||
<div class="fs-title flex-row-center">
|
||||
<NumberEditor
|
||||
kind={'button'}
|
||||
value={limit}
|
||||
focus={false}
|
||||
placeholder={getEmbeddedLabel('Limit')}
|
||||
onChange={(val) => {
|
||||
if (val) {
|
||||
limit = val
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-2 flex-row-center">
|
||||
<div class="p-1">
|
||||
{state}
|
||||
</div>
|
||||
<Button size={'large'} label={getEmbeddedLabel('Synchronize')} {loading} on:click={doSync} />
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="flex-row flex-grow bottom-divider p-2">
|
||||
{#each Object.entries(fieldsByClass) as field, i}
|
||||
{@const cl = client.getHierarchy().getClass(toClassRef(field[0]))}
|
||||
<div class="fs-title flex-row-center">
|
||||
{#if cl.icon}
|
||||
<div class="mr-1">
|
||||
<Icon icon={cl.icon} size={'large'} />
|
||||
</div>
|
||||
{/if}
|
||||
<Label label={cl.label} />
|
||||
</div>
|
||||
<div class="flex-row">
|
||||
{#each field[1] as cfield, i}
|
||||
<div class="fs-title flex-row-center ml-4">
|
||||
{i + 1}.
|
||||
<FieldMappingPresenter {mapping} value={cfield} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</Expandable>
|
38
plugins/bitrix-resources/src/components/icons/Bitrix.svelte
Normal file
38
plugins/bitrix-resources/src/components/icons/Bitrix.svelte
Normal file
@ -0,0 +1,38 @@
|
||||
<!--
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
// Copyright © 2021 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
export let size: 'small' | 'medium' | 'large'
|
||||
const fill: string = 'currentColor'
|
||||
</script>
|
||||
|
||||
<div class="flex-grow">
|
||||
<svg class="svg-{size}" {fill} viewBox="0 0 174 33" width="174" height="33" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="none">
|
||||
<path
|
||||
d="M106.8 32.4h18.7v-4.1h-12.4c1.7-6.8 12.1-8.3 12.1-15.9 0-4.1-2.8-7.1-8.6-7.1-3.7 0-6.8 1.1-9 2.2l1.3 3.8c2-.9 4.2-1.8 6.9-1.8 2.2 0 4.2.9 4.2 3.4.1 5.6-12.1 6-13.2 19.5zm56.1-5.7c-5.7 0-10.4-4.7-10.4-10.4s4.7-10.4 10.4-10.4 10.4 4.7 10.4 10.4-4.7 10.4-10.4 10.4zm0-18.9c-4.7 0-8.5 3.8-8.5 8.5s3.8 8.5 8.5 8.5 8.5-3.8 8.5-8.5-3.8-8.5-8.5-8.5z"
|
||||
fill="#00629A"
|
||||
/>
|
||||
<path d="M168.8 16.3h-5.2v-5.2h-1.4v6.6h6.6v-1.4z" fill="#00629A" /><path
|
||||
d="M0 1.6h9c6.6 0 9.6 3.8 9.6 7.8 0 2.7-1.3 5.1-3.7 6.4v.1c3.6.9 5.8 3.8 5.8 7.4 0 4.8-3.6 9.1-10.8 9.1H0V1.6zm8.3 12.9c3.1 0 4.8-1.7 4.8-4.1 0-2.3-1.5-4.1-4.8-4.1H5.7v8.2h2.6zm.9 13.3c3.7 0 5.8-1.4 5.8-4.5 0-2.6-2-4.2-5.1-4.2H5.7v8.7h3.5zM24.9 3.4c0-1.9 1.5-3.4 3.4-3.4s3.5 1.4 3.5 3.4c0 1.8-1.5 3.3-3.5 3.3s-3.4-1.4-3.4-3.3zm.6 6.9h5.6v22.1h-5.6V10.3zM38.6 27V14.7h-4v-4.4h4V5.2l5.6-1.6v6.7h6.7l-1.4 4.4h-5.3v10.9c0 2.1.7 2.8 2.2 2.8 1.3 0 2.5-.5 3.4-1.1l1.7 3.8c-1.6 1.1-4.3 1.7-6.5 1.7-4 .1-6.4-2.1-6.4-5.8zm15.9-16.7h4.7l.6 2.5c2-2 3.8-3 6.1-3 1 0 2.2.3 3.1.9l-2 4.7c-1-.6-1.9-.7-2.5-.7-1.5 0-2.7.6-4.5 2.2v15.6h-5.6V10.3h.1zm16.7-6.9c0-1.9 1.5-3.4 3.4-3.4S78 1.5 78 3.4c0 1.8-1.5 3.3-3.5 3.3s-3.3-1.4-3.3-3.3zm.6 6.9h5.6v22.1h-5.6V10.3zm16.9 11-8-11h5.8l5.2 7.2 5.3-7.2h5.8l-8.1 11 8.2 11.1h-5.8L91.8 25l-5.4 7.4h-5.8l8.1-11.1z"
|
||||
fill="#3FC0F0"
|
||||
/>
|
||||
<path
|
||||
d="M142.9 21.9V5.3h-3.5l-13.3 17.3v3.3h12v6.6h4.8v-6.6h4v-4h-4zm-4.8-3.9v3.8h-3.2c-1 0-2.8.1-3.4.1l6.8-9.2c0 .8-.2 3.2-.2 5.3z"
|
||||
fill="#00629A"
|
||||
/></g
|
||||
>
|
||||
</svg>
|
||||
</div>
|
@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
BitrixEntityMapping,
|
||||
BitrixFieldMapping,
|
||||
CopyPattern,
|
||||
CopyValueOperation,
|
||||
Fields,
|
||||
MappingOperation
|
||||
} from '@hcengineering/bitrix'
|
||||
import core, { AnyAttribute } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Button, IconActivity, IconAdd, IconClose, IconDelete } from '@hcengineering/ui'
|
||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||
import EditBox from '@hcengineering/ui/src/components/EditBox.svelte'
|
||||
import bitrix from '../../plugin'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let fields: Fields = {}
|
||||
export let attribute: AnyAttribute
|
||||
export let field: BitrixFieldMapping | undefined
|
||||
|
||||
let patterns: CopyPattern[] = [
|
||||
...((field?.operation as CopyValueOperation)?.patterns ?? [
|
||||
{
|
||||
text: ''
|
||||
}
|
||||
])
|
||||
]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
export async function save (): Promise<void> {
|
||||
if (field) {
|
||||
await client.update(field, {
|
||||
operation: {
|
||||
kind: MappingOperation.CopyValue,
|
||||
patterns
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await client.addCollection(bitrix.class.FieldMapping, mapping.space, mapping._id, mapping._class, 'fields', {
|
||||
ofClass: attribute.attributeOf,
|
||||
attributeName: attribute.name,
|
||||
operation: {
|
||||
kind: MappingOperation.CopyValue,
|
||||
patterns
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: items = Object.entries(fields)
|
||||
.filter((it) => {
|
||||
if (attribute.type._class === core.class.EnumOf) {
|
||||
return it[1].type === 'enumeration' || it[1].type === 'crm_status'
|
||||
}
|
||||
return true
|
||||
})
|
||||
.map((it) => ({
|
||||
id: it[0],
|
||||
label: `${it[1].formLabel ?? it[1].title}${it[0].startsWith('UF_') ? ' *' : ''} - ${it[0]}`
|
||||
}))
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-wrap">
|
||||
{#each patterns as p, i}
|
||||
<div class="pattern flex-row-center">
|
||||
{#if attribute.type._class !== core.class.EnumOf}
|
||||
<EditBox kind={'editbox'} bind:value={p.text} maxWidth={'5rem'} fullSize />
|
||||
{/if}
|
||||
<DropdownLabels minW0={false} label={bitrix.string.FieldMapping} {items} bind:selected={p.field} />
|
||||
{#if p.alternatives}
|
||||
{#each p.alternatives as alt, i}
|
||||
<DropdownLabels minW0={false} label={bitrix.string.FieldMapping} {items} bind:selected={p.alternatives[i]} />
|
||||
<Button
|
||||
icon={IconClose}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
p.alternatives?.splice(i, 1)
|
||||
patterns = patterns
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
<div class="ml-1">
|
||||
<Button
|
||||
icon={IconActivity}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
p.alternatives = [...(p.alternatives ?? []), items[0].id]
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
patterns.splice(i, 1)
|
||||
patterns = patterns
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
{#if attribute.type._class !== core.class.EnumOf || patterns.length === 0}
|
||||
<div class="ml-2">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
patterns = [...patterns, { text: '', field: undefined }]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, CopyValueOperation } from '@hcengineering/bitrix'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let value: BitrixFieldMapping
|
||||
|
||||
$: op = value.operation as CopyValueOperation
|
||||
|
||||
const fieldOf = (field?: string) =>
|
||||
field ? mapping.bitrixFields[field]?.formLabel ?? mapping.bitrixFields[field]?.title : field ?? ''
|
||||
</script>
|
||||
|
||||
<div class="flex-row-center flex-wrap">
|
||||
{#each op.patterns as p, i}
|
||||
<div class="pattern flex-row-center">
|
||||
<span>
|
||||
{p.text}
|
||||
</span>
|
||||
{#if mapping.bitrixFields}
|
||||
=> {fieldOf(p.field)}
|
||||
{/if}
|
||||
{#if p.alternatives}
|
||||
{#each p.alternatives as alt, i}
|
||||
|{fieldOf(alt)}
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.1rem;
|
||||
padding: 0.3rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,116 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
BitrixEntityMapping,
|
||||
BitrixFieldMapping,
|
||||
ChannelFieldMapping,
|
||||
Fields,
|
||||
MappingOperation,
|
||||
CreateChannelOperation
|
||||
} from '@hcengineering/bitrix'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
import contact, { ChannelProvider } from '@hcengineering/contact'
|
||||
import { Button, DropdownLabelsIntl, IconAdd, IconDelete } from '@hcengineering/ui'
|
||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||
import bitrix from '../../plugin'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let fields: Fields = {}
|
||||
export let attribute: AnyAttribute
|
||||
export let field: BitrixFieldMapping | undefined
|
||||
|
||||
let channelFields: ChannelFieldMapping[] = [...((field?.operation as CreateChannelOperation)?.fields ?? [])]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
export async function save (): Promise<void> {
|
||||
if (field !== undefined) {
|
||||
await client.update(field, {
|
||||
operation: {
|
||||
kind: MappingOperation.CreateChannel,
|
||||
fields: channelFields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await client.addCollection(bitrix.class.FieldMapping, mapping.space, mapping._id, mapping._class, 'fields', {
|
||||
ofClass: attribute.attributeOf,
|
||||
attributeName: attribute.name,
|
||||
operation: {
|
||||
kind: MappingOperation.CreateChannel,
|
||||
fields: channelFields
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: items = Object.entries(fields)
|
||||
.filter((it) => {
|
||||
return it[1].type !== 'enumeration'
|
||||
})
|
||||
.map((it) => ({
|
||||
id: it[0],
|
||||
label: `${it[1].formLabel ?? it[1].title}${it[0].startsWith('UF_') ? ' *' : ''} - ${it[0]}`
|
||||
}))
|
||||
|
||||
let providers: ChannelProvider[] = []
|
||||
client.findAll(contact.class.ChannelProvider, {}).then((res) => {
|
||||
providers = res
|
||||
})
|
||||
|
||||
$: providerItems = providers.map((it) => ({ id: it._id, label: it.label }))
|
||||
</script>
|
||||
|
||||
<div class="flex-col flex-wrap">
|
||||
{#each channelFields as p, i}
|
||||
<div class="pattern flex-row-center gap-2">
|
||||
<DropdownLabelsIntl
|
||||
minW0={false}
|
||||
label={getEmbeddedLabel('Channel')}
|
||||
items={providerItems}
|
||||
bind:selected={p.provider}
|
||||
/>
|
||||
<DropdownLabels minW0={false} label={bitrix.string.FieldMapping} {items} bind:selected={p.field} />
|
||||
|
||||
<div class="ml-1">
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
channelFields.splice(i, 1)
|
||||
channelFields = channelFields
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="ml-2">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
channelFields = [...channelFields, { field: items[0].id, provider: providers[0]._id }]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,46 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, CreateChannelOperation } from '@hcengineering/bitrix'
|
||||
|
||||
import contact from '@hcengineering/contact'
|
||||
import { Component } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let value: BitrixFieldMapping
|
||||
|
||||
$: op = value.operation as CreateChannelOperation
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
{#each op.fields as p, i}
|
||||
<div class="pattern flex-row-center gap-2">
|
||||
<Component
|
||||
is={view.component.ObjectPresenter}
|
||||
props={{ _class: contact.class.ChannelProvider, objectId: p.provider }}
|
||||
/>
|
||||
->
|
||||
{#if mapping.bitrixFields}
|
||||
{p.field ? mapping.bitrixFields[p.field]?.formLabel ?? mapping.bitrixFields[p.field]?.title : p.field ?? ''}
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.1rem;
|
||||
padding: 0.3rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,137 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
BitrixEntityMapping,
|
||||
BitrixFieldMapping,
|
||||
CreateTagOperation,
|
||||
Fields,
|
||||
MappingOperation,
|
||||
TagField
|
||||
} from '@hcengineering/bitrix'
|
||||
import { AnyAttribute } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { WeightPopup } from '@hcengineering/tags-resources'
|
||||
import {
|
||||
Button,
|
||||
DropdownTextItem,
|
||||
getEventPopupPositionElement,
|
||||
IconAdd,
|
||||
IconDelete,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import DropdownLabels from '@hcengineering/ui/src/components/DropdownLabels.svelte'
|
||||
import EditBox from '@hcengineering/ui/src/components/EditBox.svelte'
|
||||
import bitrix from '../../plugin'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let fields: Fields = {}
|
||||
export let attribute: AnyAttribute
|
||||
export let field: BitrixFieldMapping | undefined
|
||||
|
||||
let tagFields: TagField[] = [...((field?.operation as CreateTagOperation)?.fields ?? [])]
|
||||
|
||||
const client = getClient()
|
||||
|
||||
export async function save (): Promise<void> {
|
||||
if (field !== undefined) {
|
||||
await client.update(field, {
|
||||
operation: {
|
||||
kind: MappingOperation.CreateTag,
|
||||
fields: tagFields
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await client.addCollection(bitrix.class.FieldMapping, mapping.space, mapping._id, mapping._class, 'fields', {
|
||||
ofClass: attribute.attributeOf,
|
||||
attributeName: attribute.name,
|
||||
operation: {
|
||||
kind: MappingOperation.CreateTag,
|
||||
fields: tagFields
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getItems (fields: Fields): DropdownTextItem[] {
|
||||
return Object.entries(fields).map((it) => ({
|
||||
id: it[0],
|
||||
label: `${it[1].formLabel ?? it[1].title}${it[0].startsWith('UF_') ? ' *' : ''} - ${it[0]}`
|
||||
}))
|
||||
}
|
||||
$: items = getItems(fields)
|
||||
|
||||
const tagLevel = [tags.icon.Level1, tags.icon.Level2, tags.icon.Level3]
|
||||
const labels = [getEmbeddedLabel('Initial'), getEmbeddedLabel('Meaningfull'), getEmbeddedLabel('Expert')]
|
||||
</script>
|
||||
|
||||
<div class="flex-col flex-wrap">
|
||||
{#each tagFields as p, i}
|
||||
{@const tagIcon = tagLevel[p.weight % 3]}
|
||||
{@const tagLabel = labels[Math.floor(p.weight / 3)]}
|
||||
<div class="pattern flex-row-center gap-2">
|
||||
<DropdownLabels minW0={false} label={bitrix.string.FieldMapping} {items} bind:selected={p.field} />
|
||||
|
||||
<Button
|
||||
label={tagLabel}
|
||||
icon={tagIcon}
|
||||
on:click={(evt) => {
|
||||
showPopup(WeightPopup, { value: p.weight }, getEventPopupPositionElement(evt), (res) => {
|
||||
if (Number.isFinite(res) && res >= 0 && res <= 8) {
|
||||
if (res != null) {
|
||||
p.weight = res
|
||||
}
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<EditBox
|
||||
kind={'editbox'}
|
||||
bind:value={p.split}
|
||||
maxWidth={'5rem'}
|
||||
placeholder={getEmbeddedLabel('Separator...')}
|
||||
fullSize
|
||||
/>
|
||||
|
||||
<div class="ml-1">
|
||||
<Button
|
||||
icon={IconDelete}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
tagFields.splice(i, 1)
|
||||
tagFields = tagFields
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="ml-2">
|
||||
<Button
|
||||
icon={IconAdd}
|
||||
size={'small'}
|
||||
on:click={() => {
|
||||
tagFields = [...tagFields, { weight: 0, field: items[0].id, split: '' }]
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,49 @@
|
||||
<script lang="ts">
|
||||
import { BitrixEntityMapping, BitrixFieldMapping, CreateTagOperation } from '@hcengineering/bitrix'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import tags from '@hcengineering/tags'
|
||||
import { Button } from '@hcengineering/ui'
|
||||
|
||||
export let mapping: BitrixEntityMapping
|
||||
export let value: BitrixFieldMapping
|
||||
|
||||
$: op = value.operation as CreateTagOperation
|
||||
|
||||
const tagLevel = [tags.icon.Level1, tags.icon.Level2, tags.icon.Level3]
|
||||
const labels = [getEmbeddedLabel('Initial'), getEmbeddedLabel('Meaningfull'), getEmbeddedLabel('Expert')]
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap">
|
||||
{#each op.fields as p, i}
|
||||
{@const tagIcon = tagLevel[p.weight % 3]}
|
||||
{@const tagLabel = labels[Math.floor(p.weight / 3)]}
|
||||
|
||||
<div class="pattern flex-row-center gap-2">
|
||||
{#if mapping.bitrixFields}
|
||||
{p.field ? mapping.bitrixFields[p.field]?.formLabel ?? mapping.bitrixFields[p.field]?.title : p.field ?? ''}
|
||||
{/if}
|
||||
|
||||
<Button label={tagLabel} icon={tagIcon} size={'small'} disabled={true} />
|
||||
{p.split}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.pattern {
|
||||
margin: 0.1rem;
|
||||
padding: 0.3rem;
|
||||
flex-shrink: 0;
|
||||
border: 1px dashed var(--accent-color);
|
||||
border-radius: 0.25rem;
|
||||
|
||||
font-weight: 500;
|
||||
font-size: 0.75rem;
|
||||
|
||||
// text-transform: uppercase;
|
||||
color: var(--accent-color);
|
||||
&:hover {
|
||||
color: var(--caption-color);
|
||||
}
|
||||
}
|
||||
</style>
|
33
plugins/bitrix-resources/src/index.ts
Normal file
33
plugins/bitrix-resources/src/index.ts
Normal file
@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Resources } from '@hcengineering/platform'
|
||||
|
||||
import BitrixConnect from './components/BitrixConnect.svelte'
|
||||
import BitrixConfigure from './components/BitrixConfigure.svelte'
|
||||
import BitrixIcon from './components/icons/Bitrix.svelte'
|
||||
import BitrixImport from './components/BitrixImport.svelte'
|
||||
|
||||
export default async (): Promise<Resources> => ({
|
||||
component: {
|
||||
BitrixIcon,
|
||||
BitrixConnect,
|
||||
BitrixConfigure,
|
||||
BitrixImport
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: async () => {}
|
||||
}
|
||||
})
|
47
plugins/bitrix-resources/src/plugin.ts
Normal file
47
plugins/bitrix-resources/src/plugin.ts
Normal file
@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { IntlString, mergeIds } from '@hcengineering/platform'
|
||||
|
||||
import bitrix, { bitrixId } from '@hcengineering/bitrix'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { Handler, IntegrationType } from '@hcengineering/setting'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
export default mergeIds(bitrixId, bitrix, {
|
||||
string: {
|
||||
BitrixTokenUrl: '' as IntlString,
|
||||
Bitrix: '' as IntlString,
|
||||
BitrixDesc: '' as IntlString,
|
||||
Settings: '' as IntlString,
|
||||
EntityMapping: '' as IntlString,
|
||||
NotAllowed: '' as IntlString,
|
||||
AddMapping: '' as IntlString,
|
||||
BitrixEntityType: '' as IntlString,
|
||||
FieldMapping: '' as IntlString,
|
||||
AddField: '' as IntlString,
|
||||
Attribute: '' as IntlString,
|
||||
MapField: '' as IntlString
|
||||
},
|
||||
component: {
|
||||
BitrixIcon: '' as AnyComponent
|
||||
},
|
||||
handler: {
|
||||
DisconnectHandler: '' as Handler
|
||||
},
|
||||
integrationType: {
|
||||
Bitrix: '' as Ref<IntegrationType>
|
||||
}
|
||||
})
|
45
plugins/bitrix-resources/src/types.ts
Normal file
45
plugins/bitrix-resources/src/types.ts
Normal file
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface BitrixProfile {
|
||||
ID: string
|
||||
ADMIN: boolean
|
||||
NAME: string
|
||||
LAST_NAME: string
|
||||
PERSONAL_GENDER: string
|
||||
PERSONAL_PHOTO: string
|
||||
TIME_ZONE: string
|
||||
TIME_ZONE_OFFSET: number
|
||||
}
|
||||
|
||||
export type NumberString = string
|
||||
export type ISODate = string
|
||||
export type BoolString = 'Y' | 'N'
|
||||
export type GenderString = 'M' | 'F' | ''
|
||||
|
||||
export interface MultiField {
|
||||
readonly ID: NumberString
|
||||
readonly VALUE_TYPE: string
|
||||
readonly VALUE: string
|
||||
readonly TYPE_ID: string
|
||||
}
|
||||
export type MultiFieldArray = ReadonlyArray<Pick<MultiField, 'VALUE' | 'VALUE_TYPE'>>
|
||||
|
||||
export interface StatusValue {
|
||||
CATEGORY_ID: string | null
|
||||
COLOR: string | null
|
||||
ENTITY_ID: string | null
|
||||
ID: number
|
||||
NAME: string
|
||||
NAME_INIT: string | null
|
||||
SEMANTICS: string | null
|
||||
SORT: string | null
|
||||
STATUS_ID: string | null
|
||||
SYSTEM: 'Y' | 'N'
|
||||
}
|
||||
|
||||
export interface BitrixResult {
|
||||
result: any
|
||||
next: number
|
||||
total: number
|
||||
}
|
277
plugins/bitrix-resources/src/utils.ts
Normal file
277
plugins/bitrix-resources/src/utils.ts
Normal file
@ -0,0 +1,277 @@
|
||||
import {
|
||||
BitrixEntityMapping,
|
||||
BitrixFieldMapping,
|
||||
BitrixSyncDoc,
|
||||
CopyValueOperation,
|
||||
CreateChannelOperation,
|
||||
CreateTagOperation,
|
||||
MappingOperation
|
||||
} from '@hcengineering/bitrix'
|
||||
import { Comment } from '@hcengineering/chunter'
|
||||
import contact, { Channel, EmployeeAccount } from '@hcengineering/contact'
|
||||
import core, { AnyAttribute, Class, Client, Data, Doc, generateId, Mixin, Ref, Space } from '@hcengineering/core'
|
||||
import tags, { TagElement, TagReference } from '@hcengineering/tags'
|
||||
import { getColorNumberByText } from '@hcengineering/ui'
|
||||
|
||||
export function collectFields (fieldMapping: BitrixFieldMapping[]): string[] {
|
||||
const fields: string[] = ['ID']
|
||||
for (const f of fieldMapping) {
|
||||
switch (f.operation.kind) {
|
||||
case MappingOperation.CopyValue:
|
||||
fields.push(
|
||||
...Array.from(f.operation.patterns.map((it) => it.field).filter((it) => it !== undefined) as string[])
|
||||
)
|
||||
break
|
||||
case MappingOperation.CreateChannel:
|
||||
fields.push(...Array.from(f.operation.fields.map((it) => it.field).filter((it) => it !== undefined)))
|
||||
break
|
||||
case MappingOperation.CreateTag:
|
||||
fields.push(...Array.from(f.operation.fields.map((it) => it.field).filter((it) => it !== undefined)))
|
||||
break
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
export interface ConvertResult {
|
||||
document: BitrixSyncDoc // Document we should achive
|
||||
mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> // Mixins of document we will achive
|
||||
extraDocs: Doc[] // Extra documents we will achive, comments etc.
|
||||
blobs: File[] //
|
||||
comments?: Promise<Array<BitrixSyncDoc & Comment>>
|
||||
}
|
||||
|
||||
export async function convert (
|
||||
client: Client,
|
||||
entity: BitrixEntityMapping,
|
||||
space: Ref<Space>,
|
||||
fields: BitrixFieldMapping[],
|
||||
rawDocument: any,
|
||||
prevExtra: Doc[], // <<-- a list of previous extra documents, so for example TagElement will be reused, if present for more what one item and required to be created
|
||||
tagElements: Map<Ref<Class<Doc>>, TagElement[]>, // TagElement cache.
|
||||
userList: Map<string, Ref<EmployeeAccount>>
|
||||
): Promise<ConvertResult> {
|
||||
const hierarchy = client.getHierarchy()
|
||||
const document: BitrixSyncDoc = {
|
||||
_id: generateId(),
|
||||
type: entity.type,
|
||||
bitrixId: `${rawDocument.ID as string}`,
|
||||
_class: entity.ofClass,
|
||||
space,
|
||||
modifiedOn: new Date(rawDocument.DATE_CREATE).getTime(),
|
||||
modifiedBy: userList.get(rawDocument.CREATED_BY_ID) ?? core.account.System
|
||||
}
|
||||
|
||||
// Obtain a proper modified by for document
|
||||
|
||||
const newExtraDocs: Doc[] = []
|
||||
const blobs: File[] = []
|
||||
const mixins: Record<Ref<Mixin<Doc>>, Data<Doc>> = {}
|
||||
|
||||
const extractValue = (field?: string, alternatives?: string[]): any | undefined => {
|
||||
if (field !== undefined) {
|
||||
let lval = rawDocument[field]
|
||||
if ((lval == null || lval === '') && alternatives !== undefined) {
|
||||
for (const alt of alternatives) {
|
||||
lval = rawDocument[alt]
|
||||
if (lval != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
const bfield = entity.bitrixFields[field]
|
||||
if (bfield === undefined) {
|
||||
console.trace('Bitrix field not found', field)
|
||||
} else if (bfield.type === 'integer' || bfield.type === 'double') {
|
||||
if (bfield.isMultiple && Array.isArray(lval)) {
|
||||
return lval[0] ?? 0
|
||||
}
|
||||
return lval
|
||||
} else if (bfield.type === 'crm_multifield') {
|
||||
if (Array.isArray(lval)) {
|
||||
return lval.map((it) => it.VALUE)
|
||||
}
|
||||
} else if (bfield.type === 'string' || bfield.type === 'url') {
|
||||
if (bfield.isMultiple && Array.isArray(lval)) {
|
||||
return lval.join(', ')
|
||||
}
|
||||
return lval
|
||||
} else if (bfield.type === 'date') {
|
||||
if (lval !== '' && lval != null) {
|
||||
return new Date(lval)
|
||||
}
|
||||
} else if (bfield.type === 'char') {
|
||||
return lval === 'Y'
|
||||
} else if (bfield.type === 'enumeration' || bfield.type === 'crm_status') {
|
||||
if (lval != null && lval !== '') {
|
||||
if (bfield.isMultiple && Array.isArray(lval)) {
|
||||
lval = lval[0] ?? ''
|
||||
}
|
||||
const eValue = bfield.items?.find((it) => it.ID === lval)?.VALUE
|
||||
if (eValue !== undefined) {
|
||||
return eValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getCopyValue = async (attr: AnyAttribute, operation: CopyValueOperation): Promise<any> => {
|
||||
const r: Array<string | number | boolean | Date> = []
|
||||
for (const o of operation.patterns) {
|
||||
if (o.text.length > 0) {
|
||||
r.push(o.text)
|
||||
}
|
||||
const lval = extractValue(o.field, o.alternatives)
|
||||
if (lval != null) {
|
||||
r.push(lval)
|
||||
}
|
||||
}
|
||||
if (r.length === 1) {
|
||||
return r[0]
|
||||
}
|
||||
if (r.length === 0) {
|
||||
return
|
||||
}
|
||||
return r.join('').trim()
|
||||
}
|
||||
|
||||
const getChannelValue = async (attr: AnyAttribute, operation: CreateChannelOperation): Promise<any> => {
|
||||
for (const f of operation.fields) {
|
||||
const lval = extractValue(f.field)
|
||||
if (lval != null && lval !== '') {
|
||||
const vals = Array.isArray(lval) ? lval : [lval]
|
||||
for (const llVal of vals) {
|
||||
const c: Channel = {
|
||||
_id: generateId(),
|
||||
_class: contact.class.Channel,
|
||||
attachedTo: document._id,
|
||||
attachedToClass: attr.attributeOf,
|
||||
collection: attr.name,
|
||||
modifiedBy: document.modifiedBy,
|
||||
value: llVal,
|
||||
provider: f.provider,
|
||||
space: document.space,
|
||||
modifiedOn: document.modifiedOn
|
||||
}
|
||||
newExtraDocs.push(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
const getTagValue = async (attr: AnyAttribute, operation: CreateTagOperation): Promise<any> => {
|
||||
const elements =
|
||||
tagElements.get(attr.attributeOf) ??
|
||||
(await client.findAll(tags.class.TagElement, {
|
||||
targetClass: attr.attributeOf
|
||||
}))
|
||||
|
||||
const references = await client.findAll(tags.class.TagReference, {
|
||||
attachedTo: document._id
|
||||
})
|
||||
// Add tags creation requests from previous conversions.
|
||||
elements.push(...prevExtra.filter((it) => it._class === tags.class.TagElement).map((it) => it as TagElement))
|
||||
|
||||
tagElements.set(attr.attributeOf, elements)
|
||||
const defaultCategory = await client.findOne(tags.class.TagCategory, {
|
||||
targetClass: attr.attributeOf,
|
||||
default: true
|
||||
})
|
||||
if (defaultCategory === undefined) {
|
||||
console.error('could not proceed tags without default category')
|
||||
return
|
||||
}
|
||||
for (const o of operation.fields) {
|
||||
const lval = extractValue(o.field)
|
||||
let vals: string[] = []
|
||||
if (lval == null) {
|
||||
continue
|
||||
}
|
||||
if (o.split !== '' && o.split != null) {
|
||||
vals = `${lval as string}`.split(o.split)
|
||||
} else {
|
||||
vals = [lval as string]
|
||||
}
|
||||
for (let vv of vals) {
|
||||
vv = vv.trim()
|
||||
if (vv === '') {
|
||||
continue
|
||||
}
|
||||
// Find existing element and create reference based on it.
|
||||
let tag: TagElement | undefined = elements.find((it) => it.title === vv)
|
||||
if (tag === undefined) {
|
||||
tag = {
|
||||
_id: generateId(),
|
||||
_class: tags.class.TagElement,
|
||||
category: defaultCategory._id,
|
||||
color: getColorNumberByText(vv),
|
||||
description: '',
|
||||
title: vv,
|
||||
targetClass: attr.attributeOf,
|
||||
space: tags.space.Tags,
|
||||
modifiedBy: document.modifiedBy,
|
||||
modifiedOn: document.modifiedOn
|
||||
}
|
||||
newExtraDocs.push(tag)
|
||||
}
|
||||
const ref: TagReference = {
|
||||
_id: generateId(),
|
||||
attachedTo: document._id,
|
||||
attachedToClass: attr.attributeOf,
|
||||
collection: attr.name,
|
||||
_class: tags.class.TagReference,
|
||||
tag: tag._id,
|
||||
color: getColorNumberByText(vv),
|
||||
title: vv,
|
||||
weight: o.weight,
|
||||
modifiedBy: document.modifiedBy,
|
||||
modifiedOn: document.modifiedOn,
|
||||
space: tags.space.Tags
|
||||
}
|
||||
if (references.find((it) => it.title === vv) === undefined) {
|
||||
// Add only if not already added
|
||||
newExtraDocs.push(ref)
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const f of fields) {
|
||||
const attr = hierarchy.getAttribute(f.ofClass, f.attributeName)
|
||||
if (attr === undefined) {
|
||||
console.trace('Attribue not found', f)
|
||||
continue
|
||||
}
|
||||
let value: any
|
||||
switch (f.operation.kind) {
|
||||
case MappingOperation.CopyValue:
|
||||
value = await getCopyValue(attr, f.operation)
|
||||
break
|
||||
case MappingOperation.CreateChannel:
|
||||
value = await getChannelValue(attr, f.operation)
|
||||
break
|
||||
case MappingOperation.CreateTag:
|
||||
value = await getTagValue(attr, f.operation)
|
||||
break
|
||||
}
|
||||
if (value !== undefined) {
|
||||
if (hierarchy.isMixin(attr.attributeOf)) {
|
||||
mixins[attr.attributeOf] = { ...mixins[attr.attributeOf], [attr.name]: value }
|
||||
} else {
|
||||
;(document as any)[attr.name] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { document, mixins, extraDocs: newExtraDocs, blobs }
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export function toClassRef (val: any): Ref<Class<Doc>> {
|
||||
return val as Ref<Class<Doc>>
|
||||
}
|
5
plugins/bitrix-resources/svelte.config.js
Normal file
5
plugins/bitrix-resources/svelte.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
const sveltePreprocess = require('svelte-preprocess')
|
||||
|
||||
module.exports = {
|
||||
preprocess: sveltePreprocess()
|
||||
};
|
16
plugins/bitrix-resources/tsconfig.json
Normal file
16
plugins/bitrix-resources/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"target": "esnext",
|
||||
"module": "esnext",
|
||||
"declaration": true,
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
]
|
||||
}
|
||||
}
|
7
plugins/bitrix/.eslintrc.js
Normal file
7
plugins/bitrix/.eslintrc.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/config/eslint.config.json'],
|
||||
parserOptions: {
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.json'
|
||||
}
|
||||
}
|
4
plugins/bitrix/.npmignore
Normal file
4
plugins/bitrix/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
*
|
||||
!/lib/**
|
||||
!CHANGELOG.md
|
||||
/lib/**/__tests__/
|
18
plugins/bitrix/config/rig.json
Normal file
18
plugins/bitrix/config/rig.json
Normal file
@ -0,0 +1,18 @@
|
||||
// The "rig.json" file directs tools to look for their config files in an external package.
|
||||
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
|
||||
{
|
||||
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
|
||||
|
||||
/**
|
||||
* (Required) The name of the rig package to inherit from.
|
||||
* It should be an NPM package name with the "-rig" suffix.
|
||||
*/
|
||||
"rigPackageName": "@hcengineering/platform-rig"
|
||||
|
||||
/**
|
||||
* (Optional) Selects a config profile from the rig package. The name must consist of
|
||||
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
|
||||
* If omitted, then the "default" profile will be used."
|
||||
*/
|
||||
// "rigProfile": "your-profile-name"
|
||||
}
|
36
plugins/bitrix/package.json
Normal file
36
plugins/bitrix/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@hcengineering/bitrix",
|
||||
"version": "0.6.0",
|
||||
"main": "lib/index.js",
|
||||
"author": "Anticrm Platform Contributors",
|
||||
"license": "EPL-2.0",
|
||||
"scripts": {
|
||||
"build": "heft build",
|
||||
"build:watch": "tsc",
|
||||
"lint:fix": "eslint --fix src",
|
||||
"lint": "eslint src",
|
||||
"format": "prettier --write src && eslint --fix src"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hcengineering/platform-rig": "~0.6.0",
|
||||
"@types/heft-jest": "^1.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.41.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-n": "^15.4.0",
|
||||
"eslint": "^8.26.0",
|
||||
"@typescript-eslint/parser": "^5.41.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@rushstack/heft": "^0.47.9",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hcengineering/platform": "^0.6.8",
|
||||
"@hcengineering/core": "^0.6.19",
|
||||
"@hcengineering/ui": "^0.6.2",
|
||||
"@hcengineering/preference": "^0.6.1",
|
||||
"@hcengineering/tags": "~0.6.2",
|
||||
"@hcengineering/contact": "~0.6.8"
|
||||
}
|
||||
}
|
184
plugins/bitrix/src/index.ts
Normal file
184
plugins/bitrix/src/index.ts
Normal file
@ -0,0 +1,184 @@
|
||||
//
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { ChannelProvider } from '@hcengineering/contact'
|
||||
import type { AttachedDoc, Class, Doc, Mixin, Ref, Space } from '@hcengineering/core'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import { Asset, plugin } from '@hcengineering/platform'
|
||||
import { ExpertKnowledge, InitialKnowledge, MeaningfullKnowledge } from '@hcengineering/tags'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface BitrixSyncDoc extends Doc {
|
||||
type: string
|
||||
bitrixId: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum BitrixEntityType {
|
||||
Comment = 'crm.timeline.comment',
|
||||
Binding = 'crm.timeline.bindings',
|
||||
Lead = 'crm.lead',
|
||||
Activity = 'crm.activity',
|
||||
Company = 'crm.company'
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const mappingTypes = [
|
||||
{ label: 'Leads', id: BitrixEntityType.Lead },
|
||||
// { label: 'Comments', id: BitrixEntityType.Comment },
|
||||
{ label: 'Company', id: BitrixEntityType.Company }
|
||||
// { label: 'Activity', id: BitrixEntityType.Activity }
|
||||
]
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface FieldValue {
|
||||
type: string
|
||||
statusType?: string
|
||||
isRequired: boolean
|
||||
isReadOnly: boolean
|
||||
isImmutable: boolean
|
||||
isMultiple: boolean
|
||||
isDynamic: boolean
|
||||
title: string
|
||||
|
||||
formLabel?: string
|
||||
filterLabel?: string
|
||||
items?: Array<{
|
||||
ID: string
|
||||
VALUE: string
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface Fields {
|
||||
[key: string]: FieldValue
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface BitrixEntityMapping extends Doc {
|
||||
ofClass: Ref<Class<Doc>>
|
||||
type: string
|
||||
bitrixFields: Fields
|
||||
|
||||
fields: number
|
||||
|
||||
comments: boolean
|
||||
activity: boolean
|
||||
attachments: boolean
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export enum MappingOperation {
|
||||
CopyValue,
|
||||
CreateTag, // Create tag
|
||||
CreateChannel // Create channel
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CopyPattern {
|
||||
text: string
|
||||
field?: string
|
||||
alternatives?: string[]
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CopyValueOperation {
|
||||
kind: MappingOperation.CopyValue
|
||||
patterns: CopyPattern[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TagField {
|
||||
weight: InitialKnowledge | MeaningfullKnowledge | ExpertKnowledge
|
||||
|
||||
field: string
|
||||
split: string // If defined values from field will be split to check for multiple values.
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CreateTagOperation {
|
||||
kind: MappingOperation.CreateTag
|
||||
|
||||
fields: TagField[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface ChannelFieldMapping {
|
||||
provider: Ref<ChannelProvider>
|
||||
field: string
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface CreateChannelOperation {
|
||||
kind: MappingOperation.CreateChannel
|
||||
fields: ChannelFieldMapping[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface BitrixFieldMapping extends AttachedDoc {
|
||||
ofClass: Ref<Class<Doc>> // Specify mixin if applicable
|
||||
attributeName: string
|
||||
|
||||
operation: CopyValueOperation | CreateTagOperation | CreateChannelOperation
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const bitrixId = 'bitrix' as Plugin
|
||||
|
||||
export default plugin(bitrixId, {
|
||||
mixin: {
|
||||
BitrixSyncDoc: '' as Ref<Mixin<BitrixSyncDoc>>
|
||||
},
|
||||
class: {
|
||||
EntityMapping: '' as Ref<Class<BitrixEntityMapping>>,
|
||||
FieldMapping: '' as Ref<Class<BitrixFieldMapping>>
|
||||
},
|
||||
component: {
|
||||
BitrixIntegration: '' as AnyComponent
|
||||
},
|
||||
icon: {
|
||||
Bitrix: '' as Asset
|
||||
},
|
||||
space: {
|
||||
Mappings: '' as Ref<Space>
|
||||
}
|
||||
})
|
9
plugins/bitrix/tsconfig.json
Normal file
9
plugins/bitrix/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
|
||||
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./lib",
|
||||
"lib": ["esnext", "dom"]
|
||||
}
|
||||
}
|
@ -66,6 +66,7 @@
|
||||
"ShowAttribute": "Show attribute",
|
||||
"HideAttribute": "Hide attribute",
|
||||
"Visibility": "Visibility",
|
||||
"Hidden": "Hidden"
|
||||
"Hidden": "Hidden",
|
||||
"Configure": "Configure"
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@
|
||||
"ShowAttribute": "Hide",
|
||||
"HideAttribute": "Show",
|
||||
"Visibility": "Видимость",
|
||||
"Hidden": "Спрятанный"
|
||||
"Hidden": "Спрятанный",
|
||||
"Configure": "Настроить..."
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@
|
||||
import {
|
||||
Action,
|
||||
ActionIcon,
|
||||
AnySvelteComponent,
|
||||
CircleButton,
|
||||
Component,
|
||||
getEventPositionElement,
|
||||
@ -51,6 +52,15 @@
|
||||
import EditClassLabel from './EditClassLabel.svelte'
|
||||
|
||||
export let _class: Ref<Class<Doc>>
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
|
||||
export let attributeMapper:
|
||||
| {
|
||||
component: AnySvelteComponent
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -66,7 +76,9 @@
|
||||
|
||||
function getCustomAttributes (_class: Ref<Class<Doc>>): AnyAttribute[] {
|
||||
const cl = hierarchy.getClass(_class)
|
||||
const attributes = Array.from(hierarchy.getAllAttributes(_class, cl.extends).values())
|
||||
const attributes = Array.from(
|
||||
hierarchy.getAllAttributes(_class, _class === ofClass ? core.class.Doc : cl.extends).values()
|
||||
)
|
||||
// const filtred = attributes.filter((p) => !p.hidden)
|
||||
return attributes
|
||||
}
|
||||
@ -210,6 +222,11 @@
|
||||
<Label label={settings.string.Custom} />
|
||||
</div>
|
||||
</th>
|
||||
{#if attributeMapper}
|
||||
<th>
|
||||
<Label label={attributeMapper.label} />
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -223,7 +240,7 @@
|
||||
}}
|
||||
>
|
||||
<td>
|
||||
<div class="antiTable-cells__firstCell">
|
||||
<div class="antiTable-cells__firstCell whitespace-nowrap">
|
||||
<Label label={attr.label} />
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div id="context-menu" class="antiTable-cells__firstCell-menuRow" on:click={(ev) => showMenu(ev, attr)}>
|
||||
@ -231,7 +248,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="select-text">
|
||||
<td class="select-text whitespace-nowrap">
|
||||
<Label label={attr.type.label} />
|
||||
{#if attrType !== undefined}
|
||||
: <Label label={attrType} />
|
||||
@ -252,6 +269,11 @@
|
||||
<td>
|
||||
<Component is={view.component.BooleanTruePresenter} props={{ value: attr.isCustom ?? false }} />
|
||||
</td>
|
||||
{#if attributeMapper}
|
||||
<td>
|
||||
<svelte:component this={attributeMapper.component} {...attributeMapper.props} attribute={attr} />
|
||||
</td>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
export let classes: Ref<Class<Doc>>[] = ['contact:class:Contact' as Ref<Class<Doc>>]
|
||||
export let _class: Ref<Class<Doc>> | undefined
|
||||
export let ofClass: Ref<Class<Doc>> | undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@ -30,12 +31,16 @@
|
||||
function getDescendants (_class: Ref<Class<Doc>>): Ref<Class<Doc>>[] {
|
||||
const result: Ref<Class<Doc>>[] = []
|
||||
const desc = hierarchy.getDescendants(_class)
|
||||
const vars = [ClassifierKind.MIXIN]
|
||||
if (ofClass === undefined) {
|
||||
vars.push(ClassifierKind.CLASS)
|
||||
}
|
||||
for (const clazz of desc) {
|
||||
const cls = hierarchy.getClass(clazz)
|
||||
if (
|
||||
cls.extends === _class &&
|
||||
!cls.hidden &&
|
||||
[ClassifierKind.CLASS, ClassifierKind.MIXIN].includes(cls.kind) &&
|
||||
vars.includes(cls.kind) &&
|
||||
cls.label !== undefined &&
|
||||
(!hierarchy.hasMixin(cls, settings.mixin.Editable) || hierarchy.as(cls, settings.mixin.Editable).value)
|
||||
) {
|
||||
|
@ -13,20 +13,32 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { Class, Doc, Ref } from '@hcengineering/core'
|
||||
import core, { Class, Doc, Obj, Ref } from '@hcengineering/core'
|
||||
import { IntlString } from '@hcengineering/platform'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentLocation, Icon, Label, navigate } from '@hcengineering/ui'
|
||||
import { AnySvelteComponent, getCurrentLocation, Icon, Label, navigate } from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
import { filterDescendants } from '../utils'
|
||||
import ClassAttributes from './ClassAttributes.svelte'
|
||||
import ClassHierarchy from './ClassHierarchy.svelte'
|
||||
|
||||
export let ofClass: Ref<Class<Obj>> | undefined
|
||||
export let attributeMapper:
|
||||
| {
|
||||
component: AnySvelteComponent
|
||||
label: IntlString
|
||||
props: Record<string, any>
|
||||
}
|
||||
| undefined
|
||||
export let withoutHeader = false
|
||||
|
||||
const loc = getCurrentLocation()
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let _class: Ref<Class<Doc>> | undefined = loc.query?._class as Ref<Class<Doc>> | undefined
|
||||
let _class: Ref<Class<Doc>> | undefined = ofClass ?? (loc.query?._class as Ref<Class<Doc>> | undefined)
|
||||
|
||||
$: if (_class !== undefined) {
|
||||
$: if (_class !== undefined && ofClass === undefined) {
|
||||
const loc = getCurrentLocation()
|
||||
loc.query = undefined
|
||||
navigate(loc)
|
||||
@ -36,28 +48,24 @@
|
||||
|
||||
let classes: Ref<Class<Doc>>[] = []
|
||||
clQuery.query(core.class.Class, {}, (res) => {
|
||||
classes = res
|
||||
.filter(
|
||||
(p) =>
|
||||
hierarchy.hasMixin(p, setting.mixin.Editable) &&
|
||||
hierarchy.as(p, setting.mixin.Editable).value &&
|
||||
!hierarchy.hasMixin(p, setting.mixin.UserMixin)
|
||||
)
|
||||
.map((p) => p._id)
|
||||
classes = filterDescendants(hierarchy, ofClass, res)
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="antiComponent">
|
||||
<div class="ac-header short divide">
|
||||
<div class="ac-header__icon"><Icon icon={setting.icon.Clazz} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.ClassSetting} /></div>
|
||||
</div>
|
||||
{#if !withoutHeader}
|
||||
<div class="ac-header short divide">
|
||||
<div class="ac-header__icon"><Icon icon={setting.icon.Clazz} size={'medium'} /></div>
|
||||
<div class="ac-header__title"><Label label={setting.string.ClassSetting} /></div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="ac-body columns hScroll">
|
||||
<div class="ac-column">
|
||||
<div class="overflow-y-auto">
|
||||
<ClassHierarchy
|
||||
{classes}
|
||||
{_class}
|
||||
{ofClass}
|
||||
on:select={(e) => {
|
||||
_class = e.detail
|
||||
}}
|
||||
@ -66,7 +74,7 @@
|
||||
</div>
|
||||
<div class="ac-column max">
|
||||
{#if _class !== undefined}
|
||||
<ClassAttributes {_class} />
|
||||
<ClassAttributes {_class} {ofClass} {attributeMapper} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -31,8 +31,8 @@
|
||||
import view from '@hcengineering/view-resources/src/plugin'
|
||||
|
||||
export let value: Enum | undefined
|
||||
let name: string = value?.name ?? ''
|
||||
let values: string[] = value?.enumValues ?? []
|
||||
export let name: string = value?.name ?? ''
|
||||
export let values: string[] = value?.enumValues ?? []
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
|
@ -13,25 +13,30 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Button, Component, eventToHTMLElement, Label, Link } from '@hcengineering/ui'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { showPopup } from '@hcengineering/ui'
|
||||
import type { Integration, IntegrationType } from '@hcengineering/setting'
|
||||
import setting from '../plugin'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { getCurrentAccount, Ref, Space } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import type { Integration, IntegrationType } from '@hcengineering/setting'
|
||||
import {
|
||||
AnyComponent,
|
||||
Button,
|
||||
Component,
|
||||
eventToHTMLElement,
|
||||
Label,
|
||||
PopupPosAlignment,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import setting from '../plugin'
|
||||
|
||||
export let integrationType: IntegrationType
|
||||
export let integration: Integration | undefined
|
||||
const accountId = getCurrentAccount()._id
|
||||
const client = getClient()
|
||||
const onDisconnectP = getResource(integrationType.onDisconnect)
|
||||
const space = accountId as string as Ref<Space>
|
||||
|
||||
async function close (res: any): Promise<void> {
|
||||
if (res?.value) {
|
||||
await client.createDoc(setting.class.Integration, space, {
|
||||
type: integrationType._id,
|
||||
if (res?.value && integration !== undefined) {
|
||||
await client.update(integration, {
|
||||
value: res.value,
|
||||
disabled: false
|
||||
})
|
||||
@ -52,18 +57,28 @@
|
||||
}
|
||||
|
||||
async function disconnect (): Promise<void> {
|
||||
if (integration !== undefined) {
|
||||
await (
|
||||
await onDisconnectP
|
||||
)()
|
||||
if (integration !== undefined && integrationType.onDisconnect !== undefined) {
|
||||
const disconnect = await getResource(integrationType.onDisconnect)
|
||||
await disconnect()
|
||||
}
|
||||
}
|
||||
const handleAdd = (e: any) => {
|
||||
showPopup(integrationType.createComponent, {}, eventToHTMLElement(e), close)
|
||||
const handleConfigure = async (e: any, component?: AnyComponent, pos?: PopupPosAlignment): Promise<void> => {
|
||||
if (component === undefined) {
|
||||
return
|
||||
}
|
||||
if (integration === undefined) {
|
||||
const id = await client.createDoc(setting.class.Integration, space, {
|
||||
type: integrationType._id,
|
||||
value: '',
|
||||
disabled: false
|
||||
})
|
||||
integration = await client.findOne(setting.class.Integration, { _id: id })
|
||||
}
|
||||
showPopup(component, { integration }, pos ?? eventToHTMLElement(e), close)
|
||||
}
|
||||
const handleReconnect = (e: any) => {
|
||||
if (integrationType.reconnectComponent) {
|
||||
showPopup(integrationType.reconnectComponent, {}, eventToHTMLElement(e), reconnect)
|
||||
showPopup(integrationType.reconnectComponent, { integration }, eventToHTMLElement(e), reconnect)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -72,23 +87,34 @@
|
||||
<div class="flex-row-center header">
|
||||
<div class="icon mr-4"><Component is={integrationType.icon} /></div>
|
||||
<div class="flex-grow flex-col">
|
||||
<div class="fs-title max-label overflow-label"><Label label={integrationType.label} /></div>
|
||||
<div class="text-sm content-dark-color max-label overflow-label">
|
||||
<Label label={integrationType.description} />
|
||||
</div>
|
||||
<div class="fs-title overflow-label"><Label label={integrationType.label} /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod temp</div>
|
||||
<div class="content">
|
||||
<Label label={integrationType.description} />
|
||||
</div>
|
||||
<div class="footer">
|
||||
{#if integration}
|
||||
{#if integration.disabled && integrationType.reconnectComponent}
|
||||
<Button label={setting.string.Reconnect} kind={'primary'} on:click={handleReconnect} />
|
||||
{:else}
|
||||
{#if (integration?.value ?? '') === ''}
|
||||
{#if integrationType.createComponent}
|
||||
<Button
|
||||
label={setting.string.Add}
|
||||
kind={'primary'}
|
||||
on:click={(ev) => handleConfigure(ev, integrationType.createComponent)}
|
||||
/>
|
||||
{/if}
|
||||
{:else if (integration?.disabled ?? false) && integrationType.reconnectComponent}
|
||||
<Button label={setting.string.Reconnect} kind={'primary'} on:click={handleReconnect} />
|
||||
{:else if integration?.value !== ''}
|
||||
{#if integrationType.onDisconnect}
|
||||
<Button label={setting.string.Disconnect} on:click={disconnect} />
|
||||
{/if}
|
||||
{:else}
|
||||
<Button label={setting.string.Add} kind={'primary'} on:click={handleAdd} />
|
||||
<Link label={'Learn more'} />
|
||||
{#if integrationType.configureComponent !== undefined}
|
||||
<Button
|
||||
label={setting.string.Configure}
|
||||
kind={'primary'}
|
||||
on:click={(ev) => handleConfigure(ev, integrationType.configureComponent, 'top')}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@ -107,8 +133,8 @@
|
||||
}
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
min-width: 2.25rem;
|
||||
min-height: 2.25rem;
|
||||
}
|
||||
.content {
|
||||
margin: 0 1.5rem 0.25rem;
|
||||
|
@ -23,14 +23,13 @@
|
||||
|
||||
export let type: EnumOf | undefined
|
||||
export let editable: boolean = true
|
||||
export let value: Enum | undefined
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let value: Enum | undefined
|
||||
|
||||
$: value && dispatch('change', { type: TypeEnum(value._id) })
|
||||
$: ref = type?.of
|
||||
$: ref = value?._id ?? type?.of
|
||||
|
||||
const create = {
|
||||
label: setting.string.CreateEnum,
|
||||
|
@ -36,13 +36,15 @@ import Terms from './components/Terms.svelte'
|
||||
import BooleanTypeEditor from './components/typeEditors/BooleanTypeEditor.svelte'
|
||||
import DateTypeEditor from './components/typeEditors/DateTypeEditor.svelte'
|
||||
import EnumTypeEditor from './components/typeEditors/EnumTypeEditor.svelte'
|
||||
import HyperlinkTypeEditor from './components/typeEditors/HyperlinkTypeEditor.svelte'
|
||||
import NumberTypeEditor from './components/typeEditors/NumberTypeEditor.svelte'
|
||||
import RefEditor from './components/typeEditors/RefEditor.svelte'
|
||||
import StringTypeEditor from './components/typeEditors/StringTypeEditor.svelte'
|
||||
import HyperlinkTypeEditor from './components/typeEditors/HyperlinkTypeEditor.svelte'
|
||||
import WorkspaceSettings from './components/WorkspaceSettings.svelte'
|
||||
import setting from './plugin'
|
||||
|
||||
export { ClassSetting }
|
||||
|
||||
async function DeleteMixin (object: Mixin<Class<Doc>>): Promise<void> {
|
||||
const docs = await getClient().findAll(object._id, {}, { limit: 1 })
|
||||
|
||||
|
47
plugins/setting-resources/src/utils.ts
Normal file
47
plugins/setting-resources/src/utils.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import { Class, Doc, Hierarchy, Ref } from '@hcengineering/core'
|
||||
import setting from '@hcengineering/setting'
|
||||
|
||||
function isEditable (hierarchy: Hierarchy, p: Class<Doc>): boolean {
|
||||
let ancestors = [p._id]
|
||||
try {
|
||||
ancestors = [...hierarchy.getAncestors(p._id), p._id]
|
||||
} catch (err: any) {
|
||||
console.error(err)
|
||||
}
|
||||
for (const ao of ancestors) {
|
||||
try {
|
||||
const cl = hierarchy.getClass(ao)
|
||||
if (hierarchy.hasMixin(cl, setting.mixin.Editable) && hierarchy.as(cl, setting.mixin.Editable).value) {
|
||||
return true
|
||||
}
|
||||
} catch (err: any) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
export function filterDescendants (
|
||||
hierarchy: Hierarchy,
|
||||
ofClass: Ref<Class<Doc>> | undefined,
|
||||
res: Array<Class<Doc>>
|
||||
): Array<Ref<Class<Doc>>> {
|
||||
let _classes = res
|
||||
.filter((it) => {
|
||||
try {
|
||||
return ofClass != null ? hierarchy.isDerived(it._id, ofClass) : true
|
||||
} catch (err: any) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
.filter((it) => !(it.hidden ?? false))
|
||||
.filter((p) => isEditable(hierarchy, p))
|
||||
|
||||
let len: number
|
||||
const _set = new Set(_classes.map((it) => it._id))
|
||||
do {
|
||||
len = _classes.length
|
||||
_classes = _classes.filter((it) => (it.extends != null ? !_set.has(it.extends) : false))
|
||||
} while (len !== _classes.length)
|
||||
|
||||
return _classes.map((p) => p._id)
|
||||
}
|
@ -13,10 +13,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Asset, IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
import type { Class, Doc, Mixin, Ref } from '@hcengineering/core'
|
||||
import type { Plugin } from '@hcengineering/platform'
|
||||
import { Asset, IntlString, plugin, Resource } from '@hcengineering/platform'
|
||||
import { AnyComponent } from '@hcengineering/ui'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -30,9 +30,12 @@ export interface IntegrationType extends Doc {
|
||||
label: IntlString
|
||||
description: IntlString
|
||||
icon: AnyComponent
|
||||
createComponent: AnyComponent
|
||||
onDisconnect: Handler
|
||||
|
||||
createComponent?: AnyComponent
|
||||
onDisconnect?: Handler
|
||||
reconnectComponent?: AnyComponent
|
||||
|
||||
configureComponent?: AnyComponent
|
||||
}
|
||||
|
||||
/**
|
||||
@ -143,7 +146,8 @@ export default plugin(settingId, {
|
||||
SelectWorkspace: '' as IntlString,
|
||||
Reconnect: '' as IntlString,
|
||||
ClassSetting: '' as IntlString,
|
||||
Owners: '' as IntlString
|
||||
Owners: '' as IntlString,
|
||||
Configure: '' as IntlString
|
||||
},
|
||||
icon: {
|
||||
EditProfile: '' as Asset,
|
||||
|
@ -34,7 +34,9 @@ import CreateTagElement from './components/CreateTagElement.svelte'
|
||||
import { ObjQueryType } from '@hcengineering/core'
|
||||
import { getRefs } from './utils'
|
||||
import { Filter } from '@hcengineering/view'
|
||||
import WeightPopup from './components/WeightPopup.svelte'
|
||||
|
||||
export { WeightPopup }
|
||||
export async function tagsInResult (filter: Filter, onUpdate: () => void): Promise<ObjQueryType<any>> {
|
||||
const result = await getRefs(filter, onUpdate)
|
||||
return { $in: result }
|
||||
|
@ -26,6 +26,7 @@
|
||||
import { ListView, resizeObserver } from '@hcengineering/ui'
|
||||
import ObjectPresenter from './ObjectPresenter.svelte'
|
||||
import { tick } from 'svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let viewContext: ViewContext
|
||||
|
||||
@ -171,6 +172,7 @@
|
||||
}
|
||||
return result
|
||||
}
|
||||
const dispatch = createEventDispatcher()
|
||||
</script>
|
||||
|
||||
<ActionContext
|
||||
|
@ -15,7 +15,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { IntlString } from '@hcengineering/platform'
|
||||
import { EditBox, Label, showPopup, eventToHTMLElement } from '@hcengineering/ui'
|
||||
import { EditBox, Label, showPopup, eventToHTMLElement, Button } from '@hcengineering/ui'
|
||||
import EditBoxPopup from './EditBoxPopup.svelte'
|
||||
|
||||
// export let label: IntlString
|
||||
@ -24,7 +24,7 @@
|
||||
export let focus: boolean
|
||||
// export let maxWidth: string = '10rem'
|
||||
export let onChange: (value: number | undefined) => void
|
||||
export let kind: 'no-border' | 'link' = 'no-border'
|
||||
export let kind: 'no-border' | 'link' | 'button' = 'no-border'
|
||||
export let readonly = false
|
||||
|
||||
let shown: boolean = false
|
||||
@ -38,6 +38,7 @@
|
||||
</script>
|
||||
|
||||
{#if kind === 'link'}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class="link-container"
|
||||
on:click={(ev) => {
|
||||
@ -58,6 +59,30 @@
|
||||
<span class="dark-color"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if kind === 'button'}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<Button
|
||||
size={'small'}
|
||||
on:click={(ev) => {
|
||||
if (!shown && !readonly) {
|
||||
showPopup(EditBoxPopup, { value, format: 'number' }, eventToHTMLElement(ev), (res) => {
|
||||
if (Number.isFinite(res)) {
|
||||
value = res
|
||||
onChange(value)
|
||||
}
|
||||
shown = false
|
||||
})
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
{#if value !== undefined}
|
||||
<span class="overflow-label">{value}</span>
|
||||
{:else}
|
||||
<span class="dark-color"><Label label={placeholder} /></span>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</Button>
|
||||
{:else if readonly}
|
||||
{#if value !== undefined}
|
||||
<span class="overflow-label">{value}</span>
|
||||
|
27
rush.json
27
rush.json
@ -16,7 +16,7 @@
|
||||
* path segment in the "$schema" field for all your Rush config files. This will ensure
|
||||
* correct error-underlining and tab-completion for editors such as VS Code.
|
||||
*/
|
||||
"rushVersion": "5.78.0",
|
||||
"rushVersion": "5.86.0",
|
||||
|
||||
/**
|
||||
* The next field selects which package manager should be installed and determines its version.
|
||||
@ -176,7 +176,7 @@
|
||||
* and set projectFolderMaxDepth to a large number.
|
||||
*/
|
||||
// "projectFolderMinDepth": 2,
|
||||
// "projectFolderMaxDepth": 2,
|
||||
"projectFolderMaxDepth": 3,
|
||||
|
||||
/**
|
||||
* Today the npmjs.com registry enforces fairly strict naming rules for packages, but in the early
|
||||
@ -1413,6 +1413,27 @@
|
||||
"packageName": "@hcengineering/minio",
|
||||
"projectFolder": "server/minio",
|
||||
"shouldPublish": true
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"packageName": "@hcengineering/bitrix",
|
||||
"projectFolder": "plugins/bitrix",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/bitrix-assets",
|
||||
"projectFolder": "plugins/bitrix-assets",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/bitrix-resources",
|
||||
"projectFolder": "plugins/bitrix-resources",
|
||||
"shouldPublish": true
|
||||
},
|
||||
{
|
||||
"packageName": "@hcengineering/model-bitrix",
|
||||
"projectFolder": "models/bitrix",
|
||||
"shouldPublish": true
|
||||
},
|
||||
]
|
||||
}
|
||||
|
@ -517,8 +517,13 @@ class TServerStorage implements ServerStorage {
|
||||
;({ passed, onEnd } = await this.verifyApplyIf(ctx, applyIf))
|
||||
result = passed
|
||||
if (passed) {
|
||||
// Store apply if transaction
|
||||
await ctx.with('domain-tx', { _class, objClass }, async () => await this.getAdapter(DOMAIN_TX).tx(tx))
|
||||
// Store apply if transaction's
|
||||
await ctx.with('domain-tx', { _class, objClass }, async () => {
|
||||
const atx = await this.getAdapter(DOMAIN_TX)
|
||||
for (const ctx of applyIf.txes) {
|
||||
await atx.tx(ctx)
|
||||
}
|
||||
})
|
||||
derived = await this.processDerivedTxes(applyIf.txes, ctx, triggerFx)
|
||||
}
|
||||
} else {
|
||||
|
Loading…
Reference in New Issue
Block a user