From 3d3285d1b433550e71545f1aeee665c19470764d Mon Sep 17 00:00:00 2001 From: Kristina Date: Mon, 26 May 2025 23:31:53 +0400 Subject: [PATCH] Fix notification when Request.requested changed (#9105) Signed-off-by: Kristina Fefelova --- models/controlled-documents/src/types.ts | 4 +- models/request/src/index.ts | 40 +++++++++- models/request/src/plugin.ts | 10 ++- models/server-request/package.json | 3 +- models/server-request/src/index.ts | 19 +++++ .../components/document/EditDocTeam.svelte | 41 +++++++++- .../ActivityInboxNotificationPresenter.svelte | 24 +++++- plugins/request-assets/lang/cs.json | 4 +- plugins/request-assets/lang/de.json | 4 +- plugins/request-assets/lang/en.json | 4 +- plugins/request-assets/lang/es.json | 4 +- plugins/request-assets/lang/fr.json | 4 +- plugins/request-assets/lang/it.json | 4 +- plugins/request-assets/lang/ja.json | 4 +- plugins/request-assets/lang/pt.json | 4 +- plugins/request-assets/lang/ru.json | 4 +- plugins/request-assets/lang/zh.json | 4 +- .../RequestedChangedNotification.svelte | 80 +++++++++++++++++++ plugins/request-resources/src/index.ts | 4 +- server-plugins/request-resources/src/index.ts | 63 ++++++++++++++- server-plugins/request/src/index.ts | 6 +- 21 files changed, 302 insertions(+), 32 deletions(-) create mode 100644 plugins/request-resources/src/components/RequestedChangedNotification.svelte diff --git a/models/controlled-documents/src/types.ts b/models/controlled-documents/src/types.ts index 1efce19690..4294ef30b8 100644 --- a/models/controlled-documents/src/types.ts +++ b/models/controlled-documents/src/types.ts @@ -462,11 +462,11 @@ export class TDocumentComment extends TChatMessage implements DocumentComment { export class TDocumentRequest extends TRequest implements DocumentRequest {} @Model(documents.class.DocumentReviewRequest, documents.class.DocumentRequest) -@UX(documents.string.DocumentReviewRequest) +@UX(documents.string.DocumentReviewRequest, documents.icon.Document) export class TDocumentReviewRequest extends TDocumentRequest implements DocumentReviewRequest {} @Model(documents.class.DocumentApprovalRequest, documents.class.DocumentRequest) -@UX(documents.string.DocumentApprovalRequest) +@UX(documents.string.DocumentApprovalRequest, documents.icon.Document) export class TDocumentApprovalRequest extends TDocumentRequest implements DocumentApprovalRequest {} @Mixin(documents.mixin.DocumentSpaceTypeData, documents.class.DocumentSpace) diff --git a/models/request/src/index.ts b/models/request/src/index.ts index 0f695343af..ddaa1a8e1d 100644 --- a/models/request/src/index.ts +++ b/models/request/src/index.ts @@ -133,24 +133,56 @@ export function createModel (builder: Builder): void { field: 'requested', generated: false, group: request.ids.RequestNotificationGroup, - label: request.string.Request, + label: request.string.NewRequest, allowedForAuthor: true, defaultEnabled: true, templates: { - textTemplate: '{sender} sent you a {doc}', - htmlTemplate: '

{sender} sent you a {doc}

', + textTemplate: '{sender} sent you a request for the {doc}', + htmlTemplate: '

{sender} sent you a request for the {doc}

', subjectTemplate: '{doc}' } }, request.ids.CreateRequestNotification ) + builder.createDoc( + notification.class.NotificationType, + core.space.Model, + { + hidden: false, + objectClass: request.class.Request, + txClasses: [core.class.TxUpdateDoc], + field: 'requested', + generated: false, + group: request.ids.RequestNotificationGroup, + label: request.string.CancelRequest, + allowedForAuthor: true, + defaultEnabled: true, + templates: { + textTemplate: '{sender} canceled the request for the {doc}', + htmlTemplate: '

{sender} canceled the request for the {doc}

', + subjectTemplate: '{doc}' + } + }, + request.ids.RemoveRequestNotification + ) + + builder.createDoc(notification.class.ActivityNotificationViewlet, core.space.Model, { + messageMatch: { + _class: activity.class.DocUpdateMessage, + objectClass: request.class.Request, + action: 'update', + 'attributeUpdates.attrKey': 'requested' + }, + presenter: request.component.RequestedChangedNotification + }) + generateClassNotificationTypes( builder, request.class.Request, request.ids.RequestNotificationGroup, ['requested'], - ['comments', 'approved', 'rejected', 'status'] + ['comments'] ) builder.createDoc(core.class.DomainIndexConfiguration, core.space.Model, { diff --git a/models/request/src/plugin.ts b/models/request/src/plugin.ts index 2726dc8275..9f539a93ef 100644 --- a/models/request/src/plugin.ts +++ b/models/request/src/plugin.ts @@ -24,14 +24,18 @@ import type { NotificationGroup, NotificationType } from '@hcengineering/notific export default mergeIds(requestId, request, { component: { EditRequest: '' as AnyComponent, - NotificationRequestView: '' as AnyComponent + NotificationRequestView: '' as AnyComponent, + RequestedChangedNotification: '' as AnyComponent }, ids: { RequestNotificationGroup: '' as Ref, - CreateRequestNotification: '' as Ref + CreateRequestNotification: '' as Ref, + RemoveRequestNotification: '' as Ref }, string: { Status: '' as IntlString, - Requested: '' as IntlString + Requested: '' as IntlString, + NewRequest: '' as IntlString, + CancelRequest: '' as IntlString } }) diff --git a/models/server-request/package.json b/models/server-request/package.json index 2c2f390b8b..f7cc79d55f 100644 --- a/models/server-request/package.json +++ b/models/server-request/package.json @@ -35,6 +35,7 @@ "@hcengineering/server-request": "^0.6.0", "@hcengineering/server-core": "^0.6.1", "@hcengineering/model-request": "^0.6.0", - "@hcengineering/server-notification": "^0.6.1" + "@hcengineering/server-notification": "^0.6.1", + "@hcengineering/notification": "^0.6.23" } } diff --git a/models/server-request/src/index.ts b/models/server-request/src/index.ts index 57bf3580f8..4dd79fc6cd 100644 --- a/models/server-request/src/index.ts +++ b/models/server-request/src/index.ts @@ -20,6 +20,7 @@ import serverCore from '@hcengineering/server-core' import serverRequest from '@hcengineering/server-request' import serverNotification from '@hcengineering/server-notification' import request from '@hcengineering/model-request' +import notification from '@hcengineering/notification' export { serverRequestId } from '@hcengineering/server-request' @@ -34,4 +35,22 @@ export function createModel (builder: Builder): void { builder.mixin(request.class.Request, core.class.Class, serverNotification.mixin.TextPresenter, { presenter: serverRequest.function.RequestTextPresenter }) + + builder.mixin( + request.ids.CreateRequestNotification, + notification.class.NotificationType, + serverNotification.mixin.TypeMatch, + { + func: serverRequest.function.SendRequestMatch + } + ) + + builder.mixin( + request.ids.RemoveRequestNotification, + notification.class.NotificationType, + serverNotification.mixin.TypeMatch, + { + func: serverRequest.function.RemoveRequestMatch + } + ) } diff --git a/plugins/controlled-documents-resources/src/components/document/EditDocTeam.svelte b/plugins/controlled-documents-resources/src/components/document/EditDocTeam.svelte index 993b93cdec..dc8c32b27f 100644 --- a/plugins/controlled-documents-resources/src/components/document/EditDocTeam.svelte +++ b/plugins/controlled-documents-resources/src/components/document/EditDocTeam.svelte @@ -21,7 +21,7 @@ DocumentReviewRequest, DocumentState } from '@hcengineering/controlled-documents' - import { Ref } from '@hcengineering/core' + import { DocumentUpdate, Ref } from '@hcengineering/core' import { getClient } from '@hcengineering/presentation' import { Scroller } from '@hcengineering/ui' @@ -88,16 +88,51 @@ } const requiredApprovesCount = users.length + const requestedQuery: DocumentUpdate = {} + if (addedPersons.size > 0) { + requestedQuery.$push = { requested: { $each: Array.from(addedPersons), $position: 0 } } + } + if (removedPersons.size > 0) { + requestedQuery.$pull = { requested: { $in: Array.from(removedPersons) } } + } + + if (Object.keys(requestedQuery).length > 0) { + await ops.update(request, requestedQuery) + } await ops.update(request, { - requested: users, approved, approvedDates, requiredApprovesCount }) } - await ops.update(controlledDoc, { [type]: users }) + const added = new Set() + const removed = new Set() + + for (const user of users) { + if (!controlledDoc[type].includes(user as Ref)) { + added.add(user) + } + } + + for (const user of controlledDoc[type]) { + if (!users.includes(user)) { + removed.add(user) + } + } + + const updateQuery: DocumentUpdate = {} + if (added.size > 0) { + updateQuery.$push = { [type]: { $each: Array.from(added), $position: 0 } } + } + if (removed.size > 0) { + updateQuery.$pull = { [type]: { $in: Array.from(removed) } } + } + + if (Object.keys(updateQuery).length > 0) { + await ops.update(controlledDoc, updateQuery) + } await ops.commit() } diff --git a/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte b/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte index 836dc267dc..47bcc9c34a 100644 --- a/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte +++ b/plugins/notification-resources/src/components/inbox/ActivityInboxNotificationPresenter.svelte @@ -25,7 +25,7 @@ combineActivityMessages, sortActivityMessages } from '@hcengineering/activity-resources' - import { ActivityMessage, DisplayActivityMessage } from '@hcengineering/activity' + import activity, { ActivityMessage, DisplayActivityMessage, DocUpdateMessage } from '@hcengineering/activity' import { Action, Component } from '@hcengineering/ui' import { getActions } from '@hcengineering/view-resources' import { getResource } from '@hcengineering/platform' @@ -55,6 +55,24 @@ $: updateViewlet(viewlets, displayMessage) + function matchViewlet (viewlet: ActivityNotificationViewlet, message: DisplayActivityMessage): boolean { + const hierarchy = client.getHierarchy() + const matched = matchQuery([message], viewlet.messageMatch, message._class, hierarchy, true)[0] + if (matched !== undefined) return true + + if (hierarchy.isDerived(message._class, activity.class.DocUpdateMessage)) { + const dum = message as DocUpdateMessage + const dumUpdated: DocUpdateMessage = { + ...dum, + objectClass: hierarchy.getParentClass(dum.objectClass) + } + const matched = matchQuery([dumUpdated], viewlet.messageMatch, message._class, hierarchy, true)[0] + return matched !== undefined + } + + return false + } + function updateViewlet (viewlets: ActivityNotificationViewlet[], message?: DisplayActivityMessage): void { if (viewlets.length === 0 || message === undefined) { viewlet = undefined @@ -62,8 +80,8 @@ } for (const v of viewlets) { - const matched = matchQuery([message], v.messageMatch, message._class, client.getHierarchy(), true) - if (matched.length > 0) { + const matched = matchViewlet(v, message) + if (matched) { viewlet = v return } diff --git a/plugins/request-assets/lang/cs.json b/plugins/request-assets/lang/cs.json index 94a27aa89f..2b5b168520 100644 --- a/plugins/request-assets/lang/cs.json +++ b/plugins/request-assets/lang/cs.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Zadejte prosím zprávu ke komentáři, abyste mohli pokračovat...", "NoRequests": "Žádné žádosti", "Cancel": "Zrušit", - "Cancelled": "Zrušeno" + "Cancelled": "Zrušeno", + "NewRequest": "Nový požadavek", + "CancelRequest": "Zrušit požadavek" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/de.json b/plugins/request-assets/lang/de.json index aa2fe46569..48c3503533 100644 --- a/plugins/request-assets/lang/de.json +++ b/plugins/request-assets/lang/de.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Bitte geben Sie eine Kommentarnachricht ein, um fortzufahren...", "NoRequests": "Keine Anfragen", "Cancel": "Abbrechen", - "Cancelled": "Abgebrochen" + "Cancelled": "Abgebrochen", + "NewRequest": "Neue Anfrage", + "CancelRequest": "Anfrage abbrechen" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/en.json b/plugins/request-assets/lang/en.json index adc89bfc37..3645fc67a6 100644 --- a/plugins/request-assets/lang/en.json +++ b/plugins/request-assets/lang/en.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Please type comment message to continue...", "NoRequests": "No requests", "Cancel": "Cancel", - "Cancelled": "Cancelled" + "Cancelled": "Cancelled", + "NewRequest": "New Request", + "CancelRequest": "Cancel Request" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/es.json b/plugins/request-assets/lang/es.json index 6dc7f496d8..6d4562e7ea 100644 --- a/plugins/request-assets/lang/es.json +++ b/plugins/request-assets/lang/es.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Escriba un mensaje de comentario para continuar...", "NoRequests": "No hay solicitudes", "Cancel": "Cancelar", - "Cancelled": "Cancelado" + "Cancelled": "Cancelado", + "NewRequest": "Nueva solicitud", + "CancelRequest": "Cancelar solicitud" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/fr.json b/plugins/request-assets/lang/fr.json index e33cb3e70d..74c2d31aa9 100644 --- a/plugins/request-assets/lang/fr.json +++ b/plugins/request-assets/lang/fr.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Veuillez taper un message de commentaire pour continuer...", "NoRequests": "Aucune demande", "Cancel": "Annuler", - "Cancelled": "Annulé" + "Cancelled": "Annulé", + "NewRequest": "Nouvelle demande", + "CancelRequest": "Annuler la demande" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/it.json b/plugins/request-assets/lang/it.json index bc4d063b83..d1bc06e342 100644 --- a/plugins/request-assets/lang/it.json +++ b/plugins/request-assets/lang/it.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Per favore, digita un commento per continuare...", "NoRequests": "Nessuna richiesta", "Cancel": "Annulla", - "Cancelled": "Annullato" + "Cancelled": "Annullato", + "NewRequest": "Nuova richiesta", + "CancelRequest": "Annulla richiesta" } } diff --git a/plugins/request-assets/lang/ja.json b/plugins/request-assets/lang/ja.json index d1629d2aef..cf84bbcfd5 100644 --- a/plugins/request-assets/lang/ja.json +++ b/plugins/request-assets/lang/ja.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "コメントを入力して続行...", "NoRequests": "リクエストはありません", "Cancel": "キャンセル", - "Cancelled": "キャンセル済み" + "Cancelled": "キャンセル済み", + "NewRequest": "新しいリクエスト", + "CancelRequest": "リクエストをキャンセル" } } diff --git a/plugins/request-assets/lang/pt.json b/plugins/request-assets/lang/pt.json index 30e45c96b8..891c21ecba 100644 --- a/plugins/request-assets/lang/pt.json +++ b/plugins/request-assets/lang/pt.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Digite uma mensagem de comentário para continuar...", "NoRequests": "Sem solicitações", "Cancel": "Cancelar", - "Cancelled": "Cancelado" + "Cancelled": "Cancelado", + "NewRequest": "Nova solicitação", + "CancelRequest": "Cancelar solicitação" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/ru.json b/plugins/request-assets/lang/ru.json index a0ced6f6cb..d49e785d6f 100644 --- a/plugins/request-assets/lang/ru.json +++ b/plugins/request-assets/lang/ru.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "Пожалуйста, оставьте комментарий, чтобы продолжить...", "NoRequests": "Нет запросов", "Cancel": "Отменить", - "Cancelled": "Отменен" + "Cancelled": "Отменен", + "NewRequest": "Новый запрос", + "CancelRequest": "Отмена запроса" } } \ No newline at end of file diff --git a/plugins/request-assets/lang/zh.json b/plugins/request-assets/lang/zh.json index a6822f649b..c38c3d8c9f 100644 --- a/plugins/request-assets/lang/zh.json +++ b/plugins/request-assets/lang/zh.json @@ -18,6 +18,8 @@ "PleaseTypeMessage": "请键入评论消息以继续...", "NoRequests": "没有请求", "Cancel": "取消", - "Cancelled": "已取消" + "Cancelled": "已取消", + "NewRequest": "新请求", + "CancelRequest": "取消请求" } } diff --git a/plugins/request-resources/src/components/RequestedChangedNotification.svelte b/plugins/request-resources/src/components/RequestedChangedNotification.svelte new file mode 100644 index 0000000000..99e4c45da5 --- /dev/null +++ b/plugins/request-resources/src/components/RequestedChangedNotification.svelte @@ -0,0 +1,80 @@ + + + + + +
+ + + + {#if isAddedMe} +
+
+
+ + diff --git a/plugins/request-resources/src/index.ts b/plugins/request-resources/src/index.ts index c3d29a048d..96cace24e3 100644 --- a/plugins/request-resources/src/index.ts +++ b/plugins/request-resources/src/index.ts @@ -18,6 +18,7 @@ import EditRequest from './components/EditRequest.svelte' import RequestPresenter from './components/RequestPresenter.svelte' import RequestView from './components/RequestView.svelte' import NotificationRequestView from './components/NotificationRequestView.svelte' +import RequestedChangedNotification from './components/RequestedChangedNotification.svelte' export { default as RequestStatusPresenter } from './components/RequestStatusPresenter.svelte' export { default as RequestDetailPopup } from './components/RequestDetailPopup.svelte' @@ -27,6 +28,7 @@ export default async (): Promise => ({ EditRequest, RequestPresenter, RequestView, - NotificationRequestView + NotificationRequestView, + RequestedChangedNotification } }) diff --git a/server-plugins/request-resources/src/index.ts b/server-plugins/request-resources/src/index.ts index 50df2bac83..253d5e36b5 100644 --- a/server-plugins/request-resources/src/index.ts +++ b/server-plugins/request-resources/src/index.ts @@ -14,8 +14,20 @@ // import { DocUpdateMessage } from '@hcengineering/activity' -import core, { Doc, Tx, TxCUD, TxCreateDoc, TxProcessor, TxUpdateDoc, type MeasureContext } from '@hcengineering/core' -import notification from '@hcengineering/notification' +import core, { + Doc, + Tx, + TxCUD, + TxCreateDoc, + TxProcessor, + TxUpdateDoc, + type MeasureContext, + Ref, + PersonId, + AccountUuid, + combineAttributes +} from '@hcengineering/core' +import notification, { NotificationType } from '@hcengineering/notification' import { getResource, translate } from '@hcengineering/platform' import request, { Request, RequestStatus } from '@hcengineering/request' import { pushDocUpdateMessages } from '@hcengineering/server-activity-resources' @@ -28,6 +40,7 @@ import { getSenderInfo, getTextPresenter } from '@hcengineering/server-notification-resources' +import { Person } from '@hcengineering/contact' /** * @public @@ -191,10 +204,54 @@ export async function requestTextPresenter (doc: Doc, control: TriggerControl): return title } +export const sendRequestMatch = ( + tx: TxCreateDoc | TxUpdateDoc, + doc: Doc, + person: Ref, + socialIds: PersonId[], + type: NotificationType, + control: TriggerControl, + account: AccountUuid +): boolean => { + if (tx._class === core.class.TxCreateDoc) { + const createTx = tx as TxCreateDoc + const request = TxProcessor.createDoc2Doc(createTx) + + return request.requested.includes(person) + } else if (tx._class === core.class.TxUpdateDoc) { + const updateTx = tx as TxUpdateDoc + const pushed: Ref[] = combineAttributes([updateTx.operations], 'requested', '$push', '$each') ?? [] + + return pushed.includes(person) + } + + return false +} + +export const removeRequestMatch = ( + tx: TxUpdateDoc, + doc: Doc, + person: Ref, + socialIds: PersonId[], + type: NotificationType, + control: TriggerControl, + account: AccountUuid +): boolean => { + if (tx._class === core.class.TxUpdateDoc) { + const removed: Ref[] = combineAttributes([tx.operations], 'requested', '$pull', '$in') ?? [] + + return removed.includes(person) + } + + return false +} + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ function: { - RequestTextPresenter: requestTextPresenter + RequestTextPresenter: requestTextPresenter, + SendRequestMatch: sendRequestMatch, + RemoveRequestMatch: removeRequestMatch }, trigger: { OnRequest diff --git a/server-plugins/request/src/index.ts b/server-plugins/request/src/index.ts index c575c5b2cc..8be79093a2 100644 --- a/server-plugins/request/src/index.ts +++ b/server-plugins/request/src/index.ts @@ -15,7 +15,7 @@ import { Plugin, plugin, Resource } from '@hcengineering/platform' import type { TriggerFunc } from '@hcengineering/server-core' -import { Presenter } from '@hcengineering/server-notification' +import { Presenter, TypeMatchFunc } from '@hcengineering/server-notification' /** * @public @@ -27,7 +27,9 @@ export const serverRequestId = 'server-request' as Plugin */ export default plugin(serverRequestId, { function: { - RequestTextPresenter: '' as Resource + RequestTextPresenter: '' as Resource, + SendRequestMatch: '' as TypeMatchFunc, + RemoveRequestMatch: '' as TypeMatchFunc }, trigger: { OnRequest: '' as Resource