From e089e201f1ee88e3efc08b7c5ee3da5e2aae74c7 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Mon, 22 Sep 2025 22:01:24 +0500 Subject: [PATCH] WhenFieldChanges trigger (#9913) Signed-off-by: Denis Bykhov --- common/config/rush/pnpm-lock.yaml | 163 +++++++++++++++++- models/process/src/index.ts | 22 ++- models/server-process/src/index.ts | 6 +- plugins/process-assets/assets/icons.svg | 5 +- plugins/process-assets/lang/cs.json | 3 +- plugins/process-assets/lang/de.json | 3 +- plugins/process-assets/lang/en.json | 3 +- plugins/process-assets/lang/es.json | 3 +- plugins/process-assets/lang/fr.json | 3 +- plugins/process-assets/lang/it.json | 3 +- plugins/process-assets/lang/ja.json | 3 +- plugins/process-assets/lang/pt.json | 3 +- plugins/process-assets/lang/ru.json | 3 +- plugins/process-assets/lang/zh.json | 3 +- plugins/process-assets/src/index.ts | 3 +- .../settings/FieldChangesEditor.svelte | 98 +++++++++++ plugins/process-resources/src/index.ts | 12 +- plugins/process-resources/src/middleware.ts | 34 ++-- plugins/process-resources/src/plugin.ts | 4 +- plugins/process-resources/src/utils.ts | 28 ++- plugins/process/src/index.ts | 9 +- plugins/process/src/utils.ts | 7 + plugins/view-resources/src/plugin.ts | 1 - plugins/view/src/index.ts | 1 + .../process-resources/src/functions.ts | 19 +- server-plugins/process-resources/src/index.ts | 30 +++- server-plugins/process/src/index.ts | 3 +- services/process/src/main.ts | 39 +++-- 28 files changed, 447 insertions(+), 67 deletions(-) create mode 100644 plugins/process-resources/src/components/settings/FieldChangesEditor.svelte diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index 883c4b6a9f..6da4fa70b1 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -34,6 +34,9 @@ importers: '@faker-js/faker': specifier: ^8.4.1 version: 8.4.1 + '@hcengineering/communication-yaml': + specifier: ^0.1.0 + version: 0.1.195(typescript@5.8.3) '@hocuspocus/provider': specifier: ^2.15.2 version: 2.15.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)(y-protocols@1.0.6(yjs@13.6.23))(yjs@13.6.23) @@ -3054,6 +3057,56 @@ packages: engines: {node: '>=6'} hasBin: true + '@hcengineering/analytics@0.7.242': + resolution: {integrity: sha512-lmNwY7FxXA9zQ4XsszWP0mjWR9fF0JhfUMvKtpJOeDgyGzH+o/ak1snN82s5x3SsuaHSM47mjo56OFQY6T8UFQ==, tarball: https://npm.pkg.github.com/download/@hcengineering/analytics/0.7.242/5c0f56b0659f0592c4369ee839b3882856ffbec4} + + '@hcengineering/card@0.7.242': + resolution: {integrity: sha512-nnoDBBzymcJvwtk10uNy/bhkI12lSyIB4+GU+BmEnO+T/5/8iqk06ZVliop6ksmjlElSRn/QYV9EmZfB+Zsp5w==, tarball: https://npm.pkg.github.com/download/@hcengineering/card/0.7.242/471fe338fb94d1f99fd8a3ffe9b7f910441e48d1} + + '@hcengineering/communication-sdk-types@0.1.195': + resolution: {integrity: sha512-7c3o+qZ+4iJWDnJX6Yq6g62ZaybZO7uJf/jxfaoT0Ee1jtxgrltjXzWAJEK5LVpXzNN6oacVWxe7+W6vlZ6bQg==, tarball: https://npm.pkg.github.com/download/@hcengineering/communication-sdk-types/0.1.195/0adc1fcb52eb9ee69619808acba5e78e9a9aaa40} + peerDependencies: + typescript: ^5.6.3 + + '@hcengineering/communication-shared@0.1.195': + resolution: {integrity: sha512-i0Iq3Kil8IhbW9wS1MnzIjzmbNSP4ycZdsTZ61phKOYpShqDi0WubwMLmRzttnTHMLMTkuw77nXpHDuQCEsIrQ==, tarball: https://npm.pkg.github.com/download/@hcengineering/communication-shared/0.1.195/66db0b6e5c74227f07f791ad7a62d686b83561f2} + peerDependencies: + typescript: ^5.6.3 + + '@hcengineering/communication-types@0.1.195': + resolution: {integrity: sha512-a22nIhEZEGKVeY4pALG4OHROW3E6SKMDslyo1TNI9QR+upca/aX/gZxorc+HzLe3fkhj2BWkrDj3didGBBmJZQ==, tarball: https://npm.pkg.github.com/download/@hcengineering/communication-types/0.1.195/4bace160ae375c32f9c3e0b1814ce73944ec9829} + peerDependencies: + typescript: ^5.6.3 + + '@hcengineering/communication-yaml@0.1.195': + resolution: {integrity: sha512-vJrD8fYMYCOBJciYPV1oOy7z+4r80x4oG4ZFwYc/gMKe2J93z7uZajfBFSHfdhAH0iesIzykr1Ybk2DnL9+7Kw==, tarball: https://npm.pkg.github.com/download/@hcengineering/communication-yaml/0.1.195/d5864f390f1371f13cde0de387e792bef598c429} + peerDependencies: + typescript: ^5.6.3 + + '@hcengineering/core@0.7.242': + resolution: {integrity: sha512-fWxbCbPAyyh84oLIslxS2vdHb8uDcNqNDy1aRZcEarGEN7mdNPNCqok543D5KCqwXD/Wbegi7+Snx425T9U0Vw==, tarball: https://npm.pkg.github.com/download/@hcengineering/core/0.7.242/18e0bb24c985778998b7faef17776049c1e84782} + + '@hcengineering/measurements@0.7.242': + resolution: {integrity: sha512-azd72WdoMdQvpvkvUS7C3uIV9QCqk/wND8IWFfPJdzWA+tkGzsmkWw2jBI+l8M0T2WOerpozGiNxGTFfiC/U/Q==, tarball: https://npm.pkg.github.com/download/@hcengineering/measurements/0.7.242/b48fcc2c171a4282e6a71f7cd040703173d09c4f} + + '@hcengineering/platform@0.7.242': + resolution: {integrity: sha512-IbKw9w2I57IWsLMLo91s8+6gJoMqXwU0v4+0g0PgL0PVf5GNB/TMy2mWwP1FO26TzPa2awmeZm8TImDX1OjtPQ==, tarball: https://npm.pkg.github.com/download/@hcengineering/platform/0.7.242/03295880d68708362268f4a1fff83546aa0ad371} + + '@hcengineering/preference@0.7.242': + resolution: {integrity: sha512-a5YaKxplzaGsc94ywaH1RL2OTirGYhRryCeBpIeUTwRDq+nSTKmnEkPFmxvGj+u47yKyXI+XSdAhpZawRiBhqA==, tarball: https://npm.pkg.github.com/download/@hcengineering/preference/0.7.242/60c05c79abe301623cd1fdf75edc9cbd09a59ed1} + + '@hcengineering/tags@0.7.242': + resolution: {integrity: sha512-MYHE9nb+sKqG4aZkQZimSLm/F7j2nCmTOeq55FRMBqGvFNyJ9iU/+vd7WfQkWMVN+9BdTnVEV4+6IAjgAFD11Q==, tarball: https://npm.pkg.github.com/download/@hcengineering/tags/0.7.242/0ef7f6034822a53e39dcb3f4c7f114636b33f6ae} + + '@hcengineering/theme@0.7.242': + resolution: {integrity: sha512-n5V3xWc7j9UabPm9z7mkt1OYKA8efonTggB0PH0g8FnErukebt1i1csjzeENqE/T7XaT47DqGj/6ZbiyU9vf4A==, tarball: https://npm.pkg.github.com/download/@hcengineering/theme/0.7.242/e78e29b3d0e4a400d90960f7135620087ab35a72} + + '@hcengineering/ui@0.7.242': + resolution: {integrity: sha512-8msNyxXOHUJKWAEao9GzNK0Z46OtTk+y3eFR07r6CYPOYzKjRnVPNZMfeEx81WsKxGMxj7HDxqV1XHEQiHYAAg==, tarball: https://npm.pkg.github.com/download/@hcengineering/ui/0.7.242/cbc28e51c81490fed8ddc8b8d0ce45ab59ff951f} + + '@hcengineering/view@0.7.242': + resolution: {integrity: sha512-qp7mBJ+bKIaH4NiLy34VU1I5jwDia6ezcPtV9SwqK8fl8s+QXWp75KAa7lMVtZZBM07nTLg5qJZNq4oyuIWIow==, tarball: https://npm.pkg.github.com/download/@hcengineering/view/0.7.242/ac007500e9b7b457546cfd4fd66fa3a23a4be3bc} + '@hocuspocus/common@2.15.2': resolution: {integrity: sha512-wU1wxXNnQQMXyeL3mdSDYiQsm/r/QyJVjjQhF7sUBrLnjdsN7bA1cvfcSvJBr1ymrMSeYRmUL3UlQmEHEOaP7w==} @@ -4568,7 +4621,7 @@ packages: version: 0.0.0 '@rush-temp/communication-client-query@file:projects/communication-client-query.tgz': - resolution: {integrity: sha512-+68bXbpIU6XTujL5Dj3pFCdFelV7mE+Ln6zZEvf2NPHhAcJqy3+KjHQyIbytnWhCFsGyk+HE3S08ps6Kl6TKtw==, tarball: file:projects/communication-client-query.tgz} + resolution: {integrity: sha512-6OA/sAql1ZLhhDX9J2yZOAEgfXqSxYh6yxpH/YGrC3PfYyhVAoe7piCjpB+JpKR5wL1kzpl1eP7Bj0IEq2jNJQ==, tarball: file:projects/communication-client-query.tgz} version: 0.0.0 '@rush-temp/communication-cockroach@file:projects/communication-cockroach.tgz': @@ -4576,7 +4629,7 @@ packages: version: 0.0.0 '@rush-temp/communication-query@file:projects/communication-query.tgz': - resolution: {integrity: sha512-KMDSFpLNW1ekFoAf+BbRxPeRYnMUjYu+l80F+fgtvkKsX/6oKtb6xRhQyVh4EQiRlQBaMV//1b2d7cBwuF5HDw==, tarball: file:projects/communication-query.tgz} + resolution: {integrity: sha512-MGyVKHjVsubVphvZfGCiHLqMaXiItQvbP8YxoWOXkDdg9OoUwg8u46f6QJD8j6I9E4LorKNGPxhXU4pJzdgxBQ==, tarball: file:projects/communication-query.tgz} version: 0.0.0 '@rush-temp/communication-resources@file:projects/communication-resources.tgz': @@ -4592,11 +4645,11 @@ packages: version: 0.0.0 '@rush-temp/communication-server@file:projects/communication-server.tgz': - resolution: {integrity: sha512-HfKzdbndaS3opK1inqcTOrVnKvU7PS2asLmKuR4JHooiTTvFFCHcXXYO++t1QmdGmankZRn3Qsq59CIt/UPgsA==, tarball: file:projects/communication-server.tgz} + resolution: {integrity: sha512-Yghkuyu3fA3Thmzo36T1nu62wgMI8GHQUTV7fj0dN12/Mi20JckxRSZDVKRnmRGhNqrzbj/O3DXAM02QpDIeAQ==, tarball: file:projects/communication-server.tgz} version: 0.0.0 '@rush-temp/communication-shared@file:projects/communication-shared.tgz': - resolution: {integrity: sha512-/ZUh5sviGQkQGJnOe6ImJHpKCcn7xNyyMeOcl4Tr3TqXjsH+uOQ0MAFI0Ul4f1LI+dGIrp1QsZWSVWvEVprRQQ==, tarball: file:projects/communication-shared.tgz} + resolution: {integrity: sha512-SustnNpr/eopCXmm0ZahvUZXZPcD7psSWM+CkWVwiPlLVPGWMVUURFOcnC94F3OhY+7qu9N0Rgr3gDvr4VKXWA==, tarball: file:projects/communication-shared.tgz} version: 0.0.0 '@rush-temp/communication-types@file:projects/communication-types.tgz': @@ -4668,7 +4721,7 @@ packages: version: 0.0.0 '@rush-temp/desktop@file:projects/desktop.tgz': - resolution: {integrity: sha512-kMVLNqalurSBKidY00TqK2GIBSaEvgAKScF/32N4vsmx+pzMBkE6U63HiW+RxsqVC/ig9QikeohoDXx7Ps9kHg==, tarball: file:projects/desktop.tgz} + resolution: {integrity: sha512-EIuP2KmLDWhA4J63srKU8MI9wWCUTpNtCPGaS9P4guXJdH55cV8cqbWYbEYuWqsa28YPPoS1hel73H92Mf7nKw==, tarball: file:projects/desktop.tgz} version: 0.0.0 '@rush-temp/devmodel-resources@file:projects/devmodel-resources.tgz': @@ -4960,7 +5013,7 @@ packages: version: 0.0.0 '@rush-temp/model-all@file:projects/model-all.tgz': - resolution: {integrity: sha512-u5b+jP4WxAVIk6DlawxySE+qDG7iz7YD3dTT3LCgR3Ml5RkMOlej4YtbO0YQmX4wp4l4gMJKdRCgBCpVfytksg==, tarball: file:projects/model-all.tgz} + resolution: {integrity: sha512-1IC5c/j2fgqgamtZGZLUBHUuzMaqM6eguG6WTbmdTrSW8YjfUYeutKSRDRykbF6SilzaGS8eKxhNpZTIRT20fA==, tarball: file:projects/model-all.tgz} version: 0.0.0 '@rush-temp/model-analytics-collector@file:projects/model-analytics-collector.tgz': @@ -5532,7 +5585,7 @@ packages: version: 0.0.0 '@rush-temp/prod@file:projects/prod.tgz': - resolution: {integrity: sha512-OvmMqiOyuDeKVGHc7g8UxMlvr9WAJsqtqICsv6kcx9vSvB7bB0iXOLY9CevyOOBDk5Y6OvRKWJKeY9pif+pjMg==, tarball: file:projects/prod.tgz} + resolution: {integrity: sha512-LkECK91CfEPLwDVaCQWN4XSeBlM1jXqboKuXtu7ftZkgG66wtt1/AlVDqwRY6o1iuvVNvuTJBSp03DMfDzBIKw==, tarball: file:projects/prod.tgz} version: 0.0.0 '@rush-temp/products-assets@file:projects/products-assets.tgz': @@ -15591,6 +15644,100 @@ snapshots: protobufjs: 7.5.3 yargs: 17.7.2 + '@hcengineering/analytics@0.7.242': + dependencies: + '@hcengineering/platform': 0.7.242 + + '@hcengineering/card@0.7.242': + dependencies: + '@hcengineering/core': 0.7.242 + '@hcengineering/platform': 0.7.242 + '@hcengineering/preference': 0.7.242 + '@hcengineering/ui': 0.7.242 + '@hcengineering/view': 0.7.242 + + '@hcengineering/communication-sdk-types@0.1.195(typescript@5.8.3)': + dependencies: + '@hcengineering/communication-types': 0.1.195(typescript@5.8.3) + '@hcengineering/core': 0.7.242 + typescript: 5.8.3 + + '@hcengineering/communication-shared@0.1.195(typescript@5.8.3)': + dependencies: + '@hcengineering/communication-sdk-types': 0.1.195(typescript@5.8.3) + '@hcengineering/communication-types': 0.1.195(typescript@5.8.3) + typescript: 5.8.3 + + '@hcengineering/communication-types@0.1.195(typescript@5.8.3)': + dependencies: + '@hcengineering/card': 0.7.242 + '@hcengineering/core': 0.7.242 + '@hcengineering/tags': 0.7.242 + typescript: 5.8.3 + + '@hcengineering/communication-yaml@0.1.195(typescript@5.8.3)': + dependencies: + '@hcengineering/communication-shared': 0.1.195(typescript@5.8.3) + '@hcengineering/communication-types': 0.1.195(typescript@5.8.3) + js-yaml: 4.1.0 + typescript: 5.8.3 + + '@hcengineering/core@0.7.242': + dependencies: + '@hcengineering/analytics': 0.7.242 + '@hcengineering/measurements': 0.7.242 + '@hcengineering/platform': 0.7.242 + fast-equals: 5.2.2 + + '@hcengineering/measurements@0.7.242': {} + + '@hcengineering/platform@0.7.242': + dependencies: + intl-messageformat: 10.7.14 + + '@hcengineering/preference@0.7.242': + dependencies: + '@hcengineering/core': 0.7.242 + '@hcengineering/platform': 0.7.242 + '@hcengineering/ui': 0.7.242 + + '@hcengineering/tags@0.7.242': + dependencies: + '@hcengineering/core': 0.7.242 + '@hcengineering/platform': 0.7.242 + '@hcengineering/ui': 0.7.242 + '@hcengineering/view': 0.7.242 + + '@hcengineering/theme@0.7.242': + dependencies: + '@hcengineering/analytics': 0.7.242 + '@hcengineering/platform': 0.7.242 + svelte: 4.2.20 + + '@hcengineering/ui@0.7.242': + dependencies: + '@hcengineering/analytics': 0.7.242 + '@hcengineering/core': 0.7.242 + '@hcengineering/platform': 0.7.242 + '@hcengineering/theme': 0.7.242 + autolinker: 4.0.0 + blurhash: 2.0.5 + date-fns: 2.30.0 + date-fns-tz: 2.0.0(date-fns@2.30.0) + dompurify: 3.2.3 + emojibase: 16.0.0 + emojibase-regex: 16.0.0 + fast-equals: 5.2.2 + plyr: 3.7.8 + svelte: 4.2.20 + + '@hcengineering/view@0.7.242': + dependencies: + '@hcengineering/core': 0.7.242 + '@hcengineering/platform': 0.7.242 + '@hcengineering/preference': 0.7.242 + '@hcengineering/ui': 0.7.242 + '@hocuspocus/common@2.15.2': dependencies: lib0: 0.2.99 @@ -19085,6 +19232,7 @@ snapshots: '@rush-temp/communication-query@file:projects/communication-query.tgz(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))': dependencies: + '@hcengineering/communication-yaml': 0.1.195(typescript@5.8.3) '@types/uuid': 8.3.4 '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) @@ -19197,6 +19345,7 @@ snapshots: '@rush-temp/communication-server@file:projects/communication-server.tgz(@types/node@22.15.29)(ts-node@10.9.2(@swc/core@1.13.5)(@types/node@22.15.29)(typescript@5.8.3))': dependencies: + '@hcengineering/communication-yaml': 0.1.195(typescript@5.8.3) '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.56.0)(typescript@5.8.3))(eslint@8.56.0)(typescript@5.8.3) '@typescript-eslint/parser': 6.21.0(eslint@8.56.0)(typescript@5.8.3) esbuild: 0.25.9 diff --git a/models/process/src/index.ts b/models/process/src/index.ts index 60c8aa6f78..f724b4e6f3 100644 --- a/models/process/src/index.ts +++ b/models/process/src/index.ts @@ -1127,18 +1127,34 @@ export function createModel (builder: Builder): void { process.class.Trigger, core.space.Model, { - label: process.string.OnCardUpdate, - icon: process.icon.OnCardUpdate, + label: process.string.WhenCardMatches, + icon: process.icon.WhenCardMatches, editor: process.component.CardUpdateEditor, presenter: process.component.CardUpdatePresenter, requiredParams: [], - checkFunction: process.triggerCheck.UpdateCheck, + checkFunction: process.triggerCheck.MatchCheck, init: false, auto: true }, process.trigger.OnCardUpdate ) + builder.createDoc( + process.class.Trigger, + core.space.Model, + { + label: process.string.WhenFieldChanges, + icon: process.icon.WhenFieldChanges, + editor: process.component.FieldChangesEditor, + presenter: process.component.CardUpdatePresenter, + requiredParams: [], + checkFunction: process.triggerCheck.FieldChangedCheck, + init: false, + auto: true + }, + process.trigger.WhenFieldChanges + ) + builder.createDoc( process.class.Trigger, core.space.Model, diff --git a/models/server-process/src/index.ts b/models/server-process/src/index.ts index 9cc3d90c7b..70aee3d5db 100644 --- a/models/server-process/src/index.ts +++ b/models/server-process/src/index.ts @@ -61,7 +61,11 @@ export function createModel (builder: Builder): void { }) builder.mixin(process.trigger.OnCardUpdate, process.class.Trigger, serverProcess.mixin.TriggerImpl, { - serverCheckFunc: serverProcess.func.OnCardUpdateCheck + serverCheckFunc: serverProcess.func.MatchCardCheck + }) + + builder.mixin(process.trigger.WhenFieldChanges, process.class.Trigger, serverProcess.mixin.TriggerImpl, { + serverCheckFunc: serverProcess.func.FieldChangedCheck }) builder.mixin(process.trigger.OnTime, process.class.Trigger, serverProcess.mixin.TriggerImpl, { diff --git a/plugins/process-assets/assets/icons.svg b/plugins/process-assets/assets/icons.svg index 15617a3586..07b4a5fed0 100644 --- a/plugins/process-assets/assets/icons.svg +++ b/plugins/process-assets/assets/icons.svg @@ -26,7 +26,7 @@ d="M27.71 4.29049C27.575 4.15616 27.4045 4.06316 27.2185 4.02241C27.0325 3.98167 26.8388 3.99488 26.66 4.06049L4.66 12.0605C4.47027 12.1325 4.30692 12.2604 4.19165 12.4274C4.07638 12.5944 4.01465 12.7926 4.01465 12.9955C4.01465 13.1984 4.07638 13.3965 4.19165 13.5635C4.30692 13.7305 4.47027 13.8585 4.66 13.9305L14.26 17.7705L18.1 27.3705C18.1721 27.5519 18.2958 27.7082 18.4557 27.8201C18.6157 27.9321 18.8049 27.9948 19 28.0005C19.2021 27.9963 19.3982 27.9311 19.5624 27.8132C19.7266 27.6954 19.8513 27.5306 19.92 27.3405L27.92 5.34049C27.9881 5.16356 28.0046 4.97092 27.9674 4.78501C27.9302 4.5991 27.8409 4.4276 27.71 4.29049ZM19 24.2005L16.21 17.2005L20.295 13.1155C20.6844 12.7261 20.6844 12.0948 20.295 11.7055C19.9056 11.3161 19.2744 11.3161 18.885 11.7055L14.76 15.8305L7.8 13.0005L25.33 6.67049L19 24.2005Z" /> - + + + + diff --git a/plugins/process-assets/lang/cs.json b/plugins/process-assets/lang/cs.json index 92917001fd..4c24bf3fd7 100644 --- a/plugins/process-assets/lang/cs.json +++ b/plugins/process-assets/lang/cs.json @@ -91,7 +91,8 @@ "Started": "Spuštěno", "Each": "Každý", "CreateCard": "Vytvořit kartu", - "OnCardUpdate": "Při aktualizaci karty", + "WhenCardMatches": "Když karta odpovídá", + "WhenFieldChanges": "Když se pole změní", "Insert": "Vložit", "Remove": "Odstranit", "RemoveFirst": "Odstranit první", diff --git a/plugins/process-assets/lang/de.json b/plugins/process-assets/lang/de.json index fac87eaebc..136a8c61ff 100644 --- a/plugins/process-assets/lang/de.json +++ b/plugins/process-assets/lang/de.json @@ -91,7 +91,8 @@ "Started": "Gestartet", "Each": "Jeder", "CreateCard": "Karte erstellen", - "OnCardUpdate": "Beim Aktualisieren der Karte", + "WhenCardMatches": "Wenn Karte übereinstimmt", + "WhenFieldChanges": "Wenn sich das Feld ändert", "Insert": "Einfügen", "Remove": "Entfernen", "RemoveFirst": "Erstes entfernen", diff --git a/plugins/process-assets/lang/en.json b/plugins/process-assets/lang/en.json index c2f5ad72e7..328cd7d202 100644 --- a/plugins/process-assets/lang/en.json +++ b/plugins/process-assets/lang/en.json @@ -91,7 +91,8 @@ "Started": "Started", "Each": "Each", "CreateCard": "Create card", - "OnCardUpdate": "On card update", + "WhenCardMatches": "When card matches", + "WhenFieldChanges": "When field changes", "ArraySizeEquals": "Size equals", "ArraySizeGt": "Size greater than", "ArraySizeGte": "Size greater than or equal", diff --git a/plugins/process-assets/lang/es.json b/plugins/process-assets/lang/es.json index 4962e4004b..2da96eca9c 100644 --- a/plugins/process-assets/lang/es.json +++ b/plugins/process-assets/lang/es.json @@ -95,7 +95,8 @@ "Started": "Iniciado", "Each": "Cada", "CreateCard": "Crear tarjeta", - "OnCardUpdate": "Al actualizar la tarjeta", + "WhenCardMatches": "Cuando la tarjeta coincide", + "WhenFieldChanges": "Cuando el campo cambia", "ArraySizeEquals": "Tamaño igual a", "ArraySizeGt": "Tamaño mayor que", "ArraySizeGte": "Tamaño mayor o igual que", diff --git a/plugins/process-assets/lang/fr.json b/plugins/process-assets/lang/fr.json index 83b1210b08..2276289572 100644 --- a/plugins/process-assets/lang/fr.json +++ b/plugins/process-assets/lang/fr.json @@ -95,7 +95,8 @@ "Started": "Démarré", "Each": "Chaque", "CreateCard": "Créer une carte", - "OnCardUpdate": "À la modification de la carte", + "WhenCardMatches": "Lorsque la carte correspond", + "WhenFieldChanges": "Lorsque le champ change", "ArraySizeEquals": "Taille égale à", "ArraySizeGt": "Taille supérieure à", "ArraySizeGte": "Taille supérieure ou égale à", diff --git a/plugins/process-assets/lang/it.json b/plugins/process-assets/lang/it.json index e201ddc834..e5c7560e46 100644 --- a/plugins/process-assets/lang/it.json +++ b/plugins/process-assets/lang/it.json @@ -95,7 +95,8 @@ "Started": "Avviato", "Each": "Ogni", "CreateCard": "Crea scheda", - "OnCardUpdate": "Alla modifica della scheda", + "WhenCardMatches": "Quando la scheda corrisponde", + "WhenFieldChanges": "Quando il campo cambia", "ArraySizeEquals": "Dimensione uguale a", "ArraySizeGt": "Dimensione maggiore di", "ArraySizeGte": "Dimensione maggiore o uguale a", diff --git a/plugins/process-assets/lang/ja.json b/plugins/process-assets/lang/ja.json index 043a067d19..397d51a264 100644 --- a/plugins/process-assets/lang/ja.json +++ b/plugins/process-assets/lang/ja.json @@ -94,7 +94,8 @@ "Started": "開始", "Each": "各", "CreateCard": "カードを作成", - "OnCardUpdate": "カード更新時", + "WhenCardMatches": "カードが一致したとき", + "WhenFieldChanges": "フィールドが変更されたとき", "ArraySizeEquals": "サイズが等しい", "ArraySizeGt": "サイズがより大きい", "ArraySizeGte": "サイズが以上", diff --git a/plugins/process-assets/lang/pt.json b/plugins/process-assets/lang/pt.json index 7780c84e9e..950078aa1a 100644 --- a/plugins/process-assets/lang/pt.json +++ b/plugins/process-assets/lang/pt.json @@ -95,7 +95,8 @@ "Started": "Iniciado", "Each": "Cada", "CreateCard": "Criar Cartão", - "OnCardUpdate": "Ao atualizar o cartão", + "WhenCardMatches": "Quando o cartão corresponde", + "WhenFieldChanges": "Quando o campo muda", "ArraySizeEquals": "Tamanho igual a", "ArraySizeGt": "Tamanho maior que", "ArraySizeGte": "Tamanho maior ou igual a", diff --git a/plugins/process-assets/lang/ru.json b/plugins/process-assets/lang/ru.json index b66b4009ba..2a05db16b7 100644 --- a/plugins/process-assets/lang/ru.json +++ b/plugins/process-assets/lang/ru.json @@ -95,7 +95,8 @@ "Started": "Начат", "Each": "Каждый", "CreateCard": "Создать карточку", - "OnCardUpdate": "При обновлении карточки", + "WhenCardMatches": "Когда карточка соответствует", + "WhenFieldChanges": "Когда поле изменено", "ArraySizeEquals": "Количество равно", "ArraySizeGt": "Количество больше", "ArraySizeGte": "Количество больше или равно", diff --git a/plugins/process-assets/lang/zh.json b/plugins/process-assets/lang/zh.json index 1f47435704..68d835c559 100644 --- a/plugins/process-assets/lang/zh.json +++ b/plugins/process-assets/lang/zh.json @@ -95,7 +95,8 @@ "Started": "已启动", "Each": "每个", "CreateCard": "创建卡片", - "OnCardUpdate": "在卡片更新时", + "WhenCardMatches": "当卡片匹配时", + "WhenFieldChanges": "当字段更改时", "ArraySizeEquals": "大小等于", "ArraySizeGt": "大小大于", "ArraySizeGte": "大小大于等于", diff --git a/plugins/process-assets/src/index.ts b/plugins/process-assets/src/index.ts index 8d427d56e1..c68b4f147c 100644 --- a/plugins/process-assets/src/index.ts +++ b/plugins/process-assets/src/index.ts @@ -25,6 +25,7 @@ loadMetadata(process.icon, { ToDoRemove: `${icons}#todo-remove`, WaitSubprocesses: `${icons}#subprocesses`, Start: `${icons}#start`, - OnCardUpdate: `${icons}#card-update`, + WhenCardMatches: `${icons}#card-matches`, + WhenFieldChanges: `${icons}#field-change`, Time: `${icons}#time` }) diff --git a/plugins/process-resources/src/components/settings/FieldChangesEditor.svelte b/plugins/process-resources/src/components/settings/FieldChangesEditor.svelte new file mode 100644 index 0000000000..5bcc6ddffb --- /dev/null +++ b/plugins/process-resources/src/components/settings/FieldChangesEditor.svelte @@ -0,0 +1,98 @@ + + + + +{#if key !== undefined} + +{:else} +
+
+{/if} diff --git a/plugins/process-resources/src/index.ts b/plugins/process-resources/src/index.ts index a34cd7b3e2..f8550b3d6f 100644 --- a/plugins/process-resources/src/index.ts +++ b/plugins/process-resources/src/index.ts @@ -77,9 +77,11 @@ import { showDoneQuery, timeTransitionCheck, todoTranstionCheck, - updateCardTranstionCheck, - subProcessesDoneCheck + matchCardCheck, + subProcessesDoneCheck, + fieldChangesCheck } from './utils' +import FieldChangesEditor from './components/settings/FieldChangesEditor.svelte' export * from './query' @@ -132,7 +134,8 @@ export default async (): Promise => ({ TimePresenter, AddTagEditor, AddTagPresenter, - ExecutionMyToDos + ExecutionMyToDos, + FieldChangesEditor }, criteriaEditor: { BaseCriteria, @@ -154,7 +157,8 @@ export default async (): Promise => ({ CutEditor }, triggerCheck: { - UpdateCheck: updateCardTranstionCheck, + MatchCheck: matchCardCheck, + FieldChangedCheck: fieldChangesCheck, SubProcessesDoneCheck: subProcessesDoneCheck, ToDo: todoTranstionCheck, Time: timeTransitionCheck diff --git a/plugins/process-resources/src/middleware.ts b/plugins/process-resources/src/middleware.ts index 950f003f7c..9f252302ca 100644 --- a/plugins/process-resources/src/middleware.ts +++ b/plugins/process-resources/src/middleware.ts @@ -21,10 +21,11 @@ import core, { type TxApplyIf, type TxResult, type TxUpdateDoc, - TxProcessor + TxProcessor, + SortingOrder } from '@hcengineering/core' import { BasePresentationMiddleware, type PresentationMiddleware } from '@hcengineering/presentation' -import process, { ExecutionStatus, type ProcessToDo } from '@hcengineering/process' +import process, { ExecutionStatus, type ProcessToDo, isUpdateTx } from '@hcengineering/process' import { createExecution, getNextStateUserInput, requestResult, pickTransition } from './utils' import cardPlugin, { type Card } from '@hcengineering/card' @@ -68,8 +69,8 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre } private async handleCardUpdate (etx: Tx): Promise { - if (etx._class === core.class.TxUpdateDoc) { - const updateTx = etx as TxUpdateDoc + if (etx._class === core.class.TxUpdateDoc || etx._class === core.class.TxMixin) { + const updateTx = etx as TxUpdateDoc | TxMixin const hierarchy = this.client.getHierarchy() if (!hierarchy.isDerived(updateTx.objectClass, cardPlugin.class.Card)) return const executions = await this.client.findAll(process.class.Execution, { @@ -81,14 +82,23 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre _id: updateTx.objectId }) if (card === undefined) return - const updated = TxProcessor.updateDoc2Doc(hierarchy.clone(card), updateTx) + const updated = isUpdateTx(updateTx) + ? TxProcessor.updateDoc2Doc(hierarchy.clone(card), updateTx) + : TxProcessor.updateMixin4Doc(hierarchy.clone(card), updateTx) for (const execution of executions) { - const transitions = this.client.getModel().findAllSync(process.class.Transition, { - process: execution.process, - from: execution.currentState, - trigger: process.trigger.OnCardUpdate + const transitions = this.client.getModel().findAllSync( + process.class.Transition, + { + process: execution.process, + from: execution.currentState, + trigger: { $in: [process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges] } + }, + { sort: { rank: SortingOrder.Ascending } } + ) + const transition = await pickTransition(this.client, execution, transitions, { + card: updated, + operations: isUpdateTx(updateTx) ? updateTx.operations : updateTx.attributes }) - const transition = await pickTransition(this.client, execution, transitions, updated) if (transition === undefined) return const context = await getNextStateUserInput(execution, transition, execution.context) const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId) @@ -153,7 +163,9 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre from: execution.currentState, trigger: process.trigger.OnToDoClose }) - const transition = await pickTransition(this.client, execution, transitions, todo) + const transition = await pickTransition(this.client, execution, transitions, { + todo + }) if (transition === undefined) return const context = await getNextStateUserInput(execution, transition, execution.context) if (context !== undefined) { diff --git a/plugins/process-resources/src/plugin.ts b/plugins/process-resources/src/plugin.ts index 91d29bad7f..d952560115 100644 --- a/plugins/process-resources/src/plugin.ts +++ b/plugins/process-resources/src/plugin.ts @@ -65,6 +65,7 @@ export default mergeIds(processId, process, { AddRelationPresenter: '' as AnyComponent, CardUpdateEditor: '' as AnyComponent, CardUpdatePresenter: '' as AnyComponent, + FieldChangesEditor: '' as AnyComponent, ToDoSettingPresenter: '' as AnyComponent, TimeEditor: '' as AnyComponent, TimePresenter: '' as AnyComponent, @@ -161,7 +162,8 @@ export default mergeIds(processId, process, { OnToDoDone: '' as IntlString, OnSubProcessesDone: '' as IntlString, WaitUntil: '' as IntlString, - OnCardUpdate: '' as IntlString, + WhenCardMatches: '' as IntlString, + WhenFieldChanges: '' as IntlString, Result: '' as IntlString, RequestResult: '' as IntlString, NoResultRequired: '' as IntlString, diff --git a/plugins/process-resources/src/utils.ts b/plugins/process-resources/src/utils.ts index cbea58cc6d..34ba430598 100644 --- a/plugins/process-resources/src/utils.ts +++ b/plugins/process-resources/src/utils.ts @@ -113,7 +113,7 @@ export async function pickTransition ( client: Client, execution: Execution, transitions: Transition[], - doc: Doc + context: Record ): Promise { for (const tr of transitions) { const trigger = client.getModel().findObject(tr.trigger) @@ -122,7 +122,7 @@ export async function pickTransition ( const filled = fillParams(tr.triggerParams, execution) const checkFunc = await getResource(trigger.checkFunction) if (checkFunc === undefined) continue - const res = await checkFunc(client, execution, filled, doc) + const res = await checkFunc(client, execution, filled, context) if (res) return tr } } @@ -600,10 +600,10 @@ export function todoTranstionCheck ( client: Client, execution: Execution, params: Record, - doc: Doc + context: Record ): boolean { if (params._id === undefined) return false - return doc._id === params._id + return context.card?._id === params._id } export function timeTransitionCheck ( @@ -616,12 +616,28 @@ export function timeTransitionCheck ( return params.value <= Date.now() } -export function updateCardTranstionCheck ( +export function matchCardCheck ( client: Client, execution: Execution, params: Record, - doc: Doc + context: Record ): boolean { + const doc = context.card + if (doc === undefined) return false + const res = matchQuery([doc], params, doc._class, client.getHierarchy(), true) + return res.length > 0 +} + +export function fieldChangesCheck ( + client: Client, + execution: Execution, + params: Record, + context: Record +): boolean { + const doc = context.card + if (doc === undefined) return false + const param = Object.keys(params)[0] + if (!Object.keys(context.operations ?? {}).includes(param)) return false const res = matchQuery([doc], params, doc._class, client.getHierarchy(), true) return res.length > 0 } diff --git a/plugins/process/src/index.ts b/plugins/process/src/index.ts index 93018c79a5..842f87e31c 100644 --- a/plugins/process/src/index.ts +++ b/plugins/process/src/index.ts @@ -217,7 +217,8 @@ export default plugin(processId, { AddTag: '' as Ref> }, trigger: { - OnCardUpdate: '' as Ref, + OnCardUpdate: '' as Ref, // in fact WhenCardMatches, should migrate in future + WhenFieldChanges: '' as Ref, OnSubProcessesDone: '' as Ref, OnToDoClose: '' as Ref, OnToDoRemove: '' as Ref, @@ -227,7 +228,8 @@ export default plugin(processId, { }, triggerCheck: { ToDo: '' as Resource, - UpdateCheck: '' as Resource, + MatchCheck: '' as Resource, + FieldChangedCheck: '' as Resource, SubProcessesDoneCheck: '' as Resource, Time: '' as Resource }, @@ -263,7 +265,8 @@ export default plugin(processId, { Steps: '' as Asset, States: '' as Asset, ToDo: '' as Asset, - OnCardUpdate: '' as Asset, + WhenCardMatches: '' as Asset, + WhenFieldChanges: '' as Asset, WaitSubprocesses: '' as Asset, ToDoRemove: '' as Asset, Start: '' as Asset, diff --git a/plugins/process/src/utils.ts b/plugins/process/src/utils.ts index ddf16a8253..d025963a3a 100644 --- a/plugins/process/src/utils.ts +++ b/plugins/process/src/utils.ts @@ -1,4 +1,7 @@ +import { Tx, TxUpdateDoc } from '@hcengineering/core/types/tx' import { SelectedContext } from './types' +import core from '@hcengineering/core' +import { Card } from '@hcengineering/card' export function parseContext (value: any): SelectedContext | undefined { if (value === undefined) return @@ -14,3 +17,7 @@ export function parseContext (value: any): SelectedContext | undefined { return res } catch {} } + +export function isUpdateTx (etx: Tx): etx is TxUpdateDoc { + return etx._class === core.class.TxUpdateDoc +} diff --git a/plugins/view-resources/src/plugin.ts b/plugins/view-resources/src/plugin.ts index de2be89165..e35d4df94c 100644 --- a/plugins/view-resources/src/plugin.ts +++ b/plugins/view-resources/src/plugin.ts @@ -74,7 +74,6 @@ export default mergeIds(viewId, view, { MatchCriteria: '' as IntlString, DontMatchCriteria: '' as IntlString, MarkupEditor: '' as IntlString, - Select: '' as IntlString, Grouping: '' as IntlString, Ordering: '' as IntlString, Manual: '' as IntlString, diff --git a/plugins/view/src/index.ts b/plugins/view/src/index.ts index fd75567059..4706644685 100644 --- a/plugins/view/src/index.ts +++ b/plugins/view/src/index.ts @@ -248,6 +248,7 @@ const view = plugin(viewId, { PermissionWarningTitle: '' as IntlString, PermissionWarningMessage: '' as IntlString, Icon: '' as IntlString, + Select: '' as IntlString, Color: '' as IntlString, AutomationOnly: '' as IntlString }, diff --git a/server-plugins/process-resources/src/functions.ts b/server-plugins/process-resources/src/functions.ts index e1f57df9dc..a6657bfbcf 100644 --- a/server-plugins/process-resources/src/functions.ts +++ b/server-plugins/process-resources/src/functions.ts @@ -81,7 +81,7 @@ export async function CheckSubProcessesDone (control: ProcessControl, execution: return res === undefined } -export function OnCardUpdateCheck ( +export function MatchCardCheck ( control: ProcessControl, execution: Execution, params: Record, @@ -94,6 +94,23 @@ export function OnCardUpdateCheck ( return res.length > 0 } +export function FieldChangedCheck ( + control: ProcessControl, + execution: Execution, + params: Record, + context: Record +): boolean { + if (context.card === undefined) return false + const process = control.client.getModel().findObject(execution.process) + if (process === undefined) return false + if (context.operations === undefined) return false + const changedFields = Object.keys(context.operations) + const target = Object.keys(params)[0] + if (!changedFields.includes(target)) return false + const res = matchQuery([context.card], params, process.masterTag, control.client.getHierarchy(), true) + return res.length > 0 +} + export function CheckTime (control: ProcessControl, execution: Execution, params: Record): boolean { if (params.value === undefined) return false return params.value <= Date.now() diff --git a/server-plugins/process-resources/src/index.ts b/server-plugins/process-resources/src/index.ts index d49186811b..8f739328d0 100644 --- a/server-plugins/process-resources/src/index.ts +++ b/server-plugins/process-resources/src/index.ts @@ -21,6 +21,7 @@ import core, { Tx, TxCreateDoc, TxCUD, + TxMixin, TxProcessor, TxRemoveDoc, TxUpdateDoc @@ -36,7 +37,8 @@ import process, { SelectedExecutionContext, State, Step, - Transition + Transition, + isUpdateTx } from '@hcengineering/process' import { QueueTopic, TriggerControl } from '@hcengineering/server-core' import { ProcessMessage } from '@hcengineering/server-process' @@ -83,9 +85,10 @@ import { AddTag, CheckToDoDone, CheckToDoCancelled, - OnCardUpdateCheck, + MatchCardCheck, CheckSubProcessesDone, - CheckTime + CheckTime, + FieldChangedCheck } from './functions' import { ToDoCancellRollback, ToDoCloseRollback } from './rollback' @@ -258,16 +261,30 @@ export async function OnCardUpdate (txes: Tx[], control: TriggerControl): Promis const res: Tx[] = [] for (const tx of txes) { if (!control.hierarchy.isDerived(tx._class, core.class.TxCUD)) continue - const cudTx = tx as TxCUD + if (tx._class !== core.class.TxUpdateDoc && tx._class !== core.class.TxMixin) continue + const cudTx = tx as TxUpdateDoc | TxMixin if (!control.hierarchy.isDerived(cudTx.objectClass, cardPlugin.class.Card)) continue const card = await control.findAll(control.ctx, cardPlugin.class.Card, { _id: cudTx.objectId }, { limit: 1 }) if (card.length === 0) continue + const ops = isUpdateTx(cudTx) ? cudTx.operations : cudTx.mixin await putEventToQueue( { event: process.trigger.OnCardUpdate, card: cudTx.objectId, context: { - card: card[0] + card: card[0], + operations: ops ?? {} + } + }, + control + ) + await putEventToQueue( + { + event: process.trigger.WhenFieldChanges, + card: cudTx.objectId, + context: { + card: card[0], + operations: ops ?? {} } }, control @@ -366,7 +383,8 @@ export default async () => ({ AddTag, CheckToDoDone, CheckToDoCancelled, - OnCardUpdateCheck, + FieldChangedCheck, + MatchCardCheck, CheckSubProcessesDone, CheckTime }, diff --git a/server-plugins/process/src/index.ts b/server-plugins/process/src/index.ts index 812719cd91..554d8d1e13 100644 --- a/server-plugins/process/src/index.ts +++ b/server-plugins/process/src/index.ts @@ -56,7 +56,8 @@ export default plugin(serverProcessId, { AddTag: '' as Resource, CheckToDoDone: '' as Resource, CheckToDoCancelled: '' as Resource, - OnCardUpdateCheck: '' as Resource, + MatchCardCheck: '' as Resource, + FieldChangedCheck: '' as Resource, CheckSubProcessesDone: '' as Resource, CheckTime: '' as Resource }, diff --git a/services/process/src/main.ts b/services/process/src/main.ts index 7db1d3b659..ffc25f4a68 100644 --- a/services/process/src/main.ts +++ b/services/process/src/main.ts @@ -22,6 +22,7 @@ import core, { Ref, SortingOrder, Tx, + TxMixin, TxProcessor, TxUpdateDoc, WorkspaceUuid @@ -32,6 +33,7 @@ import process, { ExecutionError, ExecutionLogAction, ExecutionStatus, + isUpdateTx, MethodParams, parseContext, parseError, @@ -138,7 +140,7 @@ async function processCardExecutions (control: ProcessControl, record: ProcessMe } // we should update timers, probably context value changed - if (record.event === process.trigger.OnCardUpdate) { + if ([process.trigger.OnCardUpdate, process.trigger.WhenFieldChanges].includes(record.event)) { await updateTimers(control, record) } } @@ -376,6 +378,7 @@ async function executeTransition ( control.cache.set(execution.card, card) } } + const context: Record = {} for (const action of transition.actions) { const actionResult = await executeAction(action, transition._id, execution, control) if (isError(actionResult)) { @@ -386,13 +389,24 @@ async function executeTransition ( } res.push(...actionResult.txes) for (const tx of actionResult.txes) { - const updateTx = tx as TxUpdateDoc - if (updateTx._class === core.class.TxUpdateDoc && updateTx.objectId === execution.card) { - const updatedCard = TxProcessor.updateDoc2Doc( - control.client.getHierarchy().clone(control.cache.get(execution.card)), - updateTx - ) - control.cache.set(execution.card, updatedCard) + const updateTx = tx as TxUpdateDoc | TxMixin + if (updateTx.objectId === execution.card) { + if (isUpdateTx(updateTx)) { + const updatedCard = TxProcessor.updateDoc2Doc( + control.client.getHierarchy().clone(control.cache.get(execution.card)), + updateTx + ) + context.operations = { ...context.operations, ...updateTx.operations } + control.cache.set(execution.card, updatedCard) + } else if (updateTx._class === core.class.TxMixin) { + const mixinTx = updateTx as TxMixin + const updatedCard = TxProcessor.updateMixin4Doc( + control.client.getHierarchy().clone(control.cache.get(execution.card)), + mixinTx + ) + context.operations = { ...context.operations, ...mixinTx.attributes } + control.cache.set(execution.card, updatedCard) + } } } } @@ -429,7 +443,7 @@ async function executeTransition ( await checkParent(execution, control) } TxProcessor.applyUpdate(execution, executionUpdate) - transition = await checkNext(control, execution) + transition = await checkNext(control, execution, context) if (transition === undefined) { await setNextTimers(control, execution) } @@ -642,7 +656,11 @@ async function checkParent (execution: Execution, control: ProcessControl): Prom } } -async function checkNext (control: ProcessControl, execution: Execution): Promise { +async function checkNext ( + control: ProcessControl, + execution: Execution, + context: Record +): Promise { const autoTriggers = control.client.getModel().findAllSync(process.class.Trigger, { auto: true }) const transitions = control.client.getModel().findAllSync( process.class.Transition, @@ -658,6 +676,7 @@ async function checkNext (control: ProcessControl, execution: Execution): Promis if (doc !== undefined) { control.cache.set(execution.card, doc) const transition = await pickTransition(control, execution, transitions, { + ...context, card: doc }) return transition