mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 00:43:59 +03:00
Tracker notification (#2057)
Signed-off-by: Denis Bykhov <80476319+BykhovDenis@users.noreply.github.com>
This commit is contained in:
parent
b471284c53
commit
476558421b
@ -5,6 +5,7 @@
|
||||
Tracker:
|
||||
|
||||
- Issue preview
|
||||
- Enabled issue notifications
|
||||
|
||||
## 0.6.25
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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>>,
|
||||
|
@ -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}
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user