Merge remote-tracking branch 'origin/develop' into staging

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2024-11-13 14:54:13 +07:00
198 changed files with 8131 additions and 616 deletions
+2 -1
View File
@@ -55,7 +55,8 @@
// "VERSION": "0.6.289",
"ELASTIC_INDEX_NAME": "local_storage_index",
"UPLOAD_URL": "/files",
"AI_BOT_URL": "http://localhost:4010"
"AI_BOT_URL": "http://localhost:4010",
"STATS_URL": "http://host.docker.internal:4900",
},
"runtimeArgs": ["--nolazy", "-r", "ts-node/register"],
"runtimeVersion": "20",
+280 -8
View File
@@ -533,6 +533,9 @@ dependencies:
'@rush-temp/model-support':
specifier: file:./projects/model-support.tgz
version: file:projects/model-support.tgz
'@rush-temp/model-survey':
specifier: file:./projects/model-survey.tgz
version: file:projects/model-survey.tgz
'@rush-temp/model-tags':
specifier: file:./projects/model-tags.tgz
version: file:projects/model-tags.tgz
@@ -545,6 +548,9 @@ dependencies:
'@rush-temp/model-templates':
specifier: file:./projects/model-templates.tgz
version: file:projects/model-templates.tgz
'@rush-temp/model-test-management':
specifier: file:./projects/model-test-management.tgz
version: file:projects/model-test-management.tgz
'@rush-temp/model-text-editor':
specifier: file:./projects/model-text-editor.tgz
version: file:projects/model-text-editor.tgz
@@ -986,6 +992,15 @@ dependencies:
'@rush-temp/support-resources':
specifier: file:./projects/support-resources.tgz
version: file:projects/support-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/survey':
specifier: file:./projects/survey.tgz
version: file:projects/survey.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/survey-assets':
specifier: file:./projects/survey-assets.tgz
version: file:projects/survey-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/survey-resources':
specifier: file:./projects/survey-resources.tgz
version: file:projects/survey-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/tags':
specifier: file:./projects/tags.tgz
version: file:projects/tags.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
@@ -1022,6 +1037,15 @@ dependencies:
'@rush-temp/templates-resources':
specifier: file:./projects/templates-resources.tgz
version: file:projects/templates-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/test-management':
specifier: file:./projects/test-management.tgz
version: file:projects/test-management.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/test-management-assets':
specifier: file:./projects/test-management-assets.tgz
version: file:projects/test-management-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2)
'@rush-temp/test-management-resources':
specifier: file:./projects/test-management-resources.tgz
version: file:projects/test-management-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2)
'@rush-temp/tests-sanity':
specifier: file:./projects/tests-sanity.tgz
version: file:projects/tests-sanity.tgz
@@ -22727,7 +22751,7 @@ packages:
dev: false
file:projects/desktop.tgz(bufferutil@4.0.8)(sass@1.71.1)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-VjzxhhavCElN2Ak6+7CforJ3IJK0/iTE6crFhcRZrbq4RajWlGEHpD/o7O3mun0xepqA0QxpHCkTIO6SQdPsOg==, tarball: file:projects/desktop.tgz}
resolution: {integrity: sha512-rjywmlFY/JneloAn6/L8+zLke89G1qy9ROO+fB2hJwYZDK0JGaN0CE68Sq8iZX6fJ+sAIGW6IsbbfpsvhmhUag==, tarball: file:projects/desktop.tgz}
id: file:projects/desktop.tgz
name: '@rush-temp/desktop'
version: 0.0.0
@@ -24471,7 +24495,7 @@ packages:
dev: false
file:projects/model-all.tgz:
resolution: {integrity: sha512-lZSE4CGfPJX/cTsbqnSoz/ge496Cwl3zeRFHM2wrpgfCFyeCNp00xpRv62LPGe38dvHo/IIzy+Uu0RSVPHUOPw==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-Fxv8P/cfFWJfFli1C70fOej/quvHGGyzJBLpbEixsPV2F+pqX53mwpTzAPqVkHLFVwbRkpnociSpaJQYyx0Beg==, tarball: file:projects/model-all.tgz}
name: '@rush-temp/model-all'
version: 0.0.0
dependencies:
@@ -24981,7 +25005,7 @@ packages:
dev: false
file:projects/model-recruit.tgz:
resolution: {integrity: sha512-BdP6zfox/ew3/p1cG4jllbe5XNq1UBloo1GmYlbiKf8KalP+PwKUEO/sTNfhdJssVtxCbCTdDXAywt7Tht7OQw==, tarball: file:projects/model-recruit.tgz}
resolution: {integrity: sha512-YxkIn1kk3DNsJsw1ZtSxBgwGJXBbVW/Qms0gei//XbhwvbLJ7PfUX1QSI8HtK5Sgk0s6Qwk0TO6eIQmfQn2jxw==, tarball: file:projects/model-recruit.tgz}
name: '@rush-temp/model-recruit'
version: 0.0.0
dependencies:
@@ -25643,6 +25667,24 @@ packages:
- supports-color
dev: false
file:projects/model-survey.tgz:
resolution: {integrity: sha512-PmumdtOkNxmrWv+HQ3mJzjMo2tNPOryNofr53EMElCDEZEn3MPW7l35pRZXg2TUpiPqDKpmFl0UoBLYTHPZ7qQ==, tarball: file:projects/model-survey.tgz}
name: '@rush-temp/model-survey'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.6.2
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-tags.tgz:
resolution: {integrity: sha512-zAxxOBsWAAnh3A9pBqBcleZu1bI9CBzbHB2wunYf8bjMytLX10lv9b8S5IT7HwYc031Q0JLrIU7qFu0MwZLjlg==, tarball: file:projects/model-tags.tgz}
name: '@rush-temp/model-tags'
@@ -25719,6 +25761,24 @@ packages:
- supports-color
dev: false
file:projects/model-test-management.tgz:
resolution: {integrity: sha512-p0y76Msm6v7Zg8KlvMDiWV3BKCDEw8KjV9sUwC+wkMvKnJ4tOtErd+OdnJFKOyGbtX6yX8rCSp6NIJwqmMmhSA==, tarball: file:projects/model-test-management.tgz}
name: '@rush-temp/model-test-management'
version: 0.0.0
dependencies:
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
prettier: 3.2.5
typescript: 5.6.2
transitivePeerDependencies:
- supports-color
dev: false
file:projects/model-text-editor.tgz:
resolution: {integrity: sha512-z1TlVHF8VxcMeChSZ70ZGJsKdGSKisnpnPGuLmp2yYS30ckD5ohoj4KeqgbKu+qT+72FYSg4COXFoAYnszgfoA==, tarball: file:projects/model-text-editor.tgz}
name: '@rush-temp/model-text-editor'
@@ -27488,7 +27548,7 @@ packages:
dev: false
file:projects/prod.tgz(bufferutil@4.0.8)(sass@1.71.1)(ts-node@10.9.2)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-LpzXVVxqqH4eEF3U7B9uOy/zBFWAZ+FGeGqIBCo9N7UzWxndKf7LZx+eYJQ8gsg5aVoqlGAbXjZJHHSVERGeJQ==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-vNHiwvJN+OiPr+lWDdxC3+YB6vZPF0QHYXMXrmHcagQH5vEfVzRPmL9F8pTnx/MHPvAlDt4DqgQls77F8LFYgg==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@@ -29382,7 +29442,7 @@ packages:
dev: false
file:projects/server-indexer.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-H/iX6BGnUy04J35NoefNnWyoCqEd5RFve5SMXioIuA4tWqglHcAz8JqyPPLlfN+onrsh4Pz0tTVRV6lOZXSXWw==, tarball: file:projects/server-indexer.tgz}
resolution: {integrity: sha512-X0K8LN7D/inN8B9B+/ERJTAm1l0gQ7fA9nOdMMVUHEVZqmq6WoiSZ+vPxdY/dx+MQlYFTcPumxLgZiVCZET4Og==, tarball: file:projects/server-indexer.tgz}
id: file:projects/server-indexer.tgz
name: '@rush-temp/server-indexer'
version: 0.0.0
@@ -29663,7 +29723,7 @@ packages:
dev: false
file:projects/server-pipeline.tgz:
resolution: {integrity: sha512-VCAeDWNsMsyk2flDq7dhTEuS204IxDIMmB8t44NfPWwciiA/9dwla/bq1Fewnmb/28XS1xdw0UuE+X+qX0AHGA==, tarball: file:projects/server-pipeline.tgz}
resolution: {integrity: sha512-nDWnNg7fzZRCx/xLnxVoi/VBXn0JQotVBhwAgPeOn8EtgZ0ZZBMSLMU2S7rrsJFM8KLCCeIF0HJ4qUkTRrOefw==, tarball: file:projects/server-pipeline.tgz}
name: '@rush-temp/server-pipeline'
version: 0.0.0
dependencies:
@@ -30832,6 +30892,111 @@ packages:
- ts-node
dev: false
file:projects/survey-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-jqqAAeJVmFwHqA2l6rghsG0C0xdPUhdH6TBtVlQ2CiMA2w+h3fhcpjZ2T9dmx7ovL6XZaOzXpA96rwuVKmgscg==, tarball: file:projects/survey-assets.tgz}
id: file:projects/survey-assets.tgz
name: '@rush-temp/survey-assets'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/survey-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-gjJ7S5P7EX8WlBteXR2xkQpSVupNu1lvsWGIaVhNhbZN2zShtm6ud+Dafen9RS2BJ8KrI+L3aQHT3UFj9rgh1g==, tarball: file:projects/survey-resources.tgz}
id: file:projects/survey-resources.tgz
name: '@rush-temp/survey-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.19)(ts-node@10.9.2)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.19)
sass: 1.71.1
svelte: 4.2.19
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)
svelte-eslint-parser: 0.33.1(svelte@4.2.19)
svelte-loader: 3.2.0(svelte@4.2.19)
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)(typescript@5.6.2)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- coffeescript
- esbuild
- less
- node-notifier
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
- ts-node
dev: false
file:projects/survey.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-6LcozohIYU7ftHjG6jQq+0+Gcv0VlY6FMAEl6edEaSB/FZVmiC4KPoczG7adIH/aOVMqHm6C99lZqLd0PD6NlQ==, tarball: file:projects/survey.tgz}
id: file:projects/survey.tgz
name: '@rush-temp/survey'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/tags-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-HjhtvtxGTdHWK1mnrsyMIfazKoV9qWbV5H+6Bdp9WzJHUdkQxJEgoSD6OsTuiMAMsmQBtyhHJ4SKfUy733dtTg==, tarball: file:projects/tags-assets.tgz}
id: file:projects/tags-assets.tgz
@@ -31256,6 +31421,113 @@ packages:
- ts-node
dev: false
file:projects/test-management-assets.tgz(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-5goSfMWyruB2GJwVYnn8e9YYJW523dDrcME75Og3+yem8W+yUcteJ+Sy2qgMNjCzC6ADzxCM5TIHItnCpH0gjQ==, tarball: file:projects/test-management-assets.tgz}
id: file:projects/test-management-assets.tgz
name: '@rush-temp/test-management-assets'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@types/node': 20.11.19
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/test-management-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-o+axSz7dIMDinwB3h8Ntr1pP35PGsUtKtTUvEqJFQ1lA+KQdTJ9akLrijzp9T6xRSdTPa6WNG6yjtqM8yhC70w==, tarball: file:projects/test-management-resources.tgz}
id: file:projects/test-management-resources.tgz
name: '@rush-temp/test-management-resources'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
eslint-plugin-svelte: 2.35.1(eslint@8.56.0)(svelte@4.2.19)(ts-node@10.9.2)
fast-equals: 5.0.1
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
prettier: 3.2.5
prettier-plugin-svelte: 3.2.2(prettier@3.2.5)(svelte@4.2.19)
sass: 1.71.1
svelte: 4.2.19
svelte-check: 3.6.9(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)
svelte-eslint-parser: 0.33.1(svelte@4.2.19)
svelte-loader: 3.2.0(svelte@4.2.19)
svelte-preprocess: 5.1.3(postcss-load-config@4.0.2)(postcss@8.4.35)(sass@1.71.1)(svelte@4.2.19)(typescript@5.6.2)
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- coffeescript
- esbuild
- less
- node-notifier
- postcss
- postcss-load-config
- pug
- stylus
- sugarss
- supports-color
- ts-node
dev: false
file:projects/test-management.tgz(@types/node@20.11.19)(esbuild@0.20.1)(ts-node@10.9.2):
resolution: {integrity: sha512-zLxjXJwufmBoWJS5xoCGxG9uybC/T7UtJ+g4PlSnDv16aGSPDbnybLtQ0uorYvlOESpcSgQGrVLCgOhIB90tfw==, tarball: file:projects/test-management.tgz}
id: file:projects/test-management.tgz
name: '@rush-temp/test-management'
version: 0.0.0
dependencies:
'@types/jest': 29.5.12
'@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.56.0)(typescript@5.6.2)
'@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.6.2)
eslint: 8.56.0
eslint-config-standard-with-typescript: 40.0.0(@typescript-eslint/eslint-plugin@6.21.0)(eslint-plugin-import@2.29.1)(eslint-plugin-n@15.7.0)(eslint-plugin-promise@6.1.1)(eslint@8.56.0)(typescript@5.6.2)
eslint-plugin-import: 2.29.1(eslint@8.56.0)
eslint-plugin-n: 15.7.0(eslint@8.56.0)
eslint-plugin-promise: 6.1.1(eslint@8.56.0)
jest: 29.7.0(@types/node@20.11.19)(ts-node@10.9.2)
lexorank: 1.0.5
prettier: 3.2.5
ts-jest: 29.1.2(esbuild@0.20.1)(jest@29.7.0)(typescript@5.6.2)
typescript: 5.6.2
transitivePeerDependencies:
- '@babel/core'
- '@jest/types'
- '@types/node'
- babel-jest
- babel-plugin-macros
- esbuild
- node-notifier
- supports-color
- ts-node
dev: false
file:projects/tests-sanity.tgz:
resolution: {integrity: sha512-noV3nlBP0OvM6i4C5YUevkhltHxG/2bct4pusP3O3AEQ7Za+A4qwzpsE08pHfA9f9pedEG677GE4EmG9x61rVQ==, tarball: file:projects/tests-sanity.tgz}
name: '@rush-temp/tests-sanity'
@@ -31658,7 +31930,7 @@ packages:
dev: false
file:projects/tool.tgz(bufferutil@4.0.8)(utf-8-validate@6.0.4):
resolution: {integrity: sha512-VMtn5vIgitHobfk3a7XXFR5cr1nCbF/KasUqkn+o+EBDB3H+1n9krwH41lyS9KRHDe6HelYQVcBAPbCn87oPcA==, tarball: file:projects/tool.tgz}
resolution: {integrity: sha512-0sEkRVGviHVTW+esQKFXrECZU6kNST4WxDodKO4DRO9FFB6zdlnFFzjy7+QCjNtP6irb4AEgOXVWWBmdZYk4dw==, tarball: file:projects/tool.tgz}
id: file:projects/tool.tgz
name: '@rush-temp/tool'
version: 0.0.0
@@ -31748,7 +32020,7 @@ packages:
dev: false
file:projects/tracker-resources.tgz(@types/node@20.11.19)(esbuild@0.20.1)(postcss-load-config@4.0.2)(postcss@8.4.35)(ts-node@10.9.2):
resolution: {integrity: sha512-6wsyMyxPpL8OKwTKistPKvRxkmmptqXJwxFZ5VOOlGuEwqXFYIPYZNR6taZd33fq1Y97L+vKeP65rWmXhbRIdQ==, tarball: file:projects/tracker-resources.tgz}
resolution: {integrity: sha512-k1Lw3hvnvA+K22hLtdZKz4+ZQbk451p8bQL0RIHZ/yS/dIBLlBpBUU279YAqH2zxlmlFt1+C1WsTsrrPRdfx7A==, tarball: file:projects/tracker-resources.tgz}
id: file:projects/tracker-resources.tgz
name: '@rush-temp/tracker-resources'
version: 0.0.0
+6
View File
@@ -204,6 +204,12 @@
"@hcengineering/analytics-collector-resources": "^0.6.0",
"@hcengineering/ai-bot": "^0.6.0",
"@hcengineering/ai-bot-resources": "^0.6.0",
"@hcengineering/test-management": "^0.6.0",
"@hcengineering/test-management-assets": "^0.6.0",
"@hcengineering/test-management-resources": "^0.6.0",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/survey-assets": "^0.6.0",
"@hcengineering/survey-resources": "^0.6.0",
"electron-squirrel-startup": "~1.0.0",
"dotenv": "~16.0.0",
"electron-context-menu": "^4.0.4",
+8
View File
@@ -53,6 +53,8 @@ import { questionsId } from '@hcengineering/questions'
import { trainingId } from '@hcengineering/training'
import { documentsId } from '@hcengineering/controlled-documents'
import aiBot, { aiBotId } from '@hcengineering/ai-bot'
import { testManagementId } from '@hcengineering/test-management'
import { surveyId } from '@hcengineering/survey'
import '@hcengineering/activity-assets'
import '@hcengineering/attachment-assets'
@@ -94,6 +96,8 @@ import '@hcengineering/products-assets'
import '@hcengineering/controlled-documents-assets'
import '@hcengineering/analytics-collector-assets'
import '@hcengineering/text-editor-assets'
import '@hcengineering/test-management-assets'
import '@hcengineering/survey-assets'
import { coreId } from '@hcengineering/core'
import presentation, { parsePreviewConfig, parseUploadConfig, presentationId } from '@hcengineering/presentation'
@@ -185,6 +189,8 @@ function configureI18n (): void {
addStringsLoader(loveId, async (lang: string) => await import(`@hcengineering/love-assets/lang/${lang}.json`))
addStringsLoader(printId, async (lang: string) => await import(`@hcengineering/print-assets/lang/${lang}.json`))
addStringsLoader(analyticsCollectorId, async (lang: string) => await import(`@hcengineering/analytics-collector-assets/lang/${lang}.json`))
addStringsLoader(testManagementId, async (lang: string) => await import(`@hcengineering/test-management-assets/lang/${lang}.json`))
addStringsLoader(surveyId, async (lang: string) => await import(`@hcengineering/survey-assets/lang/${lang}.json`))
}
export async function configurePlatform (): Promise<void> {
@@ -306,6 +312,8 @@ export async function configurePlatform (): Promise<void> {
addLocation(loveId, () => import(/* webpackChunkName: "love" */ '@hcengineering/love-resources'))
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(testManagementId, () => import(/* webpackChunkName: "test-management" */ '@hcengineering/test-management-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/survey-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+1 -35
View File
@@ -102,7 +102,7 @@ services:
environment:
- PORT=4900
- SERVER_SECRET=secret
restart: unless-stopped
restart: unless-stopped
workspace:
image: hardcoreeng/workspace
extra_hosts:
@@ -221,7 +221,6 @@ services:
links:
- mongodb
- minio
- rekoni
- account
- stats
# - apm-server
@@ -261,7 +260,6 @@ services:
links:
- postgres
- minio
- rekoni
- account
- stats
# - apm-server
@@ -296,10 +294,6 @@ services:
restart: unless-stopped
ports:
- 4004:4004
deploy:
resources:
limits:
memory: 1024M
fulltext:
image: hardcoreeng/fulltext
extra_hosts:
@@ -319,10 +313,6 @@ services:
- STATS_URL=http://host.docker.internal:4900
- REKONI_URL=http://host.docker.internal:4004
- ACCOUNTS_URL=http://host.docker.internal:3000
deploy:
resources:
limits:
memory: 300M
fulltext_pg:
image: hardcoreeng/fulltext
extra_hosts:
@@ -343,10 +333,6 @@ services:
- STATS_URL=http://host.docker.internal:4900
- REKONI_URL=http://host.docker.internal:4004
- ACCOUNTS_URL=http://host.docker.internal:3000
deploy:
resources:
limits:
memory: 300M
print:
image: hardcoreeng/print
extra_hosts:
@@ -358,10 +344,6 @@ services:
- SECRET=secret
- STORAGE_CONFIG=${STORAGE_CONFIG}
- STATS_URL=http://host.docker.internal:4900
deploy:
resources:
limits:
memory: 300M
sign:
image: hardcoreeng/sign
extra_hosts:
@@ -382,10 +364,6 @@ services:
- SERVICE_ID=sign-service
- BRANDING_PATH=/var/cfg/branding.json
- STATS_URL=http://host.docker.internal:4900
deploy:
resources:
limits:
memory: 300M
analytics:
image: hardcoreeng/analytics-collector
extra_hosts:
@@ -402,10 +380,6 @@ services:
- ACCOUNTS_URL=http://host.docker.internal:3000
- SUPPORT_WORKSPACE=support
- STATS_URL=http://host.docker.internal:4900
deploy:
resources:
limits:
memory: 300M
aiBot:
image: hardcoreeng/ai-bot
ports:
@@ -426,10 +400,6 @@ services:
- STATS_URL=http://host.docker.internal:4900
# - LOVE_ENDPOINT=http://host.docker.internal:8096
# - OPENAI_API_KEY=token
deploy:
resources:
limits:
memory: 300M
# telegram-bot:
# image: hardcoreeng/telegram-bot
# extra_hosts:
@@ -445,10 +415,6 @@ services:
# - ACCOUNTS_URL=http://host.docker.internal:3000
# - SERVICE_ID=telegram-bot-service
# - STATS_URL=http://host.docker.internal:4900
# deploy:
# resources:
# limits:
# memory: 300M
volumes:
db:
dbpg:
+6
View File
@@ -233,6 +233,12 @@
"@hcengineering/products-resources": "^0.1.0",
"@hcengineering/ai-bot": "^0.6.0",
"@hcengineering/ai-bot-resources": "^0.6.0",
"@hcengineering/test-management": "^0.6.0",
"@hcengineering/test-management-assets": "^0.6.0",
"@hcengineering/test-management-resources": "^0.6.0",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/survey-assets": "^0.6.0",
"@hcengineering/survey-resources": "^0.6.0",
"@sentry/svelte": "~7.101.0",
"posthog-js": "~1.122.0"
}
+8
View File
@@ -60,6 +60,8 @@ import textEditor, { textEditorId } from '@hcengineering/text-editor'
import analyticsCollector, {analyticsCollectorId} from '@hcengineering/analytics-collector'
import { uploaderId } from '@hcengineering/uploader'
import aiBot, { aiBotId } from '@hcengineering/ai-bot'
import { testManagementId } from '@hcengineering/test-management'
import { surveyId } from '@hcengineering/survey'
import { bitrixId } from '@hcengineering/bitrix'
@@ -103,6 +105,8 @@ import '@hcengineering/controlled-documents-assets'
import '@hcengineering/analytics-collector-assets'
import '@hcengineering/text-editor-assets'
import '@hcengineering/uploader-assets'
import '@hcengineering/test-management-assets'
import '@hcengineering/survey-assets'
import github, { githubId } from '@hcengineering/github'
import '@hcengineering/github-assets'
@@ -231,6 +235,8 @@ function configureI18n(): void {
addStringsLoader(loveId, async (lang: string) => await import(`@hcengineering/love-assets/lang/${lang}.json`))
addStringsLoader(printId, async (lang: string) => await import(`@hcengineering/print-assets/lang/${lang}.json`))
addStringsLoader(analyticsCollectorId, async (lang: string) => await import(`@hcengineering/analytics-collector-assets/lang/${lang}.json`))
addStringsLoader(testManagementId, async (lang: string) => await import(`@hcengineering/test-management-assets/lang/${lang}.json`))
addStringsLoader(surveyId, async (lang: string) => await import(`@hcengineering/survey-assets/lang/${lang}.json`))
}
export async function configurePlatform() {
@@ -399,6 +405,8 @@ export async function configurePlatform() {
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
addLocation(uploaderId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/uploader-resources'))
addLocation(testManagementId, () => import(/* webpackChunkName: "test-management" */ '@hcengineering/test-management-resources'))
addLocation(surveyId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/survey-resources'))
setMetadata(client.metadata.FilterModel, 'ui')
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
+1
View File
@@ -330,6 +330,7 @@ module.exports = [
disableDotRule: true
},
host: '0.0.0.0',
allowedHosts: 'all',
hot: true,
client: {
logging: "info",
+3 -1
View File
@@ -110,6 +110,8 @@
"@hcengineering/model-analytics-collector": "^0.6.0",
"@hcengineering/model-server-ai-bot": "^0.6.0",
"@hcengineering/model-ai-bot": "^0.6.0",
"@hcengineering/model-server-fulltext": "^0.6.0"
"@hcengineering/model-server-fulltext": "^0.6.0",
"@hcengineering/model-test-management": "^0.6.0",
"@hcengineering/model-survey": "^0.6.0"
}
}
+18
View File
@@ -95,11 +95,17 @@ import documents, { documentsId, createModel as documentsModel } from '@hcengine
import products, { productsId, createModel as productsModel } from '@hcengineering/model-products'
import { serverProductsId, createModel as serverProductsModel } from '@hcengineering/model-server-products'
import { serverTrainingId, createModel as serverTrainingModel } from '@hcengineering/model-server-training'
import testManagement, {
testManagementId,
createModel as testManagementModel
} from '@hcengineering/model-test-management'
import {
serverDocumentsId,
createModel as serverDocumentsModel
} from '@hcengineering/model-server-controlled-documents'
import { serverFulltextId, createModel as serverFulltextModel } from '@hcengineering/model-server-fulltext'
import { surveyId, createModel as surveyModel } from '@hcengineering/model-survey'
import { type Plugin } from '@hcengineering/platform'
@@ -404,6 +410,18 @@ export default function buildModel (enabled: string[] = ['*'], disabled: string[
classFilter: defaultFilter
}
],
[
testManagementModel,
testManagementId,
{
label: testManagement.string.ConfigLabel,
description: testManagement.string.ConfigDescription,
enabled: false,
beta: false,
classFilter: defaultFilter
}
],
[surveyModel, surveyId],
[serverCoreModel, serverCoreId],
[serverAttachmentModel, serverAttachmentId],
+5 -1
View File
@@ -52,6 +52,8 @@ import { productsOperation } from '@hcengineering/model-products'
import { requestOperation } from '@hcengineering/model-request'
import { analyticsCollectorOperation } from '@hcengineering/model-analytics-collector'
import { workbenchOperation } from '@hcengineering/model-workbench'
import { testManagementOperation } from '@hcengineering/model-test-management'
import { surveyOperation } from '@hcengineering/model-survey'
export const migrateOperations: [string, MigrateOperation][] = [
['core', coreOperation],
@@ -92,5 +94,7 @@ export const migrateOperations: [string, MigrateOperation][] = [
// We should call notification migration after activityServer and chunter
['notification', notificationOperation],
['analyticsCollector', analyticsCollectorOperation],
['workbench', workbenchOperation]
['workbench', workbenchOperation],
['testManagement', testManagementOperation],
['survey', surveyOperation]
]
+2 -1
View File
@@ -68,6 +68,7 @@ export { calendarId } from '@hcengineering/calendar'
export { calendarOperation } from './migration'
export const DOMAIN_CALENDAR = 'calendar' as Domain
export const DOMAIN_EVENT = 'event' as Domain
@Model(calendar.class.Calendar, core.class.Doc, DOMAIN_CALENDAR)
@UX(calendar.string.Calendar, calendar.icon.Calendar)
@@ -85,7 +86,7 @@ export class TExternalCalendar extends TCalendar implements ExternalCalendar {
externalUser!: string
}
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_CALENDAR)
@Model(calendar.class.Event, core.class.AttachedDoc, DOMAIN_EVENT)
@UX(calendar.string.Event, calendar.icon.Calendar)
export class TEvent extends TAttachedDoc implements Event {
declare space: Ref<SystemSpace>
+11 -1
View File
@@ -24,7 +24,7 @@ import {
type MigrationUpgradeClient
} from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_CALENDAR } from '.'
import { DOMAIN_CALENDAR, DOMAIN_EVENT } from '.'
import calendar from './plugin'
async function migrateCalendars (client: MigrationClient): Promise<void> {
@@ -136,6 +136,16 @@ export const calendarOperation: MigrateOperation = {
{
state: 'migrate_calendars',
func: migrateCalendars
},
{
state: 'move-events',
func: async (client) => {
await client.move(
DOMAIN_CALENDAR,
{ _class: { $in: client.hierarchy.getDescendants(calendar.class.Event) } },
DOMAIN_EVENT
)
}
}
])
},
+34 -31
View File
@@ -2,50 +2,51 @@
// Copyright @ 2022-2023 Hardcore Engineering Inc.
//
import attachment, { type Attachment } from '@hcengineering/attachment'
import {
clone,
loadCollaborativeDoc,
saveCollaborativeDoc,
YAbstractType,
YXmlElement,
YXmlText
} from '@hcengineering/collaboration'
import {
type ChangeControl,
type ControlledDocument,
createChangeControl,
createDocumentTemplate,
type DocumentCategory,
documentsId,
DocumentState
} from '@hcengineering/controlled-documents'
import {
type Class,
type Data,
type Ref,
TxOperations,
generateId,
type Doc,
DOMAIN_TX,
generateId,
makeCollaborativeDoc,
MeasureMetricsContext,
type Class,
type Doc,
SortingOrder
type Ref,
SortingOrder,
toIdMap,
TxOperations
} from '@hcengineering/core'
import {
createDefaultSpace,
createOrUpdate,
type MigrateUpdate,
type MigrationDocumentQuery,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrateUpdate,
type MigrationClient,
type MigrationUpgradeClient
type MigrationDocumentQuery,
type MigrationUpgradeClient,
tryMigrate,
tryUpgrade
} from '@hcengineering/model'
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
import core from '@hcengineering/model-core'
import tags from '@hcengineering/tags'
import {
type ChangeControl,
type DocumentCategory,
DocumentState,
documentsId,
createDocumentTemplate,
type ControlledDocument,
createChangeControl
} from '@hcengineering/controlled-documents'
import {
loadCollaborativeDoc,
saveCollaborativeDoc,
YXmlElement,
YXmlText,
YAbstractType,
clone
} from '@hcengineering/collaboration'
import attachment, { type Attachment } from '@hcengineering/attachment'
import { DOMAIN_ATTACHMENT } from '@hcengineering/model-attachment'
import documents, { DOMAIN_DOCUMENTS } from './index'
@@ -210,6 +211,7 @@ async function createDocumentCategories (tx: TxOperations): Promise<void> {
{ code: 'CM', title: 'Client Management' }
]
const catsCache = toIdMap(await tx.findAll(documents.class.DocumentCategory, {}))
const ops = tx.apply()
for (const c of categories) {
await createOrUpdate(
@@ -217,7 +219,8 @@ async function createDocumentCategories (tx: TxOperations): Promise<void> {
documents.class.DocumentCategory,
documents.space.QualityDocuments,
{ ...c, attachments: 0 },
((documents.category.DOC as string) + ' - ' + c.code) as Ref<DocumentCategory>
((documents.category.DOC as string) + ' - ' + c.code) as Ref<DocumentCategory>,
catsCache
)
}
await ops.commit()
+1
View File
@@ -52,6 +52,7 @@
"@hcengineering/recruit": "^0.6.29",
"@hcengineering/recruit-resources": "^0.6.0",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/task": "^0.6.20",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
+21 -5
View File
@@ -14,7 +14,15 @@
//
import { getCategories } from '@anticrm/skillset'
import core, { DOMAIN_TX, TxOperations, type Ref, type Space, type Status } from '@hcengineering/core'
import core, {
DOMAIN_TX,
toIdMap,
TxOperations,
type Doc,
type Ref,
type Space,
type Status
} from '@hcengineering/core'
import {
createOrUpdate,
migrateSpace,
@@ -26,7 +34,7 @@ import {
type ModelLogger
} from '@hcengineering/model'
import tags, { type TagCategory } from '@hcengineering/model-tags'
import task, { DOMAIN_TASK, createSequence, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import task, { createSequence, DOMAIN_TASK, migrateDefaultStatusesBase } from '@hcengineering/model-task'
import { recruitId, type Applicant } from '@hcengineering/recruit'
import { DOMAIN_CALENDAR } from '@hcengineering/model-calendar'
@@ -194,10 +202,16 @@ async function createDefaults (client: MigrationUpgradeClient, tx: TxOperations)
},
recruit.category.Other
)
const ops = tx.apply()
for (const c of getCategories()) {
const cats = getCategories().filter((it, idx, arr) => arr.findIndex((qt) => qt.id === it.id) === idx)
const existingCategories = toIdMap(
await client.findAll<Doc>(tags.class.TagCategory, { targetClass: recruit.mixin.Candidate })
)
for (const c of cats) {
await createOrUpdate(
tx,
ops,
tags.class.TagCategory,
core.space.Workspace,
{
@@ -207,9 +221,11 @@ async function createDefaults (client: MigrationUpgradeClient, tx: TxOperations)
tags: c.skills,
default: false
},
(recruit.category.Category + '.' + c.id) as Ref<TagCategory>
(recruit.category.Category + '.' + c.id) as Ref<TagCategory>,
existingCategories
)
}
await ops.commit()
await createSequence(tx, recruit.class.Review)
await createSequence(tx, recruit.class.Opinion)
+5
View File
@@ -18,6 +18,7 @@ import {
Account,
IndexKind,
type CollaborativeDoc,
type Collection as Array,
type Domain,
type Markup,
type Ref,
@@ -59,6 +60,7 @@ import type {
Vacancy,
VacancyList
} from '@hcengineering/recruit'
import survey, { type Poll } from '@hcengineering/survey'
import recruit from './plugin'
@Model(recruit.class.Vacancy, task.class.Project)
@@ -159,6 +161,9 @@ export class TApplicant extends TTask implements Applicant {
@Prop(TypeRef(core.class.Status), task.string.TaskState, { _id: recruit.attribute.State })
@Index(IndexKind.Indexed)
declare status: Ref<Status>
@Prop(Collection(survey.class.Poll), survey.string.Polls)
polls?: Array<Poll>
}
@Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK)
+3 -4
View File
@@ -44,14 +44,13 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.ActivityMessagesHandler,
txMatch: {
'tx.objectClass': { $nin: [activity.class.ActivityMessage, notification.class.DocNotifyContext] }
},
arrays: true,
isAsync: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverActivity.trigger.OnDocRemoved
trigger: serverActivity.trigger.OnDocRemoved,
arrays: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+1
View File
@@ -37,6 +37,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverAiBot.trigger.OnMessageSend,
arrays: true,
isAsync: true
})
+2 -1
View File
@@ -23,6 +23,7 @@ export { serverCollaborationId } from '@hcengineering/server-collaboration'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverCollaboration.trigger.OnDelete
trigger: serverCollaboration.trigger.OnDelete,
arrays: true
})
}
+3 -2
View File
@@ -16,6 +16,7 @@
import { type Builder, Mixin, Model } from '@hcengineering/model'
import contact from '@hcengineering/contact'
import core, { type Ref } from '@hcengineering/core'
import { TClass, TDoc } from '@hcengineering/model-core'
import { TNotificationType } from '@hcengineering/model-notification'
@@ -33,7 +34,6 @@ import serverNotification, {
type TypeMatch,
type TypeMatchFunc
} from '@hcengineering/server-notification'
import contact from '@hcengineering/contact'
export { serverNotificationId } from '@hcengineering/server-notification'
@@ -89,7 +89,8 @@ export function createModel (builder: Builder): void {
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnDocRemove
trigger: serverNotification.trigger.OnDocRemove,
arrays: true
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
+2 -1
View File
@@ -25,7 +25,8 @@ export { serverRequestId } from '@hcengineering/server-request'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverRequest.trigger.OnRequest
trigger: serverRequest.trigger.OnRequest,
arrays: true
})
builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, {
+2 -1
View File
@@ -23,7 +23,8 @@ export { serverTagsId } from '@hcengineering/server-tags'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTags.trigger.onTagReference
trigger: serverTags.trigger.onTagReference,
arrays: true
})
builder.mixin<Class<Doc>, ObjectDDParticipant>(
+2 -1
View File
@@ -22,6 +22,7 @@ export { serverTaskId } from '@hcengineering/server-task'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTask.trigger.OnStateUpdate
trigger: serverTask.trigger.OnStateUpdate,
arrays: true
})
}
+1
View File
@@ -38,6 +38,7 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTime.trigger.OnTask,
arrays: true,
isAsync: true
})
+7 -3
View File
@@ -15,10 +15,10 @@
import core, { type Doc } from '@hcengineering/core'
import { type Builder, Mixin } from '@hcengineering/model'
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
import serverView, { type ServerLinkIdProvider } from '@hcengineering/server-view'
import { TClass } from '@hcengineering/model-core'
import { type Resource } from '@hcengineering/platform'
import serverCore, { type TriggerControl } from '@hcengineering/server-core'
import serverView, { type ServerLinkIdProvider } from '@hcengineering/server-view'
export { serverViewId } from '@hcengineering/server-view'
@@ -31,6 +31,10 @@ export function createModel (builder: Builder): void {
builder.createModel(TServerLinkIdProvider)
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverView.trigger.OnCustomAttributeRemove
trigger: serverView.trigger.OnCustomAttributeRemove,
txMatch: {
_class: core.class.TxRemoveDoc,
objectClass: core.class.Attribute
}
})
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@hcengineering/model-survey",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Hardcore Engineering Inc",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-chunter": "^0.6.0",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/survey-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.15"
}
}
+119
View File
@@ -0,0 +1,119 @@
//
// Copyright © 2024 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 activity from '@hcengineering/activity'
import { AccountRole } from '@hcengineering/core'
import { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import chunter from '@hcengineering/model-chunter'
import view, { type Viewlet } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import { surveyId } from '@hcengineering/survey'
import { TPoll, TSurvey } from './types'
import survey from './plugin'
export { surveyId } from '@hcengineering/survey'
export { surveyOperation } from './migration'
export default survey
export * from './types'
export function createModel (builder: Builder): void {
builder.createModel(TPoll, TSurvey)
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: survey.string.Application,
icon: survey.icon.Application,
alias: surveyId,
accessLevel: AccountRole.User,
hidden: false,
locationResolver: survey.resolver.Location,
navigatorModel: {
spaces: [],
specials: [
{
id: 'surveys',
label: survey.string.Surveys,
icon: survey.icon.Survey,
component: workbench.component.SpecialView,
componentProps: {
_class: survey.class.Survey,
icon: survey.icon.Survey,
label: survey.string.Surveys,
createLabel: survey.string.CreateSurvey,
createComponent: survey.component.CreateSurvey
}
}
]
}
},
survey.app.Survey
)
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
attachTo: survey.class.Survey,
descriptor: view.viewlet.Table,
config: ['', 'modifiedOn'],
configOptions: {
hiddenKeys: ['name', 'questions'],
sortable: true
}
},
survey.viewlet.TableSurvey
)
builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectPanel, {
component: survey.component.EditSurveyPanel
})
builder.mixin(survey.class.Survey, core.class.Class, view.mixin.ObjectPresenter, {
presenter: survey.component.SurveyPresenter
})
builder.mixin(survey.class.Survey, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: survey.class.Survey,
components: { input: chunter.component.ChatMessageInput }
})
builder.createDoc<Viewlet>(
view.class.Viewlet,
core.space.Model,
{
attachTo: survey.class.Poll,
descriptor: view.viewlet.Table,
config: ['', 'modifiedOn'],
configOptions: {
hiddenKeys: ['name', 'survey', 'results'],
sortable: true
}
},
survey.viewlet.TablePoll
)
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.ObjectPresenter, {
presenter: survey.component.PollPresenter
})
builder.mixin(survey.class.Poll, core.class.Class, view.mixin.CollectionEditor, {
editor: survey.component.PollCollection
})
}
+42
View File
@@ -0,0 +1,42 @@
//
// Copyright © 2024 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 {
createDefaultSpace,
type MigrateOperation,
type MigrationClient,
type MigrationUpgradeClient,
type ModelLogger,
tryMigrate,
tryUpgrade
} from '@hcengineering/model'
import survey, { surveyId } from './index'
export const surveyOperation: MigrateOperation = {
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
await tryMigrate(client, surveyId, [])
},
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>): Promise<void> {
await tryUpgrade(state, client, surveyId, [
{
state: 'createSpace',
func: async (client) => {
await createDefaultSpace(client, survey.space.Survey, { name: 'Surveys', description: 'Surveys' })
}
}
])
}
}
+20
View File
@@ -0,0 +1,20 @@
//
// Copyright © 2024 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 { mergeIds } from '@hcengineering/platform'
import { surveyId } from '@hcengineering/survey'
import survey from '@hcengineering/survey-resources/src/plugin'
export default mergeIds(surveyId, survey, {})
+54
View File
@@ -0,0 +1,54 @@
//
// Copyright © 2024 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 { IndexKind, type Domain, type Ref } from '@hcengineering/core'
import { ArrOf, Hidden, Index, Model, Prop, TypeRecord, TypeRef, TypeString, UX } from '@hcengineering/model'
import core, { TAttachedDoc, TDoc } from '@hcengineering/model-core'
import { getEmbeddedLabel } from '@hcengineering/platform'
import { type Poll, type Question, type Survey } from '@hcengineering/survey'
import survey from './plugin'
export const DOMAIN_SURVEY = 'survey' as Domain
@Model(survey.class.Survey, core.class.Doc, DOMAIN_SURVEY)
@UX(survey.string.Survey, survey.icon.Survey, 'SURV', 'name', undefined, survey.string.Surveys)
export class TSurvey extends TDoc implements Survey {
@Prop(TypeString(), survey.string.Name)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeString(), survey.string.Prompt)
prompt!: string
@Prop(ArrOf(TypeRecord()), survey.string.Questions)
questions?: Question[]
}
@Model(survey.class.Poll, core.class.Doc, DOMAIN_SURVEY)
@UX(survey.string.Poll, survey.icon.Poll, 'POLL', undefined, undefined, survey.string.Polls)
export class TPoll extends TAttachedDoc implements Poll {
@Prop(TypeRef(survey.class.Survey), survey.string.Survey)
survey!: Ref<Survey>
@Prop(TypeString(), survey.string.Name)
name!: string
@Prop(TypeString(), survey.string.Prompt)
prompt!: string
@Prop(ArrOf(TypeRecord()), getEmbeddedLabel('Answers'))
@Hidden()
results?: { question: string, answer: string[] }[]
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/model/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "model"
}
+59
View File
@@ -0,0 +1,59 @@
{
"name": "@hcengineering/model-test-management",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"author": "Anticrm Platform Contributors",
"template": "@hcengineering/model-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"_phase:build": "compile transpile src",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/attachment": "^0.6.14",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-attachment": "^0.6.0",
"@hcengineering/model-contact": "^0.6.1",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-print": "^0.6.0",
"@hcengineering/model-tracker": "^0.6.0",
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/model-activity": "^0.6.0",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/tags": "^0.6.16",
"@hcengineering/time": "^0.6.0",
"@hcengineering/test-management": "^0.6.0",
"@hcengineering/test-management-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/model-preference": "^0.6.0"
}
}
@@ -0,0 +1,51 @@
//
// Copyright © 2024 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 { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import { TDefaultProjectTypeData } from './types'
import testManagement from './plugin'
export function defineDefaultSpace (builder: Builder): void {
defineDefaultProject(builder)
}
function defineDefaultProject (builder: Builder): void {
builder.createModel(TDefaultProjectTypeData)
builder.createDoc(
core.class.SpaceTypeDescriptor,
core.space.Model,
{
name: testManagement.string.TestProject,
description: testManagement.string.FullDescription,
icon: testManagement.icon.TestProject,
baseClass: testManagement.class.TestProject,
availablePermissions: [
core.permission.UpdateSpace,
core.permission.ArchiveSpace,
core.permission.ForbidDeleteObject
]
},
testManagement.descriptors.ProjectType
)
builder.createDoc(core.class.SpaceType, core.space.Model, {
name: 'Default project type',
descriptor: testManagement.descriptors.ProjectType,
roles: 0,
targetClass: testManagement.mixin.DefaultProjectTypeData
})
}
+422
View File
@@ -0,0 +1,422 @@
//
// Copyright © 2024 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 activity from '@hcengineering/activity'
import chunter from '@hcengineering/chunter'
import core from '@hcengineering/model-core'
import { SortingOrder } from '@hcengineering/core'
import { type Builder } from '@hcengineering/model'
import view, { createAction } from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import print from '@hcengineering/model-print'
import tracker from '@hcengineering/model-tracker'
import { type ViewOptionsModel } from '@hcengineering/view'
import { testManagementId } from '@hcengineering/test-management'
import {
DOMAIN_TEST_MANAGEMENT,
TTypeTestCaseType,
TTypeTestCasePriority,
TTypeTestCaseStatus,
TTestProject,
TTestSuite,
TTestCase,
TDefaultProjectTypeData,
TTestRun,
TTypeTestRunResult,
TTestRunItem
} from './types'
import testManagement from './plugin'
import { definePresenters } from './presenters'
export { testManagementId } from '@hcengineering/test-management/src/index'
function defineApplication (builder: Builder): void {
builder.createDoc(
workbench.class.Application,
core.space.Model,
{
label: testManagement.string.TestManagementApplication,
icon: testManagement.icon.TestManagementApplication,
alias: testManagementId,
hidden: false,
locationResolver: testManagement.resolver.Location,
navigatorModel: {
spaces: [
{
id: 'projects',
label: testManagement.string.Projects,
spaceClass: testManagement.class.TestProject,
addSpaceLabel: testManagement.string.CreateProject,
createComponent: testManagement.component.CreateProject,
icon: testManagement.icon.Home,
specials: [
{
id: 'library',
label: testManagement.string.TestLibrary,
icon: testManagement.icon.TestLibrary,
component: workbench.component.SpecialView,
componentProps: {
_class: testManagement.class.TestCase,
icon: testManagement.icon.TestLibrary,
label: testManagement.string.TestLibrary,
createLabel: testManagement.string.CreateTestCase,
createComponent: testManagement.component.CreateTestCase
},
navigationModel: {
navigationComponent: view.component.FoldersBrowser,
navigationComponentLabel: testManagement.string.TestSuites,
navigationComponentIcon: testManagement.icon.TestSuites,
mainComponentLabel: testManagement.string.TestCases,
mainComponentIcon: testManagement.icon.TestCases,
createComponent: testManagement.component.CreateTestSuite,
navigationComponentProps: {
_class: testManagement.class.TestSuite,
icon: testManagement.icon.TestSuites,
title: testManagement.string.TestSuites,
createLabel: testManagement.string.CreateTestSuite,
createComponent: testManagement.component.CreateTestSuite,
titleKey: 'name',
parentKey: 'parent',
noParentId: testManagement.ids.NoParent,
getFolderLink: testManagement.function.GetTestSuiteLink,
allObjectsLabel: testManagement.string.AllTestCases,
allObjectsIcon: testManagement.icon.TestSuites
},
syncWithLocationQuery: true
}
}
/* TODO: UBERF-8584
{
id: opt.testRunsId,
label: testManagement.string.TestRuns,
icon: testManagement.icon.TestRuns,
component: workbench.component.SpecialView,
componentProps: {
_class: testManagement.class.TestRun,
icon: testManagement.icon.TestRuns,
title: testManagement.string.TestRuns,
createLabel: testManagement.string.NewTestRun,
createComponent: testManagement.component.CreateTestRun
}
} */
]
}
]
},
navHeaderComponent: testManagement.component.NewTestCaseHeader
},
testManagement.app.TestManagement
)
}
export function createModel (builder: Builder): void {
builder.createModel(
TTypeTestCaseType,
TTypeTestCasePriority,
TTypeTestCaseStatus,
TTestProject,
TTestSuite,
TTestCase,
TDefaultProjectTypeData,
TTestRun,
TTestRunItem,
TTypeTestRunResult
)
builder.mixin(testManagement.class.TestProject, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestProject,
components: { input: chunter.component.ChatMessageInput }
})
defineTestSuite(builder)
defineTestCase(builder)
defineTestRun(builder)
definePresenters(builder)
defineApplication(builder)
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectIcon, {
component: testManagement.component.TestCaseStatusPresenter
})
builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, {
domain: DOMAIN_TEST_MANAGEMENT,
disabled: [
{ space: 1 },
{ attachedToClass: 1 },
{ status: 1 },
{ project: 1 },
{ priority: 1 },
{ assignee: 1 },
{ sprint: 1 },
{ component: 1 },
{ category: 1 },
{ modifiedOn: 1 },
{ modifiedBy: 1 },
{ createdBy: 1 },
{ relations: 1 },
{ milestone: 1 },
{ createdOn: -1 }
]
})
defineSpaceType(builder)
}
function defineSpaceType (builder: Builder): void {
builder.createDoc(
core.class.SpaceTypeDescriptor,
core.space.Model,
{
name: testManagement.string.TestProject,
description: testManagement.string.FullDescription,
icon: testManagement.icon.TestProject,
baseClass: testManagement.class.TestProject,
availablePermissions: [
core.permission.UpdateSpace,
core.permission.ArchiveSpace,
core.permission.ForbidDeleteObject
]
},
testManagement.descriptors.ProjectType
)
builder.createDoc(
core.class.SpaceType,
core.space.Model,
{
name: 'Default project type',
descriptor: testManagement.descriptors.ProjectType,
roles: 0,
targetClass: testManagement.mixin.DefaultProjectTypeData
},
testManagement.spaceType.DefaultProject
)
}
function defineTestSuite (builder: Builder): void {
builder.mixin(testManagement.class.TestSuite, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestSuite,
components: { input: chunter.component.ChatMessageInput }
})
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectEditor, {
editor: testManagement.component.EditTestSuite
})
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectPanel, {
component: testManagement.component.EditTestSuite
})
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.TestSuitePresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: testManagement.class.TestSuite,
descriptor: view.viewlet.Table,
config: ['', 'description'],
configOptions: {
strict: true
}
},
testManagement.viewlet.TableTestSuites
)
// Actions
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.IgnoreActions, {
actions: [
view.action.Open,
view.action.OpenInNewTab,
print.action.Print,
tracker.action.EditRelatedTargets,
tracker.action.NewRelatedIssue
]
})
createAction(
builder,
{
action: testManagement.actionImpl.CreateChildTestSuite,
label: testManagement.string.CreateTestSuite,
icon: testManagement.icon.TestSuite,
category: testManagement.category.TestSuite,
input: 'none',
target: testManagement.class.TestSuite,
context: {
mode: ['context', 'browser'],
application: testManagement.app.TestManagement,
group: 'create'
}
},
testManagement.action.CreateChildTestSuite
)
}
function defineTestCase (builder: Builder): void {
builder.mixin(testManagement.class.TestCase, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestCase,
components: { input: chunter.component.ChatMessageInput }
})
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectEditor, {
editor: testManagement.component.EditTestCase
})
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectPanel, {
component: testManagement.component.EditTestCase
})
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.TestCasePresenter
})
builder.mixin(testManagement.class.TypeTestCaseStatus, core.class.Class, view.mixin.AttributeFilter, {
component: view.component.ValueFilter
})
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.AttributePresenter, {
presenter: testManagement.component.TestSuiteRefPresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: testManagement.class.TestCase,
descriptor: view.viewlet.Table,
config: ['', { key: 'attachedTo', label: testManagement.string.TestSuite }, 'status', 'assignee'],
configOptions: {
strict: true
}
},
testManagement.viewlet.TableTestCase
)
const viewOptions: ViewOptionsModel = {
groupBy: ['attachedTo'],
orderBy: [
['status', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['createdOn', SortingOrder.Descending]
],
other: [
{
key: 'shouldShowAll',
type: 'toggle',
defaultValue: false,
actionTarget: 'category',
action: view.function.ShowEmptyGroups,
label: view.string.ShowEmptyGroups
}
]
}
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: testManagement.class.TestCase,
descriptor: view.viewlet.List,
configOptions: {
strict: true,
hiddenKeys: ['title']
},
config: [
{ key: '', displayProps: { fixed: 'left', key: 'lead' } },
{
key: 'status',
props: { kind: 'list', size: 'small', shouldShowName: false }
},
{ key: 'modifiedOn', displayProps: { key: 'modified', fixed: 'right', dividerBefore: true } },
{
key: 'assignee',
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' },
displayProps: { key: 'assignee', fixed: 'right' }
}
],
viewOptions
},
testManagement.viewlet.ListTestCase
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: testManagement.class.TestCase,
descriptor: view.viewlet.Table,
config: ['', 'assignee', 'modifiedOn'],
configOptions: {
sortable: true
},
variant: 'short'
},
testManagement.viewlet.SuiteTestCases
)
}
function defineTestRun (builder: Builder): void {
builder.mixin(testManagement.class.TestRun, core.class.Class, activity.mixin.ActivityDoc, {})
builder.createDoc(activity.class.ActivityExtension, core.space.Model, {
ofClass: testManagement.class.TestRun,
components: { input: chunter.component.ChatMessageInput }
})
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectEditor, {
editor: testManagement.component.EditTestRun
})
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectPanel, {
component: testManagement.component.EditTestRun
})
builder.mixin(testManagement.class.TestRun, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.TestRunPresenter
})
builder.createDoc(
view.class.Viewlet,
core.space.Model,
{
attachTo: testManagement.class.TestRun,
descriptor: view.viewlet.Table,
config: [''],
configOptions: {
strict: true
}
},
testManagement.viewlet.TableTestRun
)
}
export { testManagementOperation } from './migration'
export { default } from './plugin'
+21
View File
@@ -0,0 +1,21 @@
//
// Copyright © 2024 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 { type MigrateOperation, type MigrationClient, type MigrationUpgradeClient } from '@hcengineering/model'
export const testManagementOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (state: Map<string, Set<string>>, client: () => Promise<MigrationUpgradeClient>): Promise<void> {}
}
+49
View File
@@ -0,0 +1,49 @@
//
// Copyright © 2024 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 { testManagementId } from '@hcengineering/test-management'
import testManganement from '@hcengineering/test-management-resources/src/plugin'
import type { Doc, Ref } from '@hcengineering/core'
import { mergeIds } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui/src/types'
import type { Action, ActionCategory, ViewAction } from '@hcengineering/view'
export default mergeIds(testManagementId, testManganement, {
action: {
DeleteTestCase: '' as Ref<Action<Doc, any>>,
CreateChildTestSuite: '' as Ref<Action>,
EditTestSuite: '' as Ref<Action>
},
actionImpl: {
CreateChildTestSuite: '' as ViewAction,
EditTestSuite: '' as ViewAction
},
category: {
TestSuite: '' as Ref<ActionCategory>
},
component: {
CreateTestCase: '' as AnyComponent,
TestCasePresenter: '' as AnyComponent,
ProjectPresenter: '' as AnyComponent,
ProjectSpacePresenter: '' as AnyComponent,
TestSuitePresenter: '' as AnyComponent,
EditTestSuite: '' as AnyComponent,
EditTestCase: '' as AnyComponent,
CreateTestRun: '' as AnyComponent,
TestRunPresenter: '' as AnyComponent,
EditTestRun: '' as AnyComponent,
TestSuiteRefPresenter: '' as AnyComponent
}
})
+56
View File
@@ -0,0 +1,56 @@
//
// Copyright © 2024 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 { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import view from '@hcengineering/model-view'
import testManagement from './plugin'
/**
* Define presenters
*/
export function definePresenters (builder: Builder): void {
//
// Project
//
builder.mixin(testManagement.class.TestProject, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.ProjectPresenter
})
builder.mixin(testManagement.class.TestProject, core.class.Class, view.mixin.SpacePresenter, {
presenter: testManagement.component.ProjectSpacePresenter
})
//
// Test Suite
//
builder.mixin(testManagement.class.TestSuite, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.TestSuitePresenter
})
//
// Test Case
//
builder.mixin(testManagement.class.TestCase, core.class.Class, view.mixin.ObjectPresenter, {
presenter: testManagement.component.TestCasePresenter
})
//
// Type Test Case Status
//
builder.mixin(testManagement.class.TypeTestCaseStatus, core.class.Class, view.mixin.AttributePresenter, {
presenter: testManagement.component.TestCaseStatusPresenter
})
}
+233
View File
@@ -0,0 +1,233 @@
//
// Copyright © 2024 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 type { Employee } from '@hcengineering/contact'
import type {
TestCase,
TestSuite,
TestCaseType,
TestCasePriority,
TestCaseStatus,
TestProject,
TestRun,
TestRunResult,
TestRunItem
} from '@hcengineering/test-management'
import { type Attachment } from '@hcengineering/attachment'
import contact from '@hcengineering/contact'
import chunter from '@hcengineering/chunter'
import { getEmbeddedLabel } from '@hcengineering/platform'
import {
Account,
DateRangeMode,
IndexKind,
type RolesAssignment,
type Role,
Ref,
type Domain,
type Timestamp,
type Type,
type CollectionSize,
type CollaborativeDoc,
type Class
} from '@hcengineering/core'
import {
Mixin,
Model,
Prop,
TypeRef,
UX,
TypeMarkup,
Index,
TypeCollaborativeDoc,
TypeString,
Collection,
ReadOnly,
TypeDate,
Hidden
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import core, { TAttachedDoc, TDoc, TType, TTypedSpace } from '@hcengineering/model-core'
import testManagement from './plugin'
export { testManagementId } from '@hcengineering/test-management/src/index'
export const DOMAIN_TEST_MANAGEMENT = 'test-management' as Domain
/** @public */
export function TypeTestCaseType (): Type<TestCaseType> {
return { _class: testManagement.class.TypeTestCaseType, label: testManagement.string.TestCaseType }
}
@Model(testManagement.class.TypeTestCaseType, core.class.Type, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestCaseType)
export class TTypeTestCaseType extends TType {}
/** @public */
export function TypeTestCasePriority (): Type<TestCasePriority> {
return { _class: testManagement.class.TypeTestCasePriority, label: testManagement.string.TestCasePriority }
}
@Model(testManagement.class.TypeTestCasePriority, core.class.Type, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestCasePriority)
export class TTypeTestCasePriority extends TType {}
/** @public */
export function TypeTestCaseStatus (): Type<TestCaseStatus> {
return { _class: testManagement.class.TypeTestCaseStatus, label: testManagement.string.TestCaseStatus }
}
@Model(testManagement.class.TypeTestCaseStatus, core.class.Type, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestCaseStatus)
export class TTypeTestCaseStatus extends TType {}
@Model(testManagement.class.TestProject, core.class.TypedSpace)
@UX(testManagement.string.TestProject)
export class TTestProject extends TTypedSpace implements TestProject {
@Prop(TypeMarkup(), testManagement.string.FullDescription)
@Index(IndexKind.FullText)
fullDescription?: string
}
@Mixin(testManagement.mixin.DefaultProjectTypeData, testManagement.class.TestProject)
@UX(getEmbeddedLabel('Default project'), testManagement.icon.TestProject)
export class TDefaultProjectTypeData extends TTestProject implements RolesAssignment {
[key: Ref<Role>]: Ref<Account>[]
}
/**
* @public
*/
@Model(testManagement.class.TestSuite, core.class.Doc, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestSuite, testManagement.icon.TestSuite, testManagement.string.TestSuite)
export class TTestSuite extends TDoc implements TestSuite {
@Prop(TypeString(), testManagement.string.SuiteName)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeMarkup(), testManagement.string.SuiteDescription)
@Index(IndexKind.FullText)
description?: string
@Prop(TypeRef(testManagement.class.TestSuite), testManagement.string.TestSuite)
parent!: Ref<TestSuite>
@Prop(Collection(testManagement.class.TestCase), testManagement.string.TestCases, {
shortLabel: testManagement.string.TestCase
})
testCases?: CollectionSize<TestCase>
declare space: Ref<TestProject>
}
/**
* @public
*/
@Model(testManagement.class.TestCase, core.class.AttachedDoc, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestCase, testManagement.icon.TestCase, testManagement.string.TestCase)
export class TTestCase extends TAttachedDoc implements TestCase {
@Prop(TypeRef(testManagement.class.TestProject), core.string.Space)
@Index(IndexKind.Indexed)
@Hidden()
declare space: Ref<TestProject>
@Prop(TypeRef(testManagement.class.TestSuite), core.string.AttachedTo)
@Index(IndexKind.Indexed)
declare attachedTo: Ref<TestSuite>
@Prop(TypeRef(testManagement.class.TestSuite), core.string.AttachedToClass)
@Index(IndexKind.Indexed)
@Hidden()
declare attachedToClass: Ref<Class<TestSuite>>
@Prop(TypeString(), core.string.Collection)
@Hidden()
override collection: 'testCases' = 'testCases'
@Prop(TypeString(), testManagement.string.TestName)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeCollaborativeDoc(), testManagement.string.FullDescription)
@Index(IndexKind.FullText)
description!: CollaborativeDoc
@Prop(TypeTestCaseType(), testManagement.string.TestType)
@ReadOnly()
type!: TestCaseType
@Prop(TypeTestCasePriority(), testManagement.string.TestPriority)
@ReadOnly()
priority!: TestCasePriority
@Prop(TypeTestCaseStatus(), testManagement.string.TestStatus)
@ReadOnly()
status!: TestCaseStatus
@Prop(TypeRef(contact.mixin.Employee), testManagement.string.TestAssignee)
assignee!: Ref<Employee>
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: CollectionSize<Attachment>
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
comments?: number
}
@Model(testManagement.class.TestRun, core.class.Doc, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestRun)
export class TTestRun extends TDoc implements TestRun {
@Prop(TypeString(), testManagement.string.TestRunName)
@Index(IndexKind.FullText)
name!: string
@Prop(TypeCollaborativeDoc(), testManagement.string.FullDescription)
@Index(IndexKind.FullText)
description!: CollaborativeDoc
@Prop(TypeDate(DateRangeMode.DATETIME), testManagement.string.DueDate)
dueDate?: Timestamp
@Prop(Collection(testManagement.class.TestRunItem), testManagement.string.TestRunItems, {
shortLabel: testManagement.string.TestRunItem
})
items?: CollectionSize<TestRunItem>
}
/** @public */
export function TypeTestRunResult (): Type<TestRunResult> {
return { _class: testManagement.class.TypeTestRunResult, label: testManagement.string.TestRunResult }
}
@Model(testManagement.class.TypeTestRunResult, core.class.Type, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestRunResult)
export class TTypeTestRunResult extends TType {}
@Model(testManagement.class.TestRunItem, core.class.AttachedDoc, DOMAIN_TEST_MANAGEMENT)
@UX(testManagement.string.TestRunItem)
export class TTestRunItem extends TAttachedDoc implements TestRunItem {
@Prop(TypeRef(testManagement.class.TestRun), testManagement.string.TestRun)
testRun!: Ref<TestRun>
@Prop(TypeRef(testManagement.class.TestCase), testManagement.string.TestCase)
testCase!: Ref<TestCase>
@Prop(TypeTestRunResult(), testManagement.string.TestRunResult)
result?: TestRunResult
@Prop(Collection(chunter.class.ChatMessage), chunter.string.Comments)
comments?: number
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/model/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+2 -1
View File
@@ -88,7 +88,8 @@ export default mergeIds(viewId, view, {
ImageViewer: '' as AnyComponent,
VideoViewer: '' as AnyComponent,
PDFViewer: '' as AnyComponent,
TextViewer: '' as AnyComponent
TextViewer: '' as AnyComponent,
FoldersBrowser: '' as AnyComponent
},
string: {
Table: '' as IntlString,
+6
View File
@@ -0,0 +1,6 @@
{
"name": "platform",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+4 -3
View File
@@ -1,4 +1,4 @@
import { Class, Data, Doc, DocumentUpdate, Ref, Space, TxOperations } from '@hcengineering/core'
import { Class, Data, Doc, DocumentUpdate, Ref, Space, TxOperations, type IdMap } from '@hcengineering/core'
import { deepEqual } from 'fast-equals'
function toUndef (value: any): any {
@@ -40,9 +40,10 @@ export async function createOrUpdate<T extends Doc> (
_class: Ref<Class<T>>,
space: Ref<Space>,
data: Data<T>,
_id: Ref<T>
_id: Ref<T>,
cache?: IdMap<Doc>
): Promise<void> {
const existingDoc = await client.findOne<Doc>(_class, { _id })
const existingDoc = cache !== undefined ? cache.get(_id) : await client.findOne<Doc>(_class, { _id })
if (existingDoc !== undefined) {
const { _class: _oldClass, _id, space: _oldSpace, modifiedBy, modifiedOn, ...oldData } = existingDoc
if (modifiedBy === client.txFactory.account) {
@@ -0,0 +1,38 @@
<!--
//
// Copyright © 2024 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 { IntlString } from '@hcengineering/platform'
import type { ComponentProps } from 'svelte'
import Icon from './Icon.svelte'
import Label from './Label.svelte'
export let icon: ComponentProps<Icon>['icon']
export let label: IntlString
export let labelParams: Record<string, any> = {}
</script>
<div class="antiSection-empty solid flex-col-center mt-3">
<div class="flex-center caption-color">
<Icon {icon} size="large" />
</div>
<span class="text-sm content-dark-color mt-2">
<Label {label} params={labelParams} />
</span>
<slot />
</div>
+1
View File
@@ -272,6 +272,7 @@ export { default as TimeZonesPopup } from './components/TimeZonesPopup.svelte'
export { default as CodeForm } from './components/CodeForm.svelte'
export { default as CodeInput } from './components/CodeInput.svelte'
export { default as TimeLeft } from './components/TimeLeft.svelte'
export { default as SectionEmpty } from './components/SectionEmpty.svelte'
export { default as Dock } from './components/Dock.svelte'
@@ -148,7 +148,6 @@
<div class="location">
<AttributeEditor maxWidth="20rem" _class={contact.class.Person} {editable} {object} key="city" focusIndex={3} />
</div>
<div class="separator" />
<Scroller contentDirection={'horizontal'} padding={'.125rem .125rem .5rem'} stickedScrollBars thinScrollBars>
{#if channels === undefined}
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import calendar from '@hcengineering/calendar'
import calendar, { Calendar } from '@hcengineering/calendar'
import type { Contact, PersonAccount, Organization, Person } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import core, {
@@ -41,7 +41,6 @@
import recruit from '../../plugin'
import IconCompany from '../icons/Company.svelte'
import { Analytics } from '@hcengineering/analytics'
import { getCandidateIdentifier } from '../../utils'
// export let space: Ref<Project>
export let candidate: Ref<Person>
@@ -86,7 +85,8 @@
title,
participants: [currentUser.person],
eventId: '',
dueDate: 0
dueDate: 0,
calendar: '' as Ref<Calendar>
}
const dispatch = createEventDispatcher()
@@ -139,7 +139,7 @@
access: 'reader',
allDay: false,
eventId: '',
calendar: undefined
calendar: '' as Ref<Calendar>
}
)
+1
View File
@@ -41,6 +41,7 @@
"@hcengineering/core": "^0.6.32",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/task": "^0.6.20",
"@hcengineering/calendar": "^0.6.24",
"@hcengineering/ui": "^0.6.15",
+12 -1
View File
@@ -15,7 +15,17 @@
import { Event } from '@hcengineering/calendar'
import type { Channel, Organization, Person } from '@hcengineering/contact'
import type { AttachedData, AttachedDoc, CollaborativeDoc, Markup, Ref, Status, Timestamp } from '@hcengineering/core'
import type {
AttachedData,
AttachedDoc,
CollaborativeDoc,
Collection,
Markup,
Ref,
Status,
Timestamp
} from '@hcengineering/core'
import { Poll } from '@hcengineering/survey'
import { TagReference } from '@hcengineering/tags'
import type { Project, Task } from '@hcengineering/task'
@@ -71,6 +81,7 @@ export interface Applicant extends Task {
attachedTo: Ref<Candidate>
status: Ref<Status>
startDate: Timestamp | null
polls?: Collection<Poll>
}
/** @public */
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+27
View File
@@ -0,0 +1,27 @@
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="application" viewBox="0 0 1024 1024">
<path d="M904 512h-56c-4.4 0-8 3.6-8 8v320H184V184h320c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V520c0-4.4-3.6-8-8-8z" />
<path d="M355.9 534.9L354 653.8c-.1 8.9 7.1 16.2 16 16.2h.4l118-2.9c2-.1 4-.9 5.4-2.3l415.9-415c3.1-3.1 3.1-8.2 0-11.3L785.4 114.3c-1.6-1.6-3.6-2.3-5.7-2.3s-4.1.8-5.7 2.3l-415.8 415a8.3 8.3 0 00-2.3 5.6zm63.5 23.6L779.7 199l45.2 45.1-360.5 359.7-45.7 1.1.7-46.4z" />
</symbol>
<symbol id="radio" viewBox="0 0 15 15">
<path d="M7.5.877a6.623 6.623 0 100 13.246A6.623 6.623 0 007.5.877zM1.827 7.5a5.673 5.673 0 1111.346 0 5.673 5.673 0 01-11.346 0zm5.673 2a2 2 0 100-4 2 2 0 000 4z"/>
</symbol>
<symbol id="checkbox" viewBox="0 0 24 24">
<path d="M19 19H5V5h10V3H5c-1.11 0-2 .89-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-8h-2m-11.09-.92L6.5 11.5 11 16 21 6l-1.41-1.42L11 13.17l-3.09-3.09z" />
</symbol>
<symbol id="textline" viewBox="0 0 24 24">
<path d="M17 7h5v10h-5v2a1 1 0 001 1h2v2h-2.5c-.55 0-1.5-.45-1.5-1 0 .55-.95 1-1.5 1H12v-2h2a1 1 0 001-1V5a1 1 0 00-1-1h-2V2h2.5c.55 0 1.5.45 1.5 1 0-.55.95-1 1.5-1H20v2h-2a1 1 0 00-1 1v2M2 7h11v2H4v6h9v2H2V7m18 8V9h-3v6h3z" />
</symbol>
<symbol id="page" viewBox="0 0 24 24">
<path d="M5 8v12h14V8H5zm0-2h14V4H5v2zm15 16H4a1 1 0 01-1-1V3a1 1 0 011-1h16a1 1 0 011 1v18a1 1 0 01-1 1zM7 10h4v4H7v-4zm0 6h10v2H7v-2zm6-5h4v2h-4v-2z" />
</symbol>
<symbol id="asterisk" viewBox="0 0 24 24">
<path d="M13 3v7.267l6.294-3.633 1 1.732-6.293 3.633 6.293 3.635-1 1.732L13 13.732V21h-2v-7.268l-6.294 3.634-1-1.732L9.999 12 3.706 8.366l1-1.732L11 10.267V3z" />
</symbol>
<symbol id="star" viewBox="0 0 24 24">
<path d="M6.1 21.98a1 1 0 01-1.45-1.06l1.03-6.03-4.38-4.26a1 1 0 01.56-1.71l6.05-.88 2.7-5.48a1 1 0 011.8 0l2.7 5.48 6.06.88a1 1 0 01.55 1.7l-4.38 4.27 1.04 6.03a1 1 0 01-1.46 1.06l-5.4-2.85-5.42 2.85zm4.95-4.87a1 1 0 01.93 0l4.08 2.15-.78-4.55a1 1 0 01.29-.88l3.3-3.22-4.56-.67a1 1 0 01-.76-.54l-2.04-4.14L9.47 9.4a1 1 0 01-.75.54l-4.57.67 3.3 3.22a1 1 0 01.3.88l-.79 4.55 4.09-2.15z" />
</symbol>
<symbol id="question" viewBox="0 0 16 16">
<path d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 01-.5.5h-.77a.5.5 0 01-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777zM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14z" />
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "assets"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+38
View File
@@ -0,0 +1,38 @@
{
"string": {
"Answer": "Answer",
"AnswerPlaceholder": "Type your answer",
"AnswerCustomOption": "Custom option",
"Application": "Survey",
"Close": "Close",
"Control": "Control",
"CreatePoll": "Make a survey",
"CreateSurvey": "Survey",
"DeleteOption": "Delete Option",
"DeleteQuestion": "Delete Question",
"DeleteQuestionConfirm": "Are you sure you want to delete the question?",
"Name": "Title",
"NoAnswer": "No answer",
"NoName": "Untitled",
"NoPollsForDocument": "There are no surveys for this",
"Poll": "Survey",
"Polls": "Surveys",
"Prompt": "Description",
"PromptPlaceholder": "Encourage user to answer some questions",
"Settings": "Settings",
"Question": "Question",
"Questions": "Questions",
"QuestionKindString": "Simple answer",
"QuestionKindOption": "Select an option",
"QuestionKindOptions": "Select several options",
"QuestionIsMandatory": "Is mandatory",
"QuestionHasCustomOption": "Has custom option",
"QuestionOptionPlaceholder": "Add an option",
"QuestionPlaceholder": "Add a question",
"QuestionEmptyPlaceholder": "Provide question text",
"Survey": "Survey Form",
"Surveys": "Survey Forms",
"SurveyPreview": "Preview Form",
"SurveySubmit": "Submit"
}
}
+38
View File
@@ -0,0 +1,38 @@
{
"string": {
"Answer": "Ответ",
"AnswerPlaceholder": "Введите ответ",
"AnswerCustomOption": "Свой вариант",
"Application": "Опросник",
"Close": "Закрыть",
"Control": "Управление",
"CreatePoll": "Провести опрос",
"CreateSurvey": "Анкета",
"DeleteOption": "Удалить вариант",
"DeleteQuestion": "Удалить вопрос",
"DeleteQuestionConfirm": "Уверены, что хотите удалить вопрос?",
"Name": "Заголовок",
"NoAnswer": "Нет ответа",
"NoName": "Без имени",
"NoPollsForDocument": "Здесь пока не опросов",
"Poll": "Опрос",
"Polls": "Опросы",
"Prompt": "Описание",
"PromptPlaceholder": "Описание анкеты",
"Settings": "Настройки",
"Question": "Вопрос",
"Questions": "Вопросы",
"QuestionKindString": "Простой ответ",
"QuestionKindOption": "Выбор варианта",
"QuestionKindOptions": "Выбор нескольких вариантов",
"QuestionIsMandatory": "Обязательный вопрос",
"QuestionHasCustomOption": "Есть свой вариант",
"QuestionOptionPlaceholder": "Добавить вариант",
"QuestionPlaceholder": "Добавить вопрос",
"QuestionEmptyPlaceholder": "Введите текст вопроса",
"Survey": "Анкета",
"Surveys": "Анкеты",
"SurveyPreview": "Просмотр формы",
"SurveySubmit": "Отправить"
}
}
+43
View File
@@ -0,0 +1,43 @@
{
"name": "@hcengineering/survey-assets",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc",
"template": "@hcengineering/assets-package",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"test": "jest --passWithNoTests --silent",
"build:docs": "",
"format": "format src",
"build:watch": "compile",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"@types/node": "~20.11.16",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"typescript": "^5.3.3"
},
"dependencies": {
"@hcengineering/platform": "^0.6.11",
"@hcengineering/survey": "^0.6.0"
},
"repository": "https://github.com/hcenginneing/anticrm",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
@@ -0,0 +1,6 @@
import { makeLocalesTest } from '@hcengineering/platform'
it(
'Locales are equale',
makeLocalesTest((lang) => import(`../../lang/${lang}.json`))
)
+31
View File
@@ -0,0 +1,31 @@
//
// Copyright © 2020 Anticrm Platform Contributors.
//
// 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 survey, { surveyId } from '@hcengineering/survey'
const icons = require('../assets/icons.svg') as string // eslint-disable-line
loadMetadata(survey.icon, {
Application: `${icons}#application`,
Poll: `${icons}#page`,
Question: `${icons}#question`,
QuestionKindString: `${icons}#textline`,
QuestionKindOption: `${icons}#radio`,
QuestionKindOptions: `${icons}#checkbox`,
Survey: `${icons}#application`,
QuestionIsMandatory: `${icons}#asterisk`,
QuestionHasCustomOption: `${icons}#star`
})
addStringsLoader(surveyId, async (lang: string) => await import(`../lang/${lang}.json`))
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/assets/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"types": ["node", "jest"],
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/ui/eslint.config.json'],
parserOptions: { tsconfigRootDir: __dirname }
}
+22
View File
@@ -0,0 +1,22 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"trailingComma": "none",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"printWidth": 120,
"useTabs": false,
"bracketSpacing": true,
"proseWrap": "preserve",
"plugins": [
"prettier-plugin-svelte"
],
"overrides": [
{
"files": "*.svelte",
"options": {
"parser": "svelte"
}
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "ui"
}
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)']
}
+51
View File
@@ -0,0 +1,51 @@
{
"name": "@hcengineering/survey-resources",
"version": "0.6.0",
"main": "src/index.ts",
"author": "Hardcore Engineering Inc",
"license": "EPL-2.0",
"scripts": {
"build": "compile ui",
"build:docs": "api-extractor run --local",
"format": "format src",
"svelte-check": "do-svelte-check",
"_phase:svelte-check": "do-svelte-check",
"build:watch": "compile ui",
"_phase:build": "compile ui",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"svelte-loader": "^3.2.0",
"sass": "^1.53.0",
"svelte-preprocess": "^5.1.3",
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-n": "^15.4.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-svelte": "^2.35.1",
"prettier-plugin-svelte": "^3.2.2",
"eslint": "^8.54.0",
"prettier": "^3.1.0",
"svelte-check": "^3.6.9",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5",
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/core": "^0.6.32",
"@hcengineering/panel": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/presentation": "^0.6.3",
"@hcengineering/survey": "^0.6.0",
"@hcengineering/view": "^0.6.13",
"@hcengineering/view-resources": "^0.6.0",
"@hcengineering/ui": "^0.6.15",
"svelte": "^4.2.19"
}
}
@@ -0,0 +1,5 @@
module.exports = {
plugins: [
require('autoprefixer')
]
}
@@ -0,0 +1,65 @@
<!--
//
// Copyright © 2024 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 { Data, generateId } from '@hcengineering/core'
import { Card, getClient } from '@hcengineering/presentation'
import { Survey } from '@hcengineering/survey'
import { createFocusManager, EditBox, FocusHandler } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import survey from '../plugin'
const manager = createFocusManager()
const dispatch = createEventDispatcher()
const client = getClient()
let name = ''
const id = generateId()
export function canClose (): boolean {
return name === ''
}
async function createSurvey (): Promise<void> {
const data: Data<Survey> = {
name,
prompt: ''
}
const surveyId = await client.createDoc(survey.class.Survey, survey.space.Survey, data, id)
dispatch('close', surveyId)
}
</script>
<FocusHandler {manager} />
<Card
label={survey.string.CreateSurvey}
okAction={createSurvey}
canSave={name.trim().length > 0}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<div class="flex-row-center">
<div class="flex-grow flex-col">
<EditBox placeholder={survey.string.Name} bind:value={name} kind={'large-style'} autoFocus focusIndex={1} />
</div>
</div>
</Card>
@@ -0,0 +1,462 @@
<!--
//
// Copyright © 2024 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 { MessageBox, getClient } from '@hcengineering/presentation'
import { Question, QuestionKind, Survey } from '@hcengineering/survey'
import { Button, EditBox, IconDelete, SelectPopup, eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import survey from '../plugin'
const client = getClient()
const dispatch = createEventDispatcher()
export let parent: Survey
export let index: number
export let readonly: boolean = false
$: question = parent?.questions?.[index] as Question
$: options = question?.options ?? []
$: questionIcon =
question === undefined
? undefined
: question.kind === QuestionKind.OPTIONS
? survey.icon.QuestionKindOptions
: question.kind === QuestionKind.OPTION
? survey.icon.QuestionKindOption
: survey.icon.QuestionKindString
let newOption = ''
let newQuestion = ''
async function updateParent (): Promise<void> {
await client.updateDoc(parent._class, parent.space, parent._id, { questions: parent.questions })
}
async function createQuestion (): Promise<void> {
if (parent.questions === undefined) {
parent.questions = []
}
parent.questions.push({
name: newQuestion,
kind: QuestionKind.STRING,
isMandatory: false,
hasCustomOption: false
})
await updateParent()
newQuestion = ''
}
async function changeName (): Promise<void> {
await updateParent()
}
async function changeKind (kind: QuestionKind): Promise<void> {
if (question.kind !== kind) {
question.kind = kind
await updateParent()
}
}
async function changeMandatory (): Promise<void> {
question.isMandatory = !question.isMandatory
await updateParent()
}
async function changeCustomOption (): Promise<void> {
question.hasCustomOption = !question.hasCustomOption
await updateParent()
}
async function changeOption (index: number): Promise<void> {
if (options[index].trim().length === 0) {
await deleteOption(index)
} else {
await updateParent()
}
}
async function addOption (): Promise<void> {
if (newOption.trim().length === 0) {
newOption = ''
return
}
if (question.options === undefined) {
question.options = []
}
question.options = [...options, newOption]
await updateParent()
newOption = ''
}
async function deleteOption (index: number): Promise<void> {
options.splice(index, 1)
await updateParent()
}
async function deleteQuestion (): Promise<void> {
showPopup(
MessageBox,
{
label: survey.string.DeleteQuestion,
message: survey.string.DeleteQuestionConfirm
},
undefined,
async (result?: boolean) => {
if (result === true) {
parent.questions?.splice(index, 1)
await updateParent()
}
}
)
}
function showQuestionParams (ev: MouseEvent): void {
showPopup(
SelectPopup,
{
value: [
{
id: QuestionKind.STRING,
label: survey.string.QuestionKindString,
icon: survey.icon.QuestionKindString,
isSelected: question.kind === QuestionKind.STRING,
category: { label: survey.string.Answer }
},
{
id: QuestionKind.OPTION,
label: survey.string.QuestionKindOption,
icon: survey.icon.QuestionKindOption,
isSelected: question.kind === QuestionKind.OPTION,
category: { label: survey.string.Answer }
},
{
id: QuestionKind.OPTIONS,
label: survey.string.QuestionKindOptions,
icon: survey.icon.QuestionKindOptions,
isSelected: question.kind === QuestionKind.OPTIONS,
category: { label: survey.string.Answer }
},
{
id: 'mandatory',
label: survey.string.QuestionIsMandatory,
icon: survey.icon.QuestionIsMandatory,
isSelected: question.isMandatory,
category: { label: survey.string.Settings }
},
{
id: 'custom-option',
label: survey.string.QuestionHasCustomOption,
icon: survey.icon.QuestionHasCustomOption,
isSelected: question.hasCustomOption,
category: { label: survey.string.Settings }
},
{
id: 'delete',
label: survey.string.DeleteQuestion,
icon: IconDelete,
category: { label: survey.string.Control }
}
]
},
eventToHTMLElement(ev),
async (id) => {
switch (id) {
case QuestionKind.STRING: {
await changeKind(QuestionKind.STRING)
break
}
case QuestionKind.OPTION: {
await changeKind(QuestionKind.OPTION)
break
}
case QuestionKind.OPTIONS: {
await changeKind(QuestionKind.OPTIONS)
break
}
case 'mandatory': {
await changeMandatory()
break
}
case 'custom-option': {
await changeCustomOption()
break
}
case 'delete': {
await deleteQuestion()
break
}
case undefined: {
break
}
default: {
console.error('Unknown command id', id)
}
}
}
)
}
function showOptionParams (ev: MouseEvent, index: number): void {
showPopup(
SelectPopup,
{
value: [
{
id: 'delete',
label: survey.string.DeleteOption,
icon: IconDelete
}
]
},
eventToHTMLElement(ev),
async (id) => {
switch (id) {
case 'delete': {
await deleteOption(index)
break
}
case undefined: {
break
}
default: {
console.error('Unknown command id', id)
}
}
}
)
}
let draggedIndex: number | undefined
let draggedOverIndex: number | undefined
const draggableElements: HTMLElement[] = []
function dragStart (ev: DragEvent, index: number): void {
if (readonly || ev.dataTransfer === null) {
return
}
draggedIndex = index
ev.dataTransfer.effectAllowed = 'move'
ev.dataTransfer.setDragImage(
draggableElements[index],
(ev.target as HTMLElement).offsetWidth / 2,
(ev.target as HTMLElement).offsetHeight / 2
)
}
function dragOver (ev: DragEvent, index: number): void {
if (draggedIndex === undefined || draggedIndex === draggedOverIndex || draggedIndex + 1 === draggedOverIndex) {
return
}
ev.preventDefault()
draggedOverIndex = index
}
function dragLeave (ev: DragEvent, index: number): void {
if (draggedIndex === undefined) {
return
}
ev.preventDefault()
if (draggedOverIndex === index) {
draggedOverIndex = undefined
}
}
async function dragDrop (): Promise<void> {
if (draggedIndex === undefined || draggedOverIndex === undefined) {
return
}
let modified = false
if (draggedOverIndex === options.length && draggedIndex !== options.length - 1) {
options.push(options[draggedIndex])
options.splice(draggedIndex, 1)
modified = true
} else if (draggedOverIndex < draggedIndex) {
const tmp = options[draggedIndex]
options[draggedIndex] = options[draggedOverIndex]
options[draggedOverIndex] = tmp
modified = true
} else if (draggedIndex + 1 !== draggedOverIndex) {
const tmp = options[draggedIndex]
options[draggedIndex] = options[draggedOverIndex - 1]
options[draggedOverIndex - 1] = tmp
modified = true
}
if (modified) {
await updateParent()
}
}
function dragEnd (): void {
draggedIndex = undefined
draggedOverIndex = undefined
}
let isRootDragging = false
let rootElement: HTMLElement
function rootDragStart (ev: DragEvent): void {
if (readonly || ev.dataTransfer === null) {
return
}
ev.dataTransfer.effectAllowed = 'move'
ev.dataTransfer.setDragImage(
rootElement,
(ev.target as HTMLElement).offsetWidth / 2,
(ev.target as HTMLElement).offsetHeight / 2
)
isRootDragging = true
dispatch('dragStart')
}
function rootDragEnd (): void {
isRootDragging = false
dispatch('dragEnd')
}
</script>
<div class="root" bind:this={rootElement} class:is-dragged={isRootDragging}>
<div class="header">
{#if question === undefined}
<Button noFocus={true} icon={survey.icon.Question} />
<div class="text">
<EditBox placeholder={survey.string.QuestionPlaceholder} bind:value={newQuestion} on:change={createQuestion} />
</div>
{:else}
<div role="presentation" draggable={!readonly} on:dragstart={rootDragStart} on:dragend={rootDragEnd}>
<Button disabled={readonly} icon={questionIcon} on:click={showQuestionParams} />
</div>
<div class="text">
<EditBox
disabled={readonly}
placeholder={survey.string.QuestionEmptyPlaceholder}
bind:value={question.name}
on:change={changeName}
/>
</div>
{/if}
</div>
{#if question !== undefined && question.kind !== QuestionKind.STRING}
<div>
{#each options as option, index (index)}
<div
class="option"
role="listitem"
bind:this={draggableElements[index]}
on:dragover={(ev) => {
dragOver(ev, index)
}}
on:dragleave={(ev) => {
dragLeave(ev, index)
}}
on:drop={dragDrop}
class:is-dragged={index === draggedIndex}
class:dragged-over={draggedIndex !== undefined &&
draggedOverIndex === index &&
draggedOverIndex !== draggedIndex &&
draggedOverIndex !== draggedIndex + 1}
>
<div
role="presentation"
draggable={!readonly}
on:dragstart={(ev) => {
dragStart(ev, index)
}}
on:dragend={dragEnd}
>
<Button
disabled={readonly}
icon={questionIcon}
kind="list"
size="small"
on:click={(ev) => {
showOptionParams(ev, index)
}}
/>
</div>
<div class="text">
<EditBox
disabled={readonly}
placeholder={survey.string.QuestionOptionPlaceholder}
bind:value={options[index]}
on:change={async () => {
await changeOption(index)
}}
/>
</div>
</div>
{/each}
</div>
{#if !readonly}
<div
class="option"
role="listitem"
on:dragover={(ev) => {
dragOver(ev, options.length)
}}
on:dragleave={(ev) => {
dragLeave(ev, options.length)
}}
on:drop={dragDrop}
class:dragged-over={draggedOverIndex === options.length && draggedIndex !== options.length - 1}
>
<Button noFocus={true} icon={survey.icon.Question} kind="list" size="small" />
<div class="text">
<EditBox placeholder={survey.string.QuestionOptionPlaceholder} bind:value={newOption} on:change={addOption} />
</div>
</div>
{/if}
{/if}
</div>
<style lang="scss">
.root {
font-size: 1.05rem;
margin-top: 0.5em;
border-radius: var(--medium-BorderRadius);
padding: 0.5em;
&:hover {
background-color: var(--global-ui-hover-highlight-BackgroundColor);
}
}
.header {
display: flex;
align-items: center;
margin-bottom: 0.25em;
}
.text {
font-size: 0.95em;
margin-left: 1em;
margin-right: 1em;
flex-grow: 1;
}
.option {
display: flex;
align-items: center;
padding-left: 2em;
padding-top: 0.25em;
padding-bottom: 0.25em;
}
.is-dragged {
opacity: 0.2;
transition: opacity 0.1s ease-in;
}
.dragged-over {
transition: box-shadow 0.1s ease-in;
box-shadow: 0 -3px 0 0 var(--primary-button-outline);
}
</style>
@@ -0,0 +1,183 @@
<!--
//
// Copyright © 2024 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 { getClient } from '@hcengineering/presentation'
import { Survey } from '@hcengineering/survey'
import { Button, EditBox, FocusHandler, Label, createFocusManager, showPopup } from '@hcengineering/ui'
import EditQuestion from './EditQuestion.svelte'
import SurveyForm from './SurveyForm.svelte'
import survey from '../plugin'
const manager = createFocusManager()
const client = getClient()
export let object: Survey
export let readonly: boolean = false
export let showPeviewButton = true
$: questions = object?.questions ?? []
async function nameChange (): Promise<void> {
await client.updateDoc(object._class, object.space, object._id, { name: object.name })
}
async function promptChange (): Promise<void> {
await client.updateDoc(object._class, object.space, object._id, { prompt: object.prompt })
}
export function previewSurveyForm (): void {
showPopup(SurveyForm, { source: object }, 'top')
}
let draggedIndex: number | undefined = undefined
let draggedOverIndex: number | undefined = undefined
function dragOver (ev: DragEvent, index: number): void {
if (draggedIndex === undefined || draggedIndex === draggedOverIndex || draggedIndex + 1 === draggedOverIndex) {
return
}
ev.preventDefault()
draggedOverIndex = index
}
function dragLeave (ev: DragEvent, index: number): void {
if (draggedIndex === undefined) {
return
}
ev.preventDefault()
if (draggedOverIndex === index) {
draggedOverIndex = undefined
}
}
async function dragDrop (): Promise<void> {
if (draggedIndex === undefined || draggedOverIndex === undefined) {
return
}
let modified = false
if (draggedOverIndex === questions.length && draggedIndex !== questions.length - 1) {
questions.push(questions[draggedIndex])
questions.splice(draggedIndex, 1)
modified = true
} else if (draggedOverIndex < draggedIndex) {
const tmp = questions[draggedIndex]
questions[draggedIndex] = questions[draggedOverIndex]
questions[draggedOverIndex] = tmp
modified = true
} else if (draggedIndex + 1 !== draggedOverIndex) {
const tmp = questions[draggedIndex]
questions[draggedIndex] = questions[draggedOverIndex - 1]
questions[draggedOverIndex - 1] = tmp
modified = true
}
if (modified) {
await client.updateDoc(object._class, object.space, object._id, { questions })
}
}
</script>
<FocusHandler {manager} />
{#if object !== undefined}
<div class="flex-grow flex-col">
<div class="name">
<EditBox disabled={readonly} placeholder={survey.string.Name} bind:value={object.name} on:change={nameChange} />
{#if showPeviewButton}
<Button icon={survey.icon.Poll} label={survey.string.SurveyPreview} on:click={previewSurveyForm} />
{/if}
</div>
<div class="prompt">
<EditBox
disabled={readonly}
placeholder={survey.string.PromptPlaceholder}
bind:value={object.prompt}
on:change={promptChange}
/>
</div>
<div class="antiSection">
<div class="antiSection-header">
<span class="antiSection-header__title">
<Label label={survey.string.Questions} />
</span>
</div>
{#each questions as question, index}
<div
role="listitem"
on:dragover={(ev) => {
dragOver(ev, index)
}}
on:dragleave={(ev) => {
dragLeave(ev, index)
}}
on:drop={dragDrop}
class:dragged-over={draggedIndex !== undefined &&
draggedOverIndex === index &&
draggedOverIndex !== draggedIndex &&
draggedOverIndex !== draggedIndex + 1}
>
<EditQuestion
{index}
{readonly}
parent={object}
on:dragStart={() => {
draggedIndex = index
}}
on:dragEnd={() => {
draggedIndex = undefined
draggedOverIndex = undefined
}}
/>
</div>
{/each}
{#if !readonly}
<div
role="listitem"
on:dragover={(ev) => {
dragOver(ev, questions.length)
}}
on:dragleave={(ev) => {
dragLeave(ev, questions.length)
}}
on:drop={dragDrop}
class:dragged-over={draggedOverIndex === questions.length && draggedIndex !== questions.length - 1}
>
<EditQuestion parent={object} index={-1} />
</div>
{/if}
</div>
</div>
{/if}
<style lang="scss">
.name {
font-weight: 500;
font-size: 1.25rem;
color: var(--caption-color);
display: flex;
}
.prompt {
font-weight: 400;
font-size: 1.05rem;
color: var(--caption-color);
margin-top: 1em;
margin-bottom: 1em;
}
.dragged-over {
transition: box-shadow 0.1s ease-in;
box-shadow: 0 -3px 0 0 var(--primary-button-outline);
}
</style>
@@ -0,0 +1,91 @@
<!--
//
// Copyright © 2024 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 { Ref } from '@hcengineering/core'
import { Panel } from '@hcengineering/panel'
import { createQuery } from '@hcengineering/presentation'
import { Survey } from '@hcengineering/survey'
import { Button, IconMoreH } from '@hcengineering/ui'
import view from '@hcengineering/view'
import { DocNavLink, showMenu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import EditSurvey from './EditSurvey.svelte'
import survey from '../plugin'
const dispatch = createEventDispatcher()
const query = createQuery()
export let _id: Ref<Survey>
export let embedded: boolean = false
export let readonly: boolean = false
let object: Survey | undefined = undefined
let editor: EditSurvey
$: updateObject(_id)
function updateObject (_id: Ref<Survey>): void {
query.query(survey.class.Survey, { _id }, (result) => {
object = result[0]
})
}
</script>
{#if object}
<Panel
isHeader={false}
isSub={false}
isAside={true}
{embedded}
{object}
on:open
on:close={() => {
dispatch('close')
}}
withoutInput={readonly}
>
<svelte:fragment slot="title">
<DocNavLink noUnderline {object}>
<div class="title">{object.name}</div>
</DocNavLink>
</svelte:fragment>
<svelte:fragment slot="utils">
<Button
icon={survey.icon.Poll}
label={survey.string.SurveyPreview}
on:click={() => {
editor.previewSurveyForm()
}}
/>
{#if !readonly}
<Button
icon={IconMoreH}
iconProps={{ size: 'medium' }}
kind={'icon'}
on:click={(e) => {
showMenu(e, { object, excludedActions: [view.action.Open] })
}}
/>
{/if}
</svelte:fragment>
<div class="flex-col flex-grow flex-no-shrink">
<EditSurvey bind:this={editor} {object} {readonly} showPeviewButton={false} />
</div>
</Panel>
{/if}
@@ -0,0 +1,88 @@
<!--
//
// Copyright © 2024 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, Doc, Ref, Space } from '@hcengineering/core'
import { Survey } from '@hcengineering/survey'
import { Button, IconAdd, Label, Section, showPopup } from '@hcengineering/ui'
import { Viewlet, ViewletPreference } from '@hcengineering/view'
import { Table, ViewletSelector, ViewletSettingButton } from '@hcengineering/view-resources'
import SurveyForm from './SurveyForm.svelte'
import SurveyPopup from './SurveyPopup.svelte'
import survey from '../plugin'
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
export let readonly: boolean = false
export let polls: number
let loading = true
let viewlet: Viewlet | undefined
let preference: ViewletPreference | undefined
function createPoll (ev: MouseEvent): void {
showPopup(SurveyPopup, {}, ev.target as HTMLElement, (result) => {
if (result != null) {
showPopup(SurveyForm, { source: result as Survey, objectId, space, _class }, 'top')
}
})
}
</script>
<Section label={survey.string.Polls} icon={survey.icon.Poll}>
<svelte:fragment slot="header">
<div class="buttons-group xsmall-gap">
<ViewletSelector
hidden
bind:viewlet
bind:preference
bind:loading
viewletQuery={{ _id: survey.viewlet.TablePoll }}
/>
<ViewletSettingButton kind={'tertiary'} bind:viewlet />
{#if !readonly}
<Button id={survey.string.CreatePoll} icon={IconAdd} kind={'ghost'} on:click={createPoll} />
{/if}
</div>
</svelte:fragment>
<svelte:fragment slot="content">
{#if polls > 0 && viewlet}
<Table
_class={survey.class.Poll}
config={preference?.config ?? viewlet.config}
options={viewlet.options}
query={{ attachedTo: objectId }}
loadingProps={{ length: polls }}
{readonly}
/>
{:else}
<div class="antiSection-empty solid flex-col mt-3">
<span class="content-dark-color">
<Label label={survey.string.NoPollsForDocument} />
</span>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
{#if !readonly}
<span class="over-underline content-color" on:click={createPoll}>
<Label label={survey.string.CreatePoll} />
</span>
{/if}
</div>
{/if}
</svelte:fragment>
</Section>
@@ -0,0 +1,41 @@
<!--
//
// Copyright © 2024 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 survey, { Poll } from '@hcengineering/survey'
import { Label, showPopup } from '@hcengineering/ui'
import PollResult from './PollResult.svelte'
import { hasText } from '../utils'
export let value: Poll | undefined | null
export let overflowLabel = true
function showPollResult (): void {
showPopup(PollResult, { poll: value })
}
</script>
{#if value}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<span class="over-underline content-color" class:overflow-label={overflowLabel} on:click={showPollResult}>
{#if hasText(value.name)}
{value.name}
{:else}
<Label label={survey.string.NoName} />
{/if}
</span>
{/if}
@@ -0,0 +1,100 @@
<!--
//
// Copyright © 2024 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 { Card } from '@hcengineering/presentation'
import { Poll } from '@hcengineering/survey'
import { FocusHandler, Label, createFocusManager } from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import survey from '../plugin'
import { hasText } from '../utils'
const dispatch = createEventDispatcher()
const manager = createFocusManager()
export let poll: Poll
onMount(() => {
dispatch('open', {})
})
</script>
<FocusHandler {manager} />
<Card
label={survey.string.Survey}
canSave={true}
okLabel={survey.string.Close}
okAction={() => {
dispatch('close')
}}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<svelte:fragment slot="title">
{#if hasText(poll.name)}
{poll.name}
{:else}
<Label label={survey.string.NoName} />
{/if}
</svelte:fragment>
<div class="antiSection">
{#if hasText(poll.prompt)}
<div class="antiSection-header">
<span class="antiSection-header__title">
{poll.prompt}
</span>
</div>
{/if}
{#each poll.results ?? [] as result}
<div class="antiSection question">
<div class="antiSection-header">
<span class="antiSection-header__title">
{result.question}
</span>
</div>
{#if result.answer === undefined || result.answer === null || result.answer.length === 0}
<div class="answer empty">
<Label label={survey.string.NoAnswer} />
</div>
{:else}
{#each result.answer as answer}
<div class="answer">
{answer}
</div>
{/each}
{/if}
</div>
{/each}
</div>
</Card>
<style lang="scss">
.question {
margin-top: 1.25em;
}
.answer {
margin-left: 2em;
margin-top: 0.5em;
&.empty {
opacity: 0.7;
}
}
</style>
@@ -0,0 +1,127 @@
<!--
//
// Copyright © 2024 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 { Class, Doc, Ref, Space } from '@hcengineering/core'
import { Card, getClient } from '@hcengineering/presentation'
import { FocusHandler, Label, createFocusManager } from '@hcengineering/ui'
import { Question, QuestionKind, Survey } from '@hcengineering/survey'
import { createEventDispatcher, onMount } from 'svelte'
import SurveyFormQuestion from './SurveyFormQuestion.svelte'
import survey from '../plugin'
import { hasText } from '../utils'
const dispatch = createEventDispatcher()
const manager = createFocusManager()
export let source: Survey
export let objectId: Ref<Doc>
export let space: Ref<Space>
export let _class: Ref<Class<Doc>>
const questionNodes: SurveyFormQuestion[] = []
function isQuestionValid (question: Question): boolean {
if (!hasText(question.name)) {
return false
}
if (question.kind === QuestionKind.OPTION || question.kind === QuestionKind.OPTIONS) {
if (question.options === undefined || question.options.length === 0) {
return false
}
}
return true
}
function isPreview (): boolean {
return objectId === undefined || space === undefined || _class === undefined
}
function canSave (): boolean {
return source.questions !== undefined && source.questions.length > 0
// TODO: validate answers
}
async function saveAnswers (): Promise<void> {
if (!isPreview() && canSave()) {
await getClient().addCollection(survey.class.Poll, space, objectId, _class, 'polls', {
survey: source._id,
name: source.name,
prompt: source.prompt,
results: (source.questions ?? []).map((q, i) => {
let answer: string[] = []
const node = questionNodes[i]
if (node !== undefined) {
answer = node.getAnswer()
}
return {
question: q.name,
answer
}
})
})
}
dispatch('close')
}
onMount(() => {
dispatch('open', {})
})
</script>
<FocusHandler {manager} />
<Card
label={survey.string.Survey}
canSave={isPreview() || canSave()}
okLabel={isPreview() ? survey.string.Close : survey.string.SurveySubmit}
okAction={saveAnswers}
on:close={() => {
dispatch('close')
}}
on:changeContent
>
<svelte:fragment slot="title">
{#if hasText(source.name)}
{source.name}
{:else}
<Label label={survey.string.NoName} />
{/if}
</svelte:fragment>
<div class="antiSection">
{#if hasText(source.prompt)}
<div class="antiSection-header">
<span class="antiSection-header__title">
{source.prompt}
</span>
</div>
{/if}
{#each source.questions ?? [] as question, index}
{#if isQuestionValid(question)}
<div class="question">
<SurveyFormQuestion bind:this={questionNodes[index]} {question} />
</div>
{/if}
{/each}
</div>
</Card>
<style lang="scss">
.question {
margin-top: 1.25em;
}
</style>
@@ -0,0 +1,132 @@
<!--
//
// Copyright © 2024 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 { generateId } from '@hcengineering/core'
import { EditBox, Label } from '@hcengineering/ui'
import { Question, QuestionKind } from '@hcengineering/survey'
import survey from '../plugin'
export let question: Question
$: id = generateId()
let answer = ''
let selectedOption: number
const selectedOptions: Record<number, boolean> = {}
const customOption = -1
export function getAnswer (): string[] {
const answers = []
if (question.kind === QuestionKind.STRING) {
if (answer.trim().length > 0) {
answers.push(answer)
}
} else if (question.kind === QuestionKind.OPTION) {
if (question.options !== undefined && selectedOption !== undefined) {
if (selectedOption === customOption) {
answers.push(answer)
} else {
answers.push(question.options[selectedOption])
}
}
} else if (question.kind === QuestionKind.OPTIONS) {
if (question.options !== undefined) {
for (let i = 0; i < question.options.length; i++) {
if (selectedOptions[i]) {
answers.push(question.options[i])
}
}
}
if (selectedOptions[customOption]) {
answers.push(answer)
}
}
return answers
}
</script>
<div class="antiSection">
<div class="antiSection-header">
<span class="antiSection-header__title">
{question.name}
</span>
</div>
{#if question.kind === QuestionKind.OPTION}
{#each question.options ?? [] as option, i}
<div class="option">
<input type="radio" id={`${id}-${i}`} value={i} bind:group={selectedOption} />
<label class="option__label" for={`${id}-${i}`}>
{option}
</label>
</div>
{/each}
{#if question.hasCustomOption}
<div class="option">
<input id="custom" type="radio" value={customOption} bind:group={selectedOption} />
<label class="option__label" for="custom">
<Label label={survey.string.AnswerCustomOption} />
</label>
{#if selectedOption === customOption}
<div class="option__custom">
<EditBox bind:value={answer} placeholder={survey.string.AnswerPlaceholder} />
</div>
{/if}
</div>
{/if}
{:else if question.kind === QuestionKind.OPTIONS}
{#each question.options ?? [] as option, i}
<div class="option">
<input id={`${id}-${i}`} type="checkbox" bind:checked={selectedOptions[i]} />
<label class="option__label" for={`${id}-${i}`}>
{option}
</label>
</div>
{/each}
{#if question.hasCustomOption}
<div class="option">
<input id="custom" type="checkbox" bind:checked={selectedOptions[customOption]} />
<label class="option__label" for="custom">
<Label label={survey.string.AnswerCustomOption} />
</label>
{#if selectedOptions[customOption]}
<div class="option__custom">
<EditBox bind:value={answer} placeholder={survey.string.AnswerPlaceholder} />
</div>
{/if}
</div>
{/if}
{:else}
<div class="option">
<EditBox bind:value={answer} placeholder={survey.string.AnswerPlaceholder} />
</div>
{/if}
</div>
<style lang="scss">
.option {
margin-left: 1em;
margin-top: 0.5em;
}
.option__label {
cursor: pointer;
margin-left: 0.25em;
}
.option__custom {
margin-left: 2em;
margin-top: 0.5em;
}
</style>
@@ -0,0 +1,28 @@
<!--
//
// Copyright © 2024 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 { ObjectPopup } from '@hcengineering/presentation'
import survey from '../plugin'
</script>
<ObjectPopup _class={survey.class.Survey} type={'object'} on:close>
<svelte:fragment slot="item" let:item>
<div class="flex-row-center flex-grow">
{item.name}
</div>
</svelte:fragment>
</ObjectPopup>
@@ -0,0 +1,53 @@
<!--
//
// Copyright © 2024 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 { getEmbeddedLabel } from '@hcengineering/platform'
import { DocNavLink, ObjectMention } from '@hcengineering/view-resources'
import { ObjectPresenterType } from '@hcengineering/view'
import { Icon, tooltip } from '@hcengineering/ui'
import survey, { Survey } from '@hcengineering/survey'
export let value: Survey | undefined | null
export let inline: boolean = false
export let disabled: boolean = false
export let accent: boolean = false
export let noUnderline: boolean = false
export let colorInherit: boolean = false
export let onEdit: ((event: MouseEvent) => void) | undefined = undefined
export let type: ObjectPresenterType = 'link'
export let overflowLabel = true
export let maxWidth: string = ''
</script>
{#if value}
{#if inline}
<ObjectMention object={value} {disabled} {accent} {noUnderline} {colorInherit} onClick={onEdit} />
{:else if type === 'link'}
<DocNavLink object={value} onClick={onEdit} {disabled} {noUnderline} {colorInherit} {accent} noOverflow>
<div class="flex-presenter" style:max-width={maxWidth} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
<div class="icon"><Icon icon={survey.icon.Survey} size={'small'} /></div>
<span class="ap-label" class:overflow-label={overflowLabel} class:colorInherit class:fs-bold={accent}>
{value.name}
</span>
</div>
</DocNavLink>
{:else if type === 'text'}
<span class:overflow-label={overflowLabel} use:tooltip={{ label: getEmbeddedLabel(value.name) }}>
{value.name}
</span>
{/if}
{/if}
+36
View File
@@ -0,0 +1,36 @@
//
// Copyright © 2024 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 { type Resources } from '@hcengineering/platform'
import CreateSurvey from './components/CreateSurvey.svelte'
import EditSurveyPanel from './components/EditSurveyPanel.svelte'
import PollCollection from './components/PollCollection.svelte'
import PollPresenter from './components/PollPresenter.svelte'
import SurveyPresenter from './components/SurveyPresenter.svelte'
import { resolveLocation } from './utils'
export default async (): Promise<Resources> => ({
component: {
CreateSurvey,
EditSurveyPanel,
PollCollection,
PollPresenter,
SurveyPresenter
},
resolver: {
Location: resolveLocation
}
})
+19
View File
@@ -0,0 +1,19 @@
//
// Copyright © 2024 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 { mergeIds } from '@hcengineering/platform'
import survey, { surveyId } from '@hcengineering/survey'
export default mergeIds(surveyId, survey, {})
+51
View File
@@ -0,0 +1,51 @@
import { type Class, type Doc, type Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { type Location, type ResolvedLocation, getPanelURI } from '@hcengineering/ui'
import { accessDeniedStore } from '@hcengineering/view-resources'
import view from '@hcengineering/view'
import survey, { surveyId, type Survey } from '@hcengineering/survey'
export function hasText (value: string | undefined | null): boolean {
return typeof value === 'string' && value.trim().length > 0
}
export async function generateLocation (loc: Location, id: Ref<Survey>): Promise<ResolvedLocation | undefined> {
const client = getClient()
const surv = await client.findOne(survey.class.Survey, { _id: id })
if (surv === undefined) {
accessDeniedStore.set(true)
console.error(`Could not find document ${id}.`)
return undefined
}
const appComponent = loc.path[0] ?? ''
const workspace = loc.path[1] ?? ''
const objectPanel = client.getHierarchy().classHierarchyMixin(surv._class as Ref<Class<Doc>>, view.mixin.ObjectPanel)
const component = objectPanel?.component ?? view.component.EditDoc
return {
loc: {
path: [appComponent, workspace, surveyId],
fragment: getPanelURI(component, surv._id, surv._class, 'content')
},
defaultLocation: {
path: [appComponent, workspace, surveyId],
fragment: getPanelURI(component, surv._id, surv._class, 'content')
}
}
}
export async function resolveLocation (loc: Location): Promise<ResolvedLocation | undefined> {
// if (loc.path[2] !== surveyId) {
// return undefined
// }
// const id = loc.path[3] as Ref<Survey>
// if (id !== undefined) {
// return await generateLocation(loc, id)
// }
return undefined
}
@@ -0,0 +1,5 @@
const sveltePreprocess = require('svelte-preprocess')
module.exports = {
preprocess: sveltePreprocess()
};
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/ui/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types"
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/default/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
+4
View File
@@ -0,0 +1,4 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig"
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
+49
View File
@@ -0,0 +1,49 @@
{
"name": "@hcengineering/survey",
"version": "0.6.0",
"main": "lib/index.js",
"svelte": "src/index.ts",
"types": "types/index.d.ts",
"files": [
"lib/**/*",
"types/**/*",
"tsconfig.json"
],
"author": "Hardcore Engineering Inc",
"license": "EPL-2.0",
"scripts": {
"build": "compile",
"build:watch": "compile",
"format": "format src",
"test": "jest --passWithNoTests --silent",
"_phase:build": "compile transpile src",
"_phase:test": "jest --passWithNoTests --silent",
"_phase:format": "format src",
"_phase:validate": "compile validate"
},
"devDependencies": {
"@hcengineering/platform-rig": "^0.6.0",
"@typescript-eslint/eslint-plugin": "^6.11.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-n": "^15.4.0",
"eslint": "^8.54.0",
"@typescript-eslint/parser": "^6.11.0",
"eslint-config-standard-with-typescript": "^40.0.0",
"prettier": "^3.1.0",
"typescript": "^5.3.3",
"jest": "^29.7.0",
"ts-jest": "^29.1.1",
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/core": "^0.6.32",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/view": "^0.6.13"
},
"repository": "https://github.com/hcengineering/platform",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
+102
View File
@@ -0,0 +1,102 @@
//
// Copyright © 2024 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 { Class, Doc, Ref, Space } from '@hcengineering/core'
import { plugin, IntlString, type Asset, type Plugin, type Resource } from '@hcengineering/platform'
import { Viewlet } from '@hcengineering/view'
import { type AnyComponent, type ResolvedLocation } from '@hcengineering/ui'
import { Poll, Survey } from './types'
export * from './types'
export const surveyId = 'survey' as Plugin
const survey = plugin(surveyId, {
class: {
Poll: '' as Ref<Class<Poll>>,
Survey: '' as Ref<Class<Survey>>
},
icon: {
Application: '' as Asset,
Poll: '' as Asset,
Question: '' as Asset,
QuestionKindString: '' as Asset,
QuestionKindOption: '' as Asset,
QuestionKindOptions: '' as Asset,
QuestionIsMandatory: '' as Asset,
QuestionHasCustomOption: '' as Asset,
Survey: '' as Asset
},
space: {
Survey: '' as Ref<Space>
},
app: {
Survey: '' as Ref<Doc>
},
string: {
Answer: '' as IntlString,
AnswerPlaceholder: '' as IntlString,
AnswerCustomOption: '' as IntlString,
Application: '' as IntlString,
Close: '' as IntlString,
Control: '' as IntlString,
CreatePoll: '' as IntlString,
CreateSurvey: '' as IntlString,
DeleteOption: '' as IntlString,
DeleteQuestion: '' as IntlString,
DeleteQuestionConfirm: '' as IntlString,
Name: '' as IntlString,
NoAnswer: '' as IntlString,
NoName: '' as IntlString,
NoPollsForDocument: '' as IntlString,
Settings: '' as IntlString,
Poll: '' as IntlString,
Polls: '' as IntlString,
Prompt: '' as IntlString,
PromptPlaceholder: '' as IntlString,
Question: '' as IntlString,
Questions: '' as IntlString,
QuestionKind: '' as IntlString,
QuestionKindString: '' as IntlString,
QuestionKindOption: '' as IntlString,
QuestionKindOptions: '' as IntlString,
QuestionIsMandatory: '' as IntlString,
QuestionHasCustomOption: '' as IntlString,
QuestionOptions: '' as IntlString,
QuestionOptionPlaceholder: '' as IntlString,
QuestionPlaceholder: '' as IntlString,
QuestionEmptyPlaceholder: '' as IntlString,
Survey: '' as IntlString,
Surveys: '' as IntlString,
SurveyPreview: '' as IntlString,
SurveySubmit: '' as IntlString
},
component: {
CreateSurvey: '' as AnyComponent,
EditSurveyPanel: '' as AnyComponent,
PollCollection: '' as AnyComponent,
PollPresenter: '' as AnyComponent,
SurveyPresenter: '' as AnyComponent
},
viewlet: {
TableSurvey: '' as Ref<Viewlet>,
TablePoll: '' as Ref<Viewlet>
},
resolver: {
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
}
})
export default survey
+47
View File
@@ -0,0 +1,47 @@
//
// Copyright © 2024 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 { AttachedDoc, Doc, Ref } from '@hcengineering/core'
/** @public */
export interface Survey extends Doc {
name: string
prompt: string
questions?: Question[]
}
/** @public */
export enum QuestionKind {
STRING = 'string',
OPTION = 'option',
OPTIONS = 'options'
}
/** @public */
export interface Question {
name: string
kind: QuestionKind
isMandatory: boolean
options?: string[]
hasCustomOption: boolean
}
/** @public */
export interface Poll extends AttachedDoc {
survey: Ref<Survey>
name: string
prompt: string
results?: { question: string, answer: string[] }[]
}
+10
View File
@@ -0,0 +1,10 @@
{
"extends": "./node_modules/@hcengineering/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"declarationDir": "./types",
"tsBuildInfoFile": ".build/build.tsbuildinfo"
}
}
@@ -0,0 +1,7 @@
module.exports = {
extends: ['./node_modules/@hcengineering/platform-rig/profiles/assets/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}
@@ -0,0 +1,52 @@
<!-- ALL HASHTAGs -->
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
<symbol id="testManagementApplication" viewBox="0 0 24 24">
<path d="M17.2,7.8h3.6c1.1,0,2-0.9,2-2V4.2c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0H9.8V4c0-1.5-1.2-2.8-2.8-2.8H4 C2.5,1.2,1.2,2.5,1.2,4v2c0,1.5,1.2,2.8,2.8,2.8h3c1.5,0,2.8-1.2,2.8-2.8V5.8h2V18c0,1.5,1.2,2.8,2.8,2.8h0.8v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-0.8c-0.7,0-1.2-0.6-1.2-1.2v-4.8h2v0c0,1.1,0.9,2,2,2h3.6 c1.1,0,2-0.9,2-2v-1.6c0-1.1-0.9-2-2-2h-3.6c-1.1,0-2,0.9-2,2v0h-2v-6h2v0C15.2,6.9,16.1,7.8,17.2,7.8z M8.2,6 c0,0.7-0.6,1.2-1.2,1.2H4C3.3,7.2,2.8,6.7,2.8,6V4c0-0.7,0.6-1.2,1.2-1.2h3c0.7,0,1.2,0.6,1.2,1.2V6z M16.8,19.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V19.2z M16.8,11.7 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V11.7z M16.8,4.2 c0-0.2,0.2-0.5,0.5-0.5h3.6c0.2,0,0.5,0.2,0.5,0.5v1.6c0,0.2-0.2,0.5-0.5,0.5h-3.6c-0.2,0-0.5-0.2-0.5-0.5V4.2z" />
</symbol>
<symbol id="testCase" viewBox="0 0 24 24">
<path xmlns:default="http://www.w3.org/2000/svg" d="M3 9H1v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2H3V9zm15-4V3c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H5v11c0 1.11.89 2 2 2h14c1.11 0 2-.89 2-2V5h-5zm-6-2h4v2h-4V3zm0 12V8l5.5 3-5.5 4z" vector-effect="non-scaling-stroke"/>
</symbol>
<symbol id="testRun" viewBox="0 0 20 20" fill="none">
<path d="M10 1.24988C8.26942 1.24988 6.57769 1.76306 5.13876 2.72452C3.69983 3.68598 2.57832 5.05254 1.91606 6.6514C1.25379 8.25025 1.08051 10.0096 1.41813 11.7069C1.75575 13.4043 2.58911 14.9634 3.81282 16.1871C5.03653 17.4108 6.59563 18.2441 8.29296 18.5817C9.9903 18.9194 11.7496 18.7461 13.3485 18.0838C14.9473 17.4216 16.3139 16.3 17.2754 14.8611C18.2368 13.4222 18.75 11.7305 18.75 9.99988C18.75 7.67923 17.8281 5.45364 16.1872 3.81269C14.5462 2.17175 12.3206 1.24988 10 1.24988ZM10 17.4999C8.51664 17.4999 7.0666 17.06 5.83323 16.2359C4.59986 15.4118 3.63856 14.2404 3.07091 12.87C2.50325 11.4996 2.35473 9.99156 2.64411 8.5367C2.9335 7.08184 3.64781 5.74547 4.6967 4.69658C5.7456 3.64768 7.08197 2.93338 8.53683 2.64399C9.99168 2.3546 11.4997 2.50312 12.8701 3.07078C14.2406 3.63844 15.4119 4.59973 16.236 5.8331C17.0601 7.06647 17.5 8.51652 17.5 9.99988C17.5 11.989 16.7098 13.8967 15.3033 15.3032C13.8968 16.7097 11.9891 17.4999 10 17.4999Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1919 7.05809C14.436 7.30217 14.436 7.6979 14.1919 7.94197L9.19194 12.942C8.94786 13.1861 8.55214 13.1861 8.30806 12.942L5.80806 10.442C5.56398 10.1979 5.56398 9.80217 5.80806 9.55809C6.05214 9.31401 6.44786 9.31401 6.69194 9.55809L8.75 11.6161L13.3081 7.05809C13.5521 6.81401 13.9479 6.81401 14.1919 7.05809Z" fill="currentColor"/>
</symbol>
<symbol id="testSuite" viewBox="0 0 16 16">
<path d="M14.5,6.7c0-2,0-3-0.7-3.8C13,2.2,12,2.2,10,2.2H6c-0.2,0-0.4,0-0.6,0l-0.1,0v0c-1.5,0-2.5,0.1-3.1,0.7 C1.5,3.6,1.5,4.7,1.5,6.7v2.7c0,2,0,3,0.7,3.8C3,13.8,4,13.8,6,13.8h4c2,0,3,0,3.8-0.7c0.5-0.5,0.7-1.3,0.7-2.4h0V10 c0-0.1,0-0.3,0-0.4c0-0.1,0-0.2,0-0.3V6.7z M13.1,3.6c0.4,0.4,0.4,1.3,0.4,3.1v0c-0.1-0.1-0.2-0.2-0.3-0.2c-0.6-0.3-1.2-0.3-2.5-0.3 c-0.7,0-1.1,0-1.3-0.1C9.2,6,9,5.9,8.9,5.8C8.7,5.6,8.5,5.3,8.2,4.6L7.6,3.5C7.6,3.3,7.5,3.3,7.4,3.2H10C11.7,3.2,12.6,3.2,13.1,3.6z M10,12.8H6c-1.7,0-2.6,0-3.1-0.4C2.5,12,2.5,11.1,2.5,9.3V6.7c0-1.7,0-2.6,0.4-3.1c0.4-0.4,1.1-0.4,2.5-0.4l0.1,0 c0.5,0,1,0.3,1.2,0.8l0.6,1.1c0.4,0.7,0.6,1.1,0.9,1.4C8.4,6.7,8.7,6.9,9,7c0.4,0.2,0.9,0.2,1.7,0.2c1.1,0,1.7,0,2,0.2 c0.2,0.1,0.4,0.3,0.6,0.6c0.2,0.3,0.2,0.9,0.2,1.9c0,1.4,0,2.2-0.4,2.6C12.6,12.8,11.7,12.8,10,12.8z"/>
</symbol>
<symbol id="home" viewBox="0 0 16 16">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1L13.8838 5.29644C14.271 5.5788 14.5 6.0292 14.5 6.50845V12.5C14.5 13.3284 13.8284 14 13 14H3C2.17157 14 1.5 13.3284 1.5 12.5V6.50845C1.5 6.0292 1.729 5.5788 2.11624 5.29644L8 1Z"/>
</symbol>
<symbol id="red-circle" viewBox="0 0 14 14">
<path d="M7,0C3.1,0,0,3.1,0,7c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C14,3.1,10.9,0,7,0z M7,12c-2.8,0-5-2.2-5-5s2.2-5,5-5s5,2.2,5,5S9.8,12,7,12z" />
</symbol>
<symbol id="status-draft" viewBox="1 1 14 14" fill="#D7D8DB">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 5.36133L8 2.73633L3.5 5.36133L3.5 10.6382L8 13.2632L12.5 10.6382L12.5 5.36133ZM8.75581 1.44066C8.28876 1.16822 7.71124 1.16822 7.24419 1.44066L2.74419 4.06566C2.28337 4.33448 2 4.82783 2 5.36133V10.6382C2 11.1717 2.28337 11.6651 2.74419 11.9339L7.24419 14.5589C7.71124 14.8313 8.28876 14.8313 8.75581 14.5589L13.2558 11.9339C13.7166 11.6651 14 11.1717 14 10.6382V5.36133C14 4.82783 13.7166 4.33448 13.2558 4.06566L8.75581 1.44066Z" />
</symbol>
<symbol id="status-review" viewBox="1 1 14 14" fill="#F2C94C">
<path d="M8.3779 4.74233C8.14438 4.60607 7.85562 4.60607 7.6221 4.74233L5.37209 6.05513C5.14168 6.18957 5 6.4363 5 6.70311V9.34216C5 9.60897 5.14168 9.85573 5.37209 9.99016L7.6221 11.303C7.85562 11.4392 8.14438 11.4392 8.3779 11.303L10.6279 9.99016C10.8583 9.85573 11 9.60897 11 9.34216V6.70311C11 6.4363 10.8583 6.18957 10.6279 6.05513L8.3779 4.74233Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.24419 1.44066C7.71124 1.16822 8.28876 1.16822 8.75581 1.44066L13.2558 4.06566C13.7166 4.33448 14 4.82783 14 5.36133V10.6382C14 11.1717 13.7166 11.6651 13.2558 11.9339L8.75581 14.5589C8.28876 14.8313 7.71124 14.8313 7.24419 14.5589L2.74419 11.9339C2.28337 11.6651 2 11.1717 2 10.6382V5.36133C2 4.82783 2.28337 4.33448 2.74419 4.06566L7.24419 1.44066ZM8 2.73633L12.5 5.36133V10.6382L8 13.2632L3.5 10.6382V5.36133L8 2.73633Z" />
</symbol>
<symbol id="status-review-comments" viewBox="1 1 14 14" fill="#8A8F98">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75581 1.21148C8.28876 0.929507 7.71124 0.929507 7.24419 1.21148L2.74419 3.92829C2.28337 4.20651 2 4.71711 2 5.26927V10.7307C2 11.2829 2.28337 11.7935 2.74419 12.0717L7.24419 14.7885C7.71124 15.0705 8.28876 15.0705 8.75581 14.7885L13.2558 12.0717C13.7166 11.7935 14 11.2829 14 10.7307V5.26927C14 4.71711 13.7166 4.20651 13.2558 3.92829L8.75581 1.21148ZM12.5 5.26928L8 2.55246L3.5 5.26927L3.5 10.7307L8 13.4475L12.5 10.7307L12.5 5.26928Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 5.75C6.91421 5.75 7.25 6.08579 7.25 6.5V9.5C7.25 9.91421 6.91421 10.25 6.5 10.25C6.08579 10.25 5.75 9.91421 5.75 9.5V6.5C5.75 6.08579 6.08579 5.75 6.5 5.75Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.5 5.75C9.91421 5.75 10.25 6.08579 10.25 6.5V9.5C10.25 9.91421 9.91421 10.25 9.5 10.25C9.08579 10.25 8.75 9.91421 8.75 9.5V6.5C8.75 6.08579 9.08579 5.75 9.5 5.75Z" />
</symbol>
<symbol id="status-approved" viewBox="1 1 14 14" fill="#5E6AD2">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 5.125L8 2.5L3.5 5.125L3.5 10.4019L8 13.0269L12.5 10.4019L12.5 5.125ZM8.75581 1.20433C8.28876 0.93189 7.71124 0.931889 7.24419 1.20433L2.74419 3.82933C2.28337 4.09815 2 4.5915 2 5.125V10.4019C2 10.9354 2.28337 11.4287 2.74419 11.6976L7.24419 14.3226C7.71124 14.595 8.28876 14.595 8.75581 14.3226L13.2558 11.6976C13.7166 11.4287 14 10.9354 14 10.4019V5.125C14 4.5915 13.7166 4.09815 13.2558 3.82933L8.75581 1.20433Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7381 5.69424C11.0526 5.96381 11.089 6.43728 10.8194 6.75178L7.81944 10.2518C7.68349 10.4104 7.48754 10.5051 7.27878 10.5131C7.07003 10.5212 6.86739 10.4417 6.71967 10.294L5.21967 8.79402C4.92678 8.50112 4.92678 8.02625 5.21967 7.73336C5.51256 7.44046 5.98744 7.44046 6.28033 7.73336L7.20764 8.66066L9.68056 5.77559C9.95012 5.4611 10.4236 5.42468 10.7381 5.69424Z" />
</symbol>
<symbol id="status-canceled" viewBox="1 1 14 14" fill="#8A8F98">
<path d="M5.96967 5.96967C6.26256 5.67678 6.73744 5.67678 7.03033 5.96967L8 6.93934L8.96967 5.96967C9.26256 5.67678 9.73744 5.67678 10.0303 5.96967C10.3232 6.26256 10.3232 6.73744 10.0303 7.03033L9.06066 8L10.0303 8.96967C10.3232 9.26256 10.3232 9.73744 10.0303 10.0303C9.73744 10.3232 9.26256 10.3232 8.96967 10.0303L8 9.06066L7.03033 10.0303C6.73744 10.3232 6.26256 10.3232 5.96967 10.0303C5.67678 9.73744 5.67678 9.26256 5.96967 8.96967L6.93934 8L5.96967 7.03033C5.67678 6.73744 5.67678 6.26256 5.96967 5.96967Z" />
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75581 1.21148C8.28876 0.929507 7.71124 0.929507 7.24419 1.21148L2.74419 3.92829C2.28337 4.20651 2 4.71711 2 5.26927V10.7307C2 11.2829 2.28337 11.7935 2.74419 12.0717L7.24419 14.7885C7.71124 15.0705 8.28876 15.0705 8.75581 14.7885L13.2558 12.0717C13.7166 11.7935 14 11.2829 14 10.7307V5.26927C14 4.71711 13.7166 4.20651 13.2558 3.92829L8.75581 1.21148ZM12.5 5.26928L8 2.55246L3.5 5.26927L3.5 10.7307L8 13.4475L12.5 10.7307L12.5 5.26928Z" />
</symbol>
<symbol id="document" viewBox="0 0 32 32">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4C7.89543 4 7 4.89543 7 6V26C7 27.1046 7.89543 28 9 28H23C24.1046 28 25 27.1046 25 26V12H21C18.7909 12 17 10.2091 17 8V4H9ZM19 4.41421V8C19 9.10457 19.8954 10 21 10H24.5858L19 4.41421ZM5 6C5 3.79086 6.79086 2 9 2H18.5858C19.1162 2 19.6249 2.21071 20 2.58579L26.4142 9C26.7893 9.37507 27 9.88378 27 10.4142V26C27 28.2091 25.2091 30 23 30H9C6.79086 30 5 28.2091 5 26V6ZM10 17C10 16.4477 10.4477 16 11 16H21C21.5523 16 22 16.4477 22 17C22 17.5523 21.5523 18 21 18H11C10.4477 18 10 17.5523 10 17ZM10 23C10 22.4477 10.4477 22 11 22H21C21.5523 22 22 22.4477 22 23C22 23.5523 21.5523 24 21 24H11C10.4477 24 10 23.5523 10 23Z" />
</symbol>
<symbol id="test-library" viewBox="0 0 24 24">
<path d="M21,2.2h-5h-5c-0.4,0-0.8,0.3-0.8,0.8v18c0,0.4,0.3,0.8,0.8,0.8h5h5c0.4,0,0.8-0.3,0.8-0.8V3C21.8,2.6,21.4,2.2,21,2.2z M11.8,3.8h3.5v16.5h-3.5V3.8z M20.2,20.2h-3.5V3.8h3.5V20.2z"/>
<path d="M9.1,2.8l-4-0.5c-0.2,0-0.4,0-0.6,0.2S4.3,2.7,4.3,2.9l-2,17.5c0,0.4,0.2,0.8,0.7,0.8l4.2,0.5c0.2,0,0.4,0,0.6-0.2C7.9,21.5,8,21.3,8,21.1L9.7,3.6C9.8,3.2,9.5,2.8,9.1,2.8z M6.6,20.2l-2.7-0.3l1.8-16l2.5,0.3L6.6,20.2z"/>
<path d="M18.5,9.8c0.4,0,0.8-0.3,0.8-0.8V7.5c0-0.4-0.3-0.8-0.8-0.8s-0.8,0.3-0.8,0.8V9C17.8,9.4,18.1,9.8,18.5,9.8z"/>
<path d="M13.5,6.8c-0.4,0-0.8,0.3-0.8,0.8V9c0,0.4,0.3,0.8,0.8,0.8s0.8-0.3,0.8-0.8V7.5C14.2,7.1,13.9,6.8,13.5,6.8z"/>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
"rigPackageName": "@hcengineering/platform-rig",
"rigProfile": "assets"
}
@@ -0,0 +1,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'],
roots: ["./src"],
coverageReporters: ["text-summary", "html"]
}
@@ -0,0 +1,66 @@
{
"string": {
"ConfigLabel": "Test Management",
"ConfigDescription": "Extension to manage test cases",
"TestCaseType": "Type",
"TestCasePriority": "Priority",
"TestCaseStatus": "Status",
"TestSuite": "Test Suite",
"SuiteName": "Name",
"SuiteDescription": "Description",
"Suite": "Suite",
"TestName": "Name",
"TestDescription": "Description",
"TestType": "Type",
"TestPriority": "Priority",
"TestStatus": "Status",
"TestEstimatedTime": "Estimated time",
"TestPreconditions": "Preconditions",
"TestSteps": "Steps",
"TestAssignee": "Assignee",
"TestCase": "Test case",
"TestProject": "Project",
"TestManagementApplication": "Test Management",
"AllTestCases": "All test cases",
"AllProjects": "All projects",
"Projects": "Projects",
"CreateProject": "Create project",
"EditProject": "Edit project",
"TestCases": "Test cases",
"TestManagementDescription": "Extension to manage test cases",
"CreateTestCase": "New test case",
"FullDescription": "Description",
"ProjectName": "Name",
"ProjectType": "Type",
"Members": "Members",
"RoleLabel": "Role",
"ProjectMembers": "Project members",
"TestSuites": "Test suites",
"CreateTestSuite": "Create test suite",
"NamePlaceholder": "Suite name",
"DescriptionPlaceholder": "Description (optional)",
"TestRuns": "Test runs",
"NewTestRun": "New test run",
"TestRun": "Test run",
"TestNamePlaceholder": "Test case title",
"ChooseIcon": "Choose icon",
"NoTestSuite": "No test suite",
"StatusDraft": "Draft",
"StatusReview": "Ready for review",
"StatusReviewComments": "Need to fix review comments",
"StatusApproved": "Approved",
"StatusRejected": "Rejected",
"SetStatus": "Set status",
"Assignee": "Assignee",
"Unassigned": "Unassigned",
"AssignTo": "Assign to",
"AssignedTo": "Assigneed to",
"PreviousAssigned": "Previously assigned",
"NoTestCases": "There are no test cases in this test suite",
"CreateTestRun": "Create test run",
"TestRunNamePlaceholder": "Test run name",
"SelectTestSuites": "Select test suites",
"SelectTestCases": "Select test cases",
"TestLibrary": "Test library"
}
}

Some files were not shown because too many files have changed in this diff Show More