Tracker notification (#2057)

Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
Denis Bykhov 2022-06-10 23:59:09 +06:00 committed by GitHub
parent b471284c53
commit 476558421b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 252 additions and 224 deletions

View File

@ -5,6 +5,7 @@
Tracker:
- Issue preview
- Enabled issue notifications
## 0.6.25

View File

@ -32,6 +32,7 @@
"@anticrm/model-core": "~0.6.0",
"@anticrm/ui": "~0.6.0",
"@anticrm/platform": "~0.6.6",
"@anticrm/notification": "~0.6.0",
"@anticrm/inventory": "~0.6.0",
"@anticrm/inventory-resources": "~0.6.0",
"@anticrm/view": "~0.6.0",

View File

@ -20,9 +20,10 @@ import attachment from '@anticrm/model-attachment'
import core, { TAttachedDoc } from '@anticrm/model-core'
import { createAction } from '@anticrm/model-view'
import workbench from '@anticrm/model-workbench'
import notification from '@anticrm/notification'
import setting from '@anticrm/setting'
import type {} from '@anticrm/view'
import view from '@anticrm/view'
import setting from '@anticrm/setting'
import inventory from './plugin'
export const DOMAIN_INVENTORY = 'inventory' as Domain
@ -149,6 +150,8 @@ export function createModel (builder: Builder): void {
inventory.category.Inventory
)
builder.mixin(inventory.class.Product, core.class.Class, notification.mixin.LastViewAttached, {})
createAction(builder, {
label: inventory.string.CreateSubcategory,
icon: inventory.icon.Categories,

View File

@ -18,18 +18,20 @@ import { Account, Doc, Domain, DOMAIN_MODEL, Ref, Timestamp, TxCUD } from '@anti
import { ArrOf, Builder, Mixin, Model, Prop, TypeRef, TypeString, TypeTimestamp } from '@anticrm/model'
import core, { TAttachedDoc, TClass, TDoc } from '@anticrm/model-core'
import type {
AnotherUserNotifications,
EmailNotification,
LastView,
NotificationType,
LastViewAttached,
Notification,
NotificationProvider,
NotificationSetting,
Notification,
NotificationStatus,
NotificationType,
SpaceLastEdit
} from '@anticrm/notification'
import type { IntlString } from '@anticrm/platform'
import notification from './plugin'
import setting from '@anticrm/setting'
import notification from './plugin'
export const DOMAIN_NOTIFICATION = 'notification' as Domain
@ -95,6 +97,14 @@ export class TSpaceLastEdit extends TClass implements SpaceLastEdit {
lastEditField!: string
}
@Mixin(notification.mixin.AnotherUserNotifications, core.class.Class)
export class TAnotherUserNotifications extends TClass implements AnotherUserNotifications {
fields!: string[]
}
@Mixin(notification.mixin.LastViewAttached, core.class.Class)
export class TLastViewAttached extends TClass implements LastViewAttached {}
export function createModel (builder: Builder): void {
builder.createModel(
TLastView,
@ -103,7 +113,9 @@ export function createModel (builder: Builder): void {
TNotificationType,
TNotificationProvider,
TNotificationSetting,
TSpaceLastEdit
TSpaceLastEdit,
TAnotherUserNotifications,
TLastViewAttached
)
builder.createDoc(

View File

@ -17,9 +17,8 @@ import { Builder } from '@anticrm/model'
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'
import view from '@anticrm/view'
export function createModel (builder: Builder): void {
builder.mixin(inventory.class.Product, core.class.Class, view.mixin.HTMLPresenter, {
@ -29,12 +28,4 @@ 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
})
}

View File

@ -16,10 +16,9 @@
import { Builder } from '@anticrm/model'
import core from '@anticrm/core'
import serverTask from '@anticrm/server-task'
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, {
@ -29,12 +28,4 @@ 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
})
}

View File

@ -35,6 +35,7 @@
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/task": "~0.6.0",
"@anticrm/task-resources": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",

View File

@ -36,6 +36,7 @@ import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
import view, { actionTemplates as viewTemplates, createAction, template } from '@anticrm/model-view'
import notification from '@anticrm/notification'
import { IntlString } from '@anticrm/platform'
import tags from '@anticrm/tags'
import {
@ -375,6 +376,11 @@ export function createModel (builder: Builder): void {
editor: task.component.TaskHeader
})
builder.mixin(task.class.Task, core.class.Class, notification.mixin.LastViewAttached, {})
builder.mixin(task.class.Task, core.class.Class, notification.mixin.AnotherUserNotifications, {
fields: ['assignee']
})
builder.createDoc(view.class.Viewlet, core.space.Model, {
attachTo: task.class.Issue,
descriptor: task.viewlet.Kanban,

View File

@ -35,6 +35,7 @@
"@anticrm/model-workbench": "~0.6.1",
"@anticrm/model-contact": "~0.6.1",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/notification": "~0.6.0",
"@anticrm/tracker": "~0.6.0",
"@anticrm/tracker-resources": "~0.6.0",
"@anticrm/model-chunter": "~0.6.0",

View File

@ -35,8 +35,8 @@ import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace, TType } from '@anticrm/model-core'
import view, { createAction } from '@anticrm/model-view'
import { KeyBinding } from '@anticrm/view'
import workbench, { createNavigateAction } from '@anticrm/model-workbench'
import notification from '@anticrm/notification'
import { Asset, IntlString } from '@anticrm/platform'
import setting from '@anticrm/setting'
import {
@ -49,6 +49,7 @@ import {
ProjectStatus,
Team
} from '@anticrm/tracker'
import { KeyBinding } from '@anticrm/view'
import tracker from './plugin'
import presentation from '@anticrm/model-presentation'
@ -403,6 +404,11 @@ export function createModel (builder: Builder): void {
editor: tracker.component.ProjectStatusEditor
})
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.LastViewAttached, {})
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.AnotherUserNotifications, {
fields: ['assignee']
})
builder.createDoc(
workbench.class.Application,
core.space.Model,

View File

@ -462,6 +462,38 @@
}
}
/* List */
.antiList-cells {
display: flex;
align-items: center;
white-space: nowrap;
&__checkCell, &__notifyCell {
display: flex;
justify-content: center;
align-items: center;
}
&__checkCell { visibility: hidden; }
}
.antiList__row {
.antiList-cells__notifyCell {
z-index: 1;
}
&:hover, &.checking {
.antiList-cells__checkCell { visibility: visible; }
.antiList-cells__notifyCell .notify-table-kind {
width: 1.15rem;
height: 1.15rem;
background-color: var(--highlight-hover);
border: 1px solid currentColor;
border-radius: .375rem;
}
}
}
/* Select */
.antiSelect {
display: flex;

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { Doc } from '@anticrm/core'
import { Button, Tooltip } from '@anticrm/ui'
import { Button, tooltip } from '@anticrm/ui'
import notification from '../plugin'
import { NotificationClientImpl } from '../utils'
@ -26,14 +26,14 @@
$: subscribed = lastView !== undefined && lastView !== -1
</script>
<Tooltip label={subscribed ? notification.string.DontTrack : notification.string.Track}>
<div use:tooltip={{ label: subscribed ? notification.string.DontTrack : notification.string.Track }}>
<Button
size={'medium'}
kind={'transparent'}
icon={subscribed ? notification.icon.DontTrack : notification.icon.Track}
icon={subscribed ? notification.icon.Track : notification.icon.DontTrack}
on:click={() => {
if (subscribed) notificationClient.unsubscribe(value._id)
else notificationClient.updateLastView(value._id, value._class, undefined, true)
}}
/>
</Tooltip>
</div>

View File

@ -86,6 +86,18 @@ export interface SpaceLastEdit extends Class<Doc> {
lastEditField: string
}
/**
* @public
*/
export interface LastViewAttached extends Class<AttachedDoc> {}
/**
* @public
*/
export interface AnotherUserNotifications extends Class<Doc> {
fields: string[]
}
/**
* @public
*/
@ -109,7 +121,9 @@ export type NotificationClientFactoy = () => NotificationClient
*/
const notification = plugin(notificationId, {
mixin: {
SpaceLastEdit: '' as Ref<Mixin<SpaceLastEdit>>
SpaceLastEdit: '' as Ref<Mixin<SpaceLastEdit>>,
AnotherUserNotifications: '' as Ref<Mixin<AnotherUserNotifications>>,
LastViewAttached: '' as Ref<Mixin<LastViewAttached>>
},
class: {
LastView: '' as Ref<Class<LastView>>,

View File

@ -18,12 +18,13 @@
import { Kanban, TypeState } from '@anticrm/kanban'
import { createQuery } from '@anticrm/presentation'
import type { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { Button, Icon, IconAdd, showPopup, Tooltip, showPanel } from '@anticrm/ui'
import { Button, Icon, IconAdd, showPopup, Tooltip, showPanel, Component } from '@anticrm/ui'
import { focusStore, ListSelectionProvider, SelectDirection, selectionStore } from '@anticrm/view-resources'
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
import { onMount } from 'svelte'
import tracker from '../../plugin'
import notification from '@anticrm/notification'
import CreateIssue from '../CreateIssue.svelte'
import AssigneePresenter from './AssigneePresenter.svelte'
import IssuePresenter from './IssuePresenter.svelte'
@ -34,8 +35,6 @@
export let currentSpace: Ref<Team>
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
/* eslint-disable no-undef */
const spaceQuery = createQuery()
const statusesQuery = createQuery()
@ -64,10 +63,6 @@
}
)
/* eslint-disable prefer-const */
/* eslint-disable no-unused-vars */
let issue: Issue
function toIssue (object: any): WithLookup<Issue> {
return object as WithLookup<Issue>
}
@ -175,6 +170,9 @@
{currentSpace}
isEditable={true}
/>
<div class="flex-center mt-2">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
</div>
<div class="buttons-group xxsmall-gap mt-10px">
{#if issue && issueStatuses && issue.subIssues > 0}

View File

@ -17,13 +17,24 @@
import { Class, Doc, FindOptions, getObjectValue, Ref, WithLookup } from '@anticrm/core'
import { getClient } from '@anticrm/presentation'
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
import { Button, CheckBox, Component, eventToHTMLElement, IconAdd, showPopup, Spinner, Tooltip } from '@anticrm/ui'
import {
Button,
CheckBox,
Component,
eventToHTMLElement,
IconAdd,
showPopup,
Spinner,
tooltip,
Tooltip
} from '@anticrm/ui'
import { AttributeModel, BuildModelKey } from '@anticrm/view'
import { buildModel, getObjectPresenter, LoadingProps, Menu } from '@anticrm/view-resources'
import { createEventDispatcher } from 'svelte'
import tracker from '../../plugin'
import { IssuesGroupByKeys, issuesGroupEditorMap, IssuesOrderByKeys, issuesSortOrderMap } from '../../utils'
import CreateIssue from '../CreateIssue.svelte'
import notification from '@anticrm/notification'
export let _class: Ref<Class<Doc>>
export let currentSpace: Ref<Team> | undefined = undefined
@ -181,8 +192,8 @@
{#each groupedIssues[category] as docObject (docObject._id)}
<div
bind:this={objectRefs[combinedGroupedIssues.findIndex((x) => x === docObject)]}
class="listGrid"
class:mListGridChecked={selectedObjectIdsSet.has(docObject._id)}
class="listGrid antiList__row"
class:checking={selectedObjectIdsSet.has(docObject._id)}
class:mListGridFixed={selectedRowIndex === combinedGroupedIssues.findIndex((x) => x === docObject)}
class:mListGridSelected={selectedRowIndex === combinedGroupedIssues.findIndex((x) => x === docObject)}
on:contextmenu|preventDefault={(event) =>
@ -195,26 +206,33 @@
on:mouseover={() => handleRowFocused(docObject)}
>
<div class="contentWrapper">
<div
class="flex-center relative"
use:tooltip={{ label: tracker.string.SelectIssue, direction: 'bottom' }}
>
<div class="antiList-cells__notifyCell">
<div class="antiList-cells__checkCell">
<CheckBox
checked={selectedObjectIdsSet.has(docObject._id)}
on:value={(event) => {
onObjectChecked([docObject], event.detail)
}}
/>
</div>
<Component
is={notification.component.NotificationPresenter}
props={{ value: docObject, kind: 'table' }}
/>
</div>
</div>
{#each itemModels as attributeModel, attributeModelIndex}
{#if attributeModelIndex === 0}
<div class="gridElement">
<Tooltip direction={'bottom'} label={tracker.string.SelectIssue}>
<div class="eListGridCheckBox">
<CheckBox
checked={selectedObjectIdsSet.has(docObject._id)}
on:value={(event) => {
onObjectChecked([docObject], event.detail)
}}
/>
</div>
</Tooltip>
<div class="priorityPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
<div class="priorityPresenter">
<svelte:component
this={attributeModel.presenter}
value={getObjectValue(attributeModel.key, docObject) ?? ''}
{...attributeModel.props}
/>
</div>
{:else if attributeModelIndex === 1}
<div class="issuePresenter">
@ -299,28 +317,13 @@
color: var(--theme-caption-color);
border-bottom: 1px solid var(--theme-button-border-hovered);
&.mListGridChecked {
&.checking {
background-color: var(--theme-table-bg-hover);
.eListGridCheckBox {
opacity: 1;
}
}
&.mListGridSelected {
background-color: var(--menu-bg-select);
}
.eListGridCheckBox {
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
&:hover {
opacity: 1;
}
}
}
.filler {

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { Class, Data, Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { Class, Data, Doc, Ref, SortingOrder, WithLookup } from '@anticrm/core'
import { AttachmentDocList } from '@anticrm/attachment-resources'
import { Panel } from '@anticrm/panel'
import presentation, { createQuery, getClient, MessageViewer } from '@anticrm/presentation'
@ -33,17 +33,21 @@
} from '@anticrm/ui'
import { ContextMenu } from '@anticrm/view-resources'
import { StyledTextArea } from '@anticrm/text-editor'
import { createEventDispatcher, onMount } from 'svelte'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import tracker from '../../../plugin'
import { getIssueId } from '../../../utils'
import ControlPanel from './ControlPanel.svelte'
import CopyToClipboard from './CopyToClipboard.svelte'
import SubIssueSelector from './SubIssueSelector.svelte'
import SubIssues from './SubIssues.svelte'
import { getResource } from '@anticrm/platform'
import notification from '@anticrm/notification'
export let _id: Ref<Issue>
export let _class: Ref<Class<Issue>>
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const query = createQuery()
const statusesQuery = createQuery()
const dispatch = createEventDispatcher()
@ -57,6 +61,23 @@
let innerWidth: number
let isEditing = false
const notificationClient = getResource(notification.function.GetNotificationClient).then((res) => res())
$: read(_id)
function read (_id: Ref<Doc>) {
if (lastId !== _id) {
const prev = lastId
const prevClass = lastClass
lastId = _id
lastClass = _class
notificationClient.then((client) => client.updateLastView(prev, prevClass))
}
}
onDestroy(async () => {
notificationClient.then((client) => client.updateLastView(_id, _class))
})
$: _id &&
_class &&
query.query(

View File

@ -13,69 +13,13 @@
// limitations under the License.
//
import core, { Doc, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@anticrm/core'
import { Doc } from '@anticrm/core'
import inventory, { Product } from '@anticrm/inventory'
import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform'
import { extractTx, TriggerControl } from '@anticrm/server-core'
import { getUpdateLastViewTx } from '@anticrm/server-notification'
import view from '@anticrm/view'
import workbench from '@anticrm/workbench'
/**
* @public
*/
export async function OnProductCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = extractTx(tx)
if (actualTx._class !== core.class.TxCreateDoc) {
return []
}
const createTx = actualTx as TxCreateDoc<Product>
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<Tx[]> {
const actualTx = extractTx(tx)
if (actualTx._class !== core.class.TxUpdateDoc) {
return []
}
const updateTx = actualTx as TxUpdateDoc<Product>
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
*/
@ -95,10 +39,6 @@ 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

View File

@ -13,10 +13,9 @@
// limitations under the License.
//
import type { Resource, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { Doc } from '@anticrm/core'
import { TriggerFunc } from '@anticrm/server-core'
import type { Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
@ -27,10 +26,6 @@ export const serverInventoryId = 'server-inventory' as Plugin
* @public
*/
export default plugin(serverInventoryId, {
trigger: {
OnProductCreate: '' as Resource<TriggerFunc>,
OnProductUpdate: '' as Resource<TriggerFunc>
},
function: {
ProductHTMLPresenter: '' as Resource<(doc: Doc) => string>,
ProductTextPresenter: '' as Resource<(doc: Doc) => string>

View File

@ -17,6 +17,7 @@
import chunter, { Backlink } from '@anticrm/chunter'
import contact, { Employee, EmployeeAccount, formatName } from '@anticrm/contact'
import core, {
Account,
AttachedDoc,
Class,
Data,
@ -26,6 +27,7 @@ import core, {
Obj,
Ref,
Space,
Timestamp,
Tx,
TxCollectionCUD,
TxCreateDoc,
@ -36,7 +38,7 @@ import notification, { EmailNotification, Notification, NotificationStatus } fro
import { getResource } from '@anticrm/platform'
import type { TriggerControl } from '@anticrm/server-core'
import { extractTx } from '@anticrm/server-core'
import { getUpdateLastViewTx } from '@anticrm/server-notification'
import { createLastViewTx, getUpdateLastViewTx } from '@anticrm/server-notification'
import view, { HTMLPresenter, TextPresenter } from '@anticrm/view'
/**
@ -73,6 +75,42 @@ export async function OnBacklinkCreate (tx: Tx, control: TriggerControl): Promis
return result
}
async function getUpdateLastViewTxes (
doc: Doc,
_id: Ref<Doc>,
_class: Ref<Class<Doc>>,
modifiedOn: Timestamp,
user: Ref<Account>,
control: TriggerControl
): Promise<Tx[]> {
const updatedUsers: Set<Ref<Account>> = new Set<Ref<Account>>()
const result: Tx[] = []
const tx = await getUpdateLastViewTx(control.findAll, _id, _class, modifiedOn, user)
if (tx !== undefined) {
updatedUsers.add(user)
result.push(tx)
}
const docClass = control.hierarchy.getClass(doc._class)
const anotherUserNotifications = control.hierarchy.as(docClass, notification.mixin.AnotherUserNotifications)
for (const field of anotherUserNotifications?.fields ?? []) {
const value = (doc as any)[field]
if (value != null) {
for (const employeeId of Array.isArray(value) ? value : [value]) {
const account = (await control.modelDb.findAll(core.class.Account, { employee: employeeId }, { limit: 1 }))[0]
if (account !== undefined) {
if (updatedUsers.has(account._id)) continue
const assigneeTx = await createLastViewTx(control.findAll, _id, _class, account._id)
if (assigneeTx !== undefined) {
updatedUsers.add(account._id)
result.push(assigneeTx)
}
}
}
}
}
return result
}
/**
* @public
*/
@ -92,40 +130,36 @@ export async function UpdateLastView (tx: Tx, control: TriggerControl): Promise<
}
if (control.hierarchy.isDerived(createTx.objectClass, core.class.AttachedDoc)) {
const doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<AttachedDoc>)
const attachedTx = await getUpdateLastViewTx(
control.findAll,
const attachedTxes = await getUpdateLastViewTxes(
doc,
doc.attachedTo,
doc.attachedToClass,
createTx.modifiedOn,
createTx.modifiedBy
createTx.modifiedBy,
control
)
if (attachedTx !== undefined) {
result.push(attachedTx)
}
} else {
const doc = TxProcessor.createDoc2Doc(createTx)
const tx = await getUpdateLastViewTx(
control.findAll,
const docClass = control.hierarchy.getClass(doc._class)
if (!control.hierarchy.hasMixin(docClass, notification.mixin.LastViewAttached)) return attachedTxes
const parentTxes = await getUpdateLastViewTxes(
doc,
doc._id,
doc._class,
createTx.modifiedOn,
createTx.modifiedBy
createTx.modifiedBy,
control
)
if (tx !== undefined) {
result.push(tx)
}
return [...attachedTxes, ...parentTxes]
} else {
const doc = TxProcessor.createDoc2Doc(createTx)
return await getUpdateLastViewTxes(doc, doc._id, doc._class, createTx.modifiedOn, createTx.modifiedBy, control)
}
break
}
case core.class.TxUpdateDoc:
case core.class.TxMixin: {
const tx = actualTx as TxCUD<Doc>
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
if (doc !== undefined) {
const resTx = await getUpdateLastViewTx(control.findAll, doc._id, doc._class, tx.modifiedOn, tx.modifiedBy)
if (resTx !== undefined) {
result.push(resTx)
}
return await getUpdateLastViewTxes(doc, doc._id, doc._class, tx.modifiedOn, tx.modifiedBy, control)
}
break
}

View File

@ -69,6 +69,40 @@ export async function getUpdateLastViewTx (
}
}
/**
* @public
*/
export async function createLastViewTx (
findAll: TriggerControl['findAll'],
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
user: Ref<Account>
): Promise<TxCreateDoc<LastView> | undefined> {
const current = (
await findAll(
notification.class.LastView,
{
attachedTo,
attachedToClass,
user
},
{ limit: 1 }
)
)[0]
if (current === undefined) {
const factory = new TxFactory(user)
const u = factory.createTxCreateDoc(notification.class.LastView, notification.space.Notifications, {
user,
lastView: 1,
attachedTo,
attachedToClass,
collection: 'lastViews'
})
u.space = core.space.DerivedTx
return u
}
}
/**
* @public
*/

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import core, { Doc, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@anticrm/core'
import core, { Doc, Tx, TxUpdateDoc } from '@anticrm/core'
import login from '@anticrm/login'
import { getMetadata } from '@anticrm/platform'
import { extractTx, TriggerControl } from '@anticrm/server-core'
@ -39,53 +39,6 @@ export function issueTextPresenter (doc: Doc): string {
return `Task-${issue.number}`
}
/**
* @public
*/
export async function OnTaskCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = extractTx(tx)
if (actualTx._class !== core.class.TxCreateDoc) {
return []
}
const createTx = actualTx as TxCreateDoc<Task>
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
*/
@ -135,10 +88,6 @@ export async function OnTaskUpdate (tx: Tx, control: TriggerControl): Promise<Tx
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnTaskCreate,
OnTaskUpdate
},
function: {
IssueHTMLPresenter: issueHTMLPresenter,
IssueTextPresenter: issueTextPresenter

View File

@ -13,10 +13,9 @@
// limitations under the License.
//
import type { Resource, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import { Doc } from '@anticrm/core'
import { TriggerFunc } from '@anticrm/server-core'
import type { Plugin, Resource } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
/**
* @public
@ -27,10 +26,6 @@ export const serverTaskId = 'server-task' as Plugin
* @public
*/
export default plugin(serverTaskId, {
trigger: {
OnTaskCreate: '' as Resource<TriggerFunc>,
OnTaskUpdate: '' as Resource<TriggerFunc>
},
function: {
IssueHTMLPresenter: '' as Resource<(doc: Doc) => string>,
IssueTextPresenter: '' as Resource<(doc: Doc) => string>