From 776b657e79c6adb10c32c826ed26ee0e88b573ff Mon Sep 17 00:00:00 2001 From: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> Date: Fri, 11 Mar 2022 15:06:46 +0600 Subject: [PATCH] Auto lastview subscribe (#1130) Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com> --- models/server-inventory/src/index.ts | 9 +++ models/server-notification/src/index.ts | 4 + models/server-task/src/index.ts | 9 +++ .../src/components/LastViewEditor.svelte | 4 +- .../components/NotificationPresenter.svelte | 2 +- plugins/notification-resources/src/utils.ts | 6 +- .../inventory-resources/package.json | 1 + .../inventory-resources/src/index.ts | 66 +++++++++++++++- server-plugins/inventory/src/index.ts | 5 ++ .../notification-resources/package.json | 1 + .../notification-resources/src/index.ts | 76 +++++++++++++++++- server-plugins/notification/package.json | 1 + server-plugins/notification/src/index.ts | 39 +++++++++- server-plugins/task-resources/package.json | 1 + server-plugins/task-resources/src/index.ts | 78 ++++++++++++++++++- server-plugins/task/src/index.ts | 5 ++ 16 files changed, 294 insertions(+), 13 deletions(-) diff --git a/models/server-inventory/src/index.ts b/models/server-inventory/src/index.ts index 70b1f10d65..131c9a526d 100644 --- a/models/server-inventory/src/index.ts +++ b/models/server-inventory/src/index.ts @@ -19,6 +19,7 @@ import core from '@anticrm/core' import inventory from '@anticrm/inventory' import view from '@anticrm/view' import serverInventory from '@anticrm/server-inventory' +import serverCore from '@anticrm/server-core' export function createModel (builder: Builder): void { builder.mixin(inventory.class.Product, core.class.Class, view.mixin.HTMLPresenter, { @@ -28,4 +29,12 @@ export function createModel (builder: Builder): void { builder.mixin(inventory.class.Product, core.class.Class, view.mixin.TextPresenter, { presenter: serverInventory.function.ProductTextPresenter }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverInventory.trigger.OnProductCreate + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverInventory.trigger.OnProductUpdate + }) } diff --git a/models/server-notification/src/index.ts b/models/server-notification/src/index.ts index aceab5c4ce..efafb3c12d 100644 --- a/models/server-notification/src/index.ts +++ b/models/server-notification/src/index.ts @@ -24,4 +24,8 @@ export function createModel (builder: Builder): void { builder.createDoc(serverCore.class.Trigger, core.space.Model, { trigger: serverNotification.trigger.OnBacklinkCreate }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverNotification.trigger.UpdateLastView + }) } diff --git a/models/server-task/src/index.ts b/models/server-task/src/index.ts index 18c2284325..8ae6c635fc 100644 --- a/models/server-task/src/index.ts +++ b/models/server-task/src/index.ts @@ -19,6 +19,7 @@ import core from '@anticrm/core' import task from '@anticrm/task' import view from '@anticrm/view' import serverTask from '@anticrm/server-task' +import serverCore from '@anticrm/server-core' export function createModel (builder: Builder): void { builder.mixin(task.class.Issue, core.class.Class, view.mixin.HTMLPresenter, { @@ -28,4 +29,12 @@ export function createModel (builder: Builder): void { builder.mixin(task.class.Issue, core.class.Class, view.mixin.TextPresenter, { presenter: serverTask.function.IssueTextPresenter }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverTask.trigger.OnTaskCreate + }) + + builder.createDoc(serverCore.class.Trigger, core.space.Model, { + trigger: serverTask.trigger.OnTaskUpdate + }) } diff --git a/plugins/notification-resources/src/components/LastViewEditor.svelte b/plugins/notification-resources/src/components/LastViewEditor.svelte index 34e42a8dae..8709784705 100644 --- a/plugins/notification-resources/src/components/LastViewEditor.svelte +++ b/plugins/notification-resources/src/components/LastViewEditor.svelte @@ -22,8 +22,8 @@ const notificationClient = NotificationClientImpl.getClient() const lastViews = notificationClient.getLastViews() - $: subscribed = $lastViews.has(value._id) - + $: lastView = $lastViews.get(value._id) + $: subscribed = lastView !== undefined && lastView !== -1 {#if !subscribed} diff --git a/plugins/notification-resources/src/components/NotificationPresenter.svelte b/plugins/notification-resources/src/components/NotificationPresenter.svelte index 93883dc338..3cba26a428 100644 --- a/plugins/notification-resources/src/components/NotificationPresenter.svelte +++ b/plugins/notification-resources/src/components/NotificationPresenter.svelte @@ -24,7 +24,7 @@ const lastViews = notificationClient.getLastViews() $: lastView = $lastViews.get(value._id) - $: hasNotification = lastView !== undefined && lastView < value.modifiedOn + $: hasNotification = lastView !== undefined && lastView !== -1 && lastView < value.modifiedOn {#if hasNotification} diff --git a/plugins/notification-resources/src/utils.ts b/plugins/notification-resources/src/utils.ts index 32ca6b4fc5..955480430f 100644 --- a/plugins/notification-resources/src/utils.ts +++ b/plugins/notification-resources/src/utils.ts @@ -63,7 +63,7 @@ export class NotificationClientImpl implements NotificationClient { const user = getCurrentAccount()._id const lastView = time ?? new Date().getTime() const current = this.lastViews.get(_id) - if (current !== undefined) { + if (current !== undefined && current.lastView !== -1) { if (current.lastView < lastView || force) { const u = client.txFactory.createTxUpdateDoc(current._class, current.space, current._id, { lastView: lastView @@ -89,7 +89,9 @@ export class NotificationClientImpl implements NotificationClient { const user = getCurrentAccount()._id const current = await client.findOne(notification.class.LastView, { attachedTo: _id, user }) if (current !== undefined) { - await client.removeDoc(current._class, current.space, current._id) + await client.updateDoc(current._class, current.space, current._id, { + lastView: -1 + }) } } } diff --git a/server-plugins/inventory-resources/package.json b/server-plugins/inventory-resources/package.json index 3adccb24ce..ce0ae78a2e 100644 --- a/server-plugins/inventory-resources/package.json +++ b/server-plugins/inventory-resources/package.json @@ -29,6 +29,7 @@ "@anticrm/core": "~0.6.11", "@anticrm/platform": "~0.6.5", "@anticrm/server-core": "~0.6.0", + "@anticrm/server-notification": "~0.6.0", "@anticrm/inventory": "~0.6.0", "@anticrm/view": "~0.6.0", "@anticrm/login": "~0.6.1", diff --git a/server-plugins/inventory-resources/src/index.ts b/server-plugins/inventory-resources/src/index.ts index f5e54a2563..685d53fac4 100644 --- a/server-plugins/inventory-resources/src/index.ts +++ b/server-plugins/inventory-resources/src/index.ts @@ -13,13 +13,73 @@ // limitations under the License. // -import { Doc } from '@anticrm/core' +import core, { AttachedDoc, Doc, Tx, TxCollectionCUD, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@anticrm/core' import inventory, { Product } from '@anticrm/inventory' import login from '@anticrm/login' import { getMetadata } from '@anticrm/platform' +import { TriggerControl } from '@anticrm/server-core' +import { getUpdateLastViewTx } from '@anticrm/server-notification' import view from '@anticrm/view' import workbench from '@anticrm/workbench' +const extractTx = (tx: Tx): Tx => { + if (tx._class === core.class.TxCollectionCUD) { + const ctx = (tx as TxCollectionCUD) + if (ctx.tx._class === core.class.TxCreateDoc) { + const create = ctx.tx as TxCreateDoc + create.attributes.attachedTo = ctx.objectId + create.attributes.attachedToClass = ctx.objectClass + create.attributes.collection = ctx.collection + return create + } + return ctx + } + + return tx +} + +/** + * @public + */ +export async function OnProductCreate (tx: Tx, control: TriggerControl): Promise { + const actualTx = extractTx(tx) + if (actualTx._class !== core.class.TxCreateDoc) { + return [] + } + + const createTx = actualTx as TxCreateDoc + + if (!control.hierarchy.isDerived(createTx.objectClass, inventory.class.Product)) { + return [] + } + + const doc = TxProcessor.createDoc2Doc(createTx) + + const lastViewTx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, createTx.modifiedOn, createTx.modifiedBy) + + return lastViewTx !== undefined ? [lastViewTx] : [] +} + +/** + * @public + */ +export async function OnProductUpdate (tx: Tx, control: TriggerControl): Promise { + const actualTx = extractTx(tx) + if (actualTx._class !== core.class.TxUpdateDoc) { + return [] + } + + const updateTx = actualTx as TxUpdateDoc + + if (!control.hierarchy.isDerived(updateTx.objectClass, inventory.class.Product)) { + return [] + } + + const lastViewTx = await getUpdateLastViewTx(control.findAll, updateTx.objectId, updateTx.objectClass, updateTx.modifiedOn, updateTx.modifiedBy) + + return lastViewTx !== undefined ? [lastViewTx] : [] +} + /** * @public */ @@ -39,6 +99,10 @@ export function productTextPresenter (doc: Doc): string { // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ + trigger: { + OnProductCreate, + OnProductUpdate + }, function: { ProductHTMLPresenter: productHTMLPresenter, ProductTextPresenter: productTextPresenter diff --git a/server-plugins/inventory/src/index.ts b/server-plugins/inventory/src/index.ts index bad59f6561..d828eb835b 100644 --- a/server-plugins/inventory/src/index.ts +++ b/server-plugins/inventory/src/index.ts @@ -16,6 +16,7 @@ import type { Resource, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import { Doc } from '@anticrm/core' +import { TriggerFunc } from '@anticrm/server-core' /** * @public @@ -26,6 +27,10 @@ export const serverInventoryId = 'server-inventory' as Plugin * @public */ export default plugin(serverInventoryId, { + trigger: { + OnProductCreate: '' as Resource, + OnProductUpdate: '' as Resource + }, function: { ProductHTMLPresenter: '' as Resource<(doc: Doc) => string>, ProductTextPresenter: '' as Resource<(doc: Doc) => string> diff --git a/server-plugins/notification-resources/package.json b/server-plugins/notification-resources/package.json index f443681f41..cb19b4362a 100644 --- a/server-plugins/notification-resources/package.json +++ b/server-plugins/notification-resources/package.json @@ -29,6 +29,7 @@ "@anticrm/core": "~0.6.11", "@anticrm/platform": "~0.6.5", "@anticrm/server-core": "~0.6.0", + "@anticrm/server-notification": "~0.6.0", "@anticrm/notification": "~0.6.0", "@anticrm/chunter": "~0.6.1", "@anticrm/view": "~0.6.0", diff --git a/server-plugins/notification-resources/src/index.ts b/server-plugins/notification-resources/src/index.ts index 39163c028a..e19c514a4b 100644 --- a/server-plugins/notification-resources/src/index.ts +++ b/server-plugins/notification-resources/src/index.ts @@ -16,12 +16,29 @@ import chunter, { Backlink } from '@anticrm/chunter' import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact' -import core, { Class, Data, Doc, generateId, Hierarchy, Obj, Ref, Space, Tx, TxCollectionCUD, TxCreateDoc, TxProcessor } from '@anticrm/core' +import core, { AttachedDoc, Class, Data, Doc, generateId, Hierarchy, Obj, Ref, Space, Tx, TxCollectionCUD, TxCreateDoc, TxCUD, TxProcessor } from '@anticrm/core' import notification, { EmailNotification, Notification, NotificationStatus } from '@anticrm/notification' import { getResource } from '@anticrm/platform' import type { TriggerControl } from '@anticrm/server-core' +import { getUpdateLastViewTx } from '@anticrm/server-notification' import view, { HTMLPresenter, TextPresenter } from '@anticrm/view' +const extractTx = (tx: Tx): Tx => { + if (tx._class === core.class.TxCollectionCUD) { + const ctx = (tx as TxCollectionCUD) + if (ctx.tx._class === core.class.TxCreateDoc) { + const create = ctx.tx as TxCreateDoc + create.attributes.attachedTo = ctx.objectId + create.attributes.attachedToClass = ctx.objectClass + create.attributes.collection = ctx.collection + return create + } + return ctx + } + + return tx +} + /** * @public */ @@ -56,6 +73,60 @@ export async function OnBacklinkCreate (tx: Tx, control: TriggerControl): Promis return result } +/** + * @public + */ +export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise { + const actualTx = extractTx(tx) + if (![core.class.TxUpdateDoc, core.class.TxCreateDoc, core.class.TxMixin].includes(actualTx._class)) { + return [] + } + + const result: Tx[] = [] + + switch (actualTx._class) { + case core.class.TxCreateDoc: { + const createTx = actualTx as TxCreateDoc + if (control.hierarchy.isDerived(createTx.objectClass, core.class.AttachedDoc)) { + const doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc) + const attachedTx = await getUpdateLastViewTx(control.findAll, doc.attachedTo, doc.attachedToClass, createTx.modifiedOn, createTx.modifiedBy) + if (attachedTx !== undefined) { + result.push(attachedTx) + } + } else { + const doc = TxProcessor.createDoc2Doc(createTx) + const tx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, createTx.modifiedOn, createTx.modifiedBy) + if (tx !== undefined) { + result.push(tx) + } + } + break + } + case core.class.TxUpdateDoc: + case core.class.TxMixin: { + const tx = actualTx as TxCUD + const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0] + if (control.hierarchy.isDerived(doc._class, core.class.AttachedDoc)) { + const attachedDoc = doc as AttachedDoc + const attachedTx = await getUpdateLastViewTx(control.findAll, attachedDoc.attachedTo, attachedDoc.attachedToClass, tx.modifiedOn, tx.modifiedBy) + if (attachedTx !== undefined) { + result.push(attachedTx) + } + } else { + const resTx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, tx.modifiedOn, tx.modifiedBy) + if (resTx !== undefined) { + result.push(resTx) + } + } + break + } + default: + break + } + + return result +} + async function getPlatformNotificationTx (ptx: TxCollectionCUD, control: TriggerControl): Promise | undefined> { const attached = (await control.modelDb.findAll(contact.class.EmployeeAccount, { employee: ptx.objectId as Ref @@ -191,6 +262,7 @@ function getTextPresenter (_class: Ref>, hierarchy: Hierarchy): TextP // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ trigger: { - OnBacklinkCreate + OnBacklinkCreate, + UpdateLastView } }) diff --git a/server-plugins/notification/package.json b/server-plugins/notification/package.json index acfc89aad5..80bd73b3de 100644 --- a/server-plugins/notification/package.json +++ b/server-plugins/notification/package.json @@ -29,6 +29,7 @@ "dependencies": { "@anticrm/core": "~0.6.11", "@anticrm/platform": "~0.6.5", + "@anticrm/notification": "~0.6.0", "@anticrm/server-core": "~0.6.0" } } diff --git a/server-plugins/notification/src/index.ts b/server-plugins/notification/src/index.ts index bcba670dd5..c790629212 100644 --- a/server-plugins/notification/src/index.ts +++ b/server-plugins/notification/src/index.ts @@ -14,20 +14,55 @@ // limitations under the License. // +import core, { Account, Class, Doc, Ref, TxCreateDoc, TxFactory, TxUpdateDoc } from '@anticrm/core' import type { Resource, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' -import type { TriggerFunc } from '@anticrm/server-core' +import type { TriggerControl, TriggerFunc } from '@anticrm/server-core' +import notification, { LastView } from '@anticrm/notification' /** * @public */ export const serverNotificationId = 'server-notification' as Plugin +/** + * @public + */ +export async function getUpdateLastViewTx (findAll: TriggerControl['findAll'], attachedTo: Ref, attachedToClass: Ref>, lastView: number, user: Ref): Promise | TxCreateDoc | undefined> { + const current = (await findAll(notification.class.LastView, { + attachedTo, + attachedToClass, + user + }, { limit: 1 }))[0] + const factory = new TxFactory(user) + if (current !== undefined) { + if (current.lastView === -1) { + return + } + const u = factory.createTxUpdateDoc(current._class, current.space, current._id, { + lastView + }) + u.space = core.space.DerivedTx + return u + } else { + const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, { + user, + lastView, + attachedTo, + attachedToClass, + collection: 'lastViews' + }) + u.space = core.space.DerivedTx + return u + } +} + /** * @public */ export default plugin(serverNotificationId, { trigger: { - OnBacklinkCreate: '' as Resource + OnBacklinkCreate: '' as Resource, + UpdateLastView: '' as Resource } }) diff --git a/server-plugins/task-resources/package.json b/server-plugins/task-resources/package.json index 9877fcf1bc..2f5fd2e75f 100644 --- a/server-plugins/task-resources/package.json +++ b/server-plugins/task-resources/package.json @@ -29,6 +29,7 @@ "@anticrm/core": "~0.6.11", "@anticrm/platform": "~0.6.5", "@anticrm/server-core": "~0.6.0", + "@anticrm/server-notification": "~0.6.0", "@anticrm/task": "~0.6.0", "@anticrm/view": "~0.6.0", "@anticrm/login": "~0.6.1", diff --git a/server-plugins/task-resources/src/index.ts b/server-plugins/task-resources/src/index.ts index 2b19bb98a6..4c9be5ec2f 100644 --- a/server-plugins/task-resources/src/index.ts +++ b/server-plugins/task-resources/src/index.ts @@ -13,12 +13,14 @@ // limitations under the License. // -import task, { Issue } from '@anticrm/task' -import { Doc } from '@anticrm/core' +import core, { Doc, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@anticrm/core' import login from '@anticrm/login' import { getMetadata } from '@anticrm/platform' -import workbench from '@anticrm/workbench' +import { TriggerControl } from '@anticrm/server-core' +import { getUpdateLastViewTx } from '@anticrm/server-notification' +import task, { Issue, Task } from '@anticrm/task' import view from '@anticrm/view' +import workbench from '@anticrm/workbench' /** * @public @@ -37,8 +39,78 @@ export function issueTextPresenter (doc: Doc): string { return `Task-${issue.number}` } +/** + * @public + */ +export async function OnTaskCreate (tx: Tx, control: TriggerControl): Promise { + if (tx._class !== core.class.TxCreateDoc) { + return [] + } + + const createTx = tx as TxCreateDoc + + if (!control.hierarchy.isDerived(createTx.objectClass, task.class.Task)) { + return [] + } + + const doc = TxProcessor.createDoc2Doc(createTx) + const txes: Tx[] = [] + + const mainTx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, createTx.modifiedOn, createTx.modifiedBy) + if (mainTx !== undefined) { + txes.push(mainTx) + } + if (doc.assignee != null) { + const assignee = (await control.modelDb.findAll(core.class.Account, { emoloyee: doc.assignee }, { limit: 1 }))[0] + if (assignee !== undefined) { + const assigneeTx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, createTx.modifiedOn, assignee._id) + if (assigneeTx !== undefined) { + txes.push(assigneeTx) + } + } + } + + return txes +} + +/** + * @public + */ +export async function OnTaskUpdate (tx: Tx, control: TriggerControl): Promise { + if (tx._class !== core.class.TxUpdateDoc) { + return [] + } + + const updateTx = tx as TxUpdateDoc + + if (!control.hierarchy.isDerived(updateTx.objectClass, task.class.Task)) { + return [] + } + const txes: Tx[] = [] + + const mainTx = await getUpdateLastViewTx(control.findAll, updateTx.objectId, updateTx.objectClass, updateTx.modifiedOn, updateTx.modifiedBy) + if (mainTx !== undefined) { + txes.push(mainTx) + } + if (updateTx.operations.assignee != null) { + const assignee = (await control.modelDb.findAll(core.class.Account, { emoloyee: updateTx.operations.assignee }, { limit: 1 }))[0] + if (assignee !== undefined) { + const assigneeTx = await getUpdateLastViewTx(control.findAll, updateTx.objectId, updateTx.objectClass, updateTx.modifiedOn, assignee._id) + if (assigneeTx !== undefined) { + txes.push(assigneeTx) + } + } + } + + return txes +} + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type export default async () => ({ + trigger: { + OnTaskCreate, + OnTaskUpdate + }, function: { IssueHTMLPresenter: issueHTMLPresenter, IssueTextPresenter: issueTextPresenter diff --git a/server-plugins/task/src/index.ts b/server-plugins/task/src/index.ts index 0956f9572e..a5a3fcdac8 100644 --- a/server-plugins/task/src/index.ts +++ b/server-plugins/task/src/index.ts @@ -16,6 +16,7 @@ import type { Resource, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import { Doc } from '@anticrm/core' +import { TriggerFunc } from '@anticrm/server-core' /** * @public @@ -26,6 +27,10 @@ export const serverTaskId = 'server-task' as Plugin * @public */ export default plugin(serverTaskId, { + trigger: { + OnTaskCreate: '' as Resource, + OnTaskUpdate: '' as Resource + }, function: { IssueHTMLPresenter: '' as Resource<(doc: Doc) => string>, IssueTextPresenter: '' as Resource<(doc: Doc) => string>