TSK-1263 Inbox configurator (#3067)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-04-25 14:34:38 +06:00 committed by GitHub
parent aa218ab898
commit 98d71a25df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
66 changed files with 1348 additions and 679 deletions

View File

@ -125,15 +125,35 @@ export function createModel (builder: Builder): void {
calendar.viewlet.CalendarEvent
)
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: calendar.string.Calendar,
icon: calendar.icon.Calendar
},
calendar.ids.CalendarNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: calendar.string.Reminder,
textTemplate: 'Reminder: {doc}',
htmlTemplate: 'Reminder: {doc}',
subjectTemplate: 'Reminder: {doc}'
group: calendar.ids.CalendarNotificationGroup,
txClasses: [],
objectClass: calendar.mixin.Reminder,
templates: {
textTemplate: 'Reminder: {doc}',
htmlTemplate: 'Reminder: {doc}',
subjectTemplate: 'Reminder: {doc}'
},
providers: {
[notification.providers.PlatformNotification]: true,
[notification.providers.EmailNotification]: false
}
},
calendar.ids.ReminderNotification
)

View File

@ -17,6 +17,7 @@ import { TxViewlet } from '@hcengineering/activity'
import { calendarId } from '@hcengineering/calendar'
import calendar from '@hcengineering/calendar-resources/src/plugin'
import { Ref } from '@hcengineering/core'
import { NotificationGroup } from '@hcengineering/notification'
import type { IntlString } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
import { AnyComponent } from '@hcengineering/ui'
@ -54,6 +55,7 @@ export default mergeIds(calendarId, calendar, {
CalendarEvent: '' as Ref<Viewlet>
},
ids: {
ReminderViewlet: '' as Ref<TxViewlet>
ReminderViewlet: '' as Ref<TxViewlet>,
CalendarNotificationGroup: '' as Ref<NotificationGroup>
}
})

View File

@ -38,6 +38,7 @@
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/activity": "^0.6.0",
"@hcengineering/workbench": "^0.6.4",
"@hcengineering/model-preference": "^0.6.0"

View File

@ -88,6 +88,7 @@ export class TChunterMessage extends TAttachedDoc implements ChunterMessage {
attachments?: number
@Prop(TypeRef(core.class.Account), chunter.string.CreateBy)
@ReadOnly()
createBy!: Ref<Account>
@Prop(TypeTimestamp(), chunter.string.Create)
@ -584,7 +585,7 @@ export function createModel (builder: Builder, options = { addApplication: true
icon: chunter.icon.Chunter,
txClass: core.class.TxCreateDoc,
component: chunter.activity.TxMessageCreate,
label: notification.string.DMNotification,
label: chunter.string.DMNotification,
display: 'content',
editable: true,
hideOnRemove: true
@ -641,6 +642,96 @@ export function createModel (builder: Builder, options = { addApplication: true
filters: []
})
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: chunter.string.ApplicationLabelChunter,
icon: chunter.icon.Chunter
},
chunter.ids.ChunterNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.MentionNotification,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.Backlink,
group: chunter.ids.ChunterNotificationGroup,
providers: {
[notification.providers.EmailNotification]: true,
[notification.providers.PlatformNotification]: true
},
templates: {
textTemplate: '{sender} mentioned you in {doc} {data}',
htmlTemplate: '<p><b>{sender}</b> mentioned you in {doc}</p> {data}',
subjectTemplate: 'You were mentioned in {doc}'
}
},
chunter.ids.MentionNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.DM,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.Message,
providers: {
[notification.providers.EmailNotification]: false,
[notification.providers.PlatformNotification]: true
},
group: chunter.ids.ChunterNotificationGroup,
templates: {
textTemplate: '{sender} has send you a message: {doc} {data}',
htmlTemplate: '<p><b>{sender}</b> has send you a message {doc}</p> {data}',
subjectTemplate: 'You have new direct message in {doc}'
}
},
chunter.ids.DMNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.Message,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.Message,
providers: {
[notification.providers.PlatformNotification]: true
},
group: chunter.ids.ChunterNotificationGroup
},
chunter.ids.ThreadNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: chunter.string.ThreadMessage,
generated: false,
hidden: false,
txClasses: [core.class.TxCreateDoc],
objectClass: chunter.class.ThreadMessage,
providers: {
[notification.providers.PlatformNotification]: true
},
group: chunter.ids.ChunterNotificationGroup
},
chunter.ids.ChannelNotification
)
createAction(builder, {
...viewTemplates.open,
target: chunter.class.Channel,

View File

@ -16,7 +16,8 @@
import type { DisplayTx, TxViewlet } from '@hcengineering/activity'
import { Channel, chunterId } from '@hcengineering/chunter'
import chunter from '@hcengineering/chunter-resources/src/plugin'
import type { Ref, Space, Doc } from '@hcengineering/core'
import type { Doc, Ref, Space } from '@hcengineering/core'
import { NotificationGroup } from '@hcengineering/notification'
import type { IntlString, Resource } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
import type { AnyComponent, Location } from '@hcengineering/ui'
@ -68,6 +69,7 @@ export default mergeIds(chunterId, chunter, {
Edit: '' as IntlString,
MarkUnread: '' as IntlString,
LastMessage: '' as IntlString,
MentionNotification: '' as IntlString,
PinnedMessages: '' as IntlString,
SavedMessages: '' as IntlString,
ThreadMessage: '' as IntlString,
@ -75,6 +77,8 @@ export default mergeIds(chunterId, chunter, {
Emoji: '' as IntlString,
FilterComments: '' as IntlString,
FilterBacklinks: '' as IntlString,
DM: '' as IntlString,
DMNotification: '' as IntlString,
ConfigLabel: '' as IntlString,
ConfigDescription: '' as IntlString
},
@ -86,7 +90,8 @@ export default mergeIds(chunterId, chunter, {
TxBacklinkCreate: '' as Ref<TxViewlet>,
TxCommentRemove: '' as Ref<TxViewlet>,
TxBacklinkRemove: '' as Ref<TxViewlet>,
TxMessageCreate: '' as Ref<TxViewlet>
TxMessageCreate: '' as Ref<TxViewlet>,
ChunterNotificationGroup: '' as Ref<NotificationGroup>
},
activity: {
TxCommentCreate: '' as AnyComponent,

View File

@ -434,15 +434,36 @@ export function createModel (builder: Builder): void {
presenter: hr.component.RequestPresenter
})
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: hr.string.HRApplication,
icon: hr.icon.HR
},
hr.ids.HRNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: true,
label: hr.string.Request,
textTemplate: 'New request: {doc}',
htmlTemplate: 'New request: {doc}',
subjectTemplate: 'New request'
hidden: false,
generated: false,
label: hr.string.RequestCreated,
group: hr.ids.HRNotificationGroup,
// will be created with different trigger
txClasses: [],
objectClass: hr.class.Request,
providers: {
[notification.providers.EmailNotification]: true,
[notification.providers.PlatformNotification]: true
},
templates: {
textTemplate: 'New request: {doc}',
htmlTemplate: 'New request: {doc}',
subjectTemplate: 'New request'
}
},
hr.ids.CreateRequestNotification
)
@ -451,11 +472,22 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
core.space.Model,
{
hidden: true,
label: hr.string.Request,
textTemplate: 'Request updated: {doc}',
htmlTemplate: 'Request updated: {doc}',
subjectTemplate: 'Request updated'
hidden: false,
generated: false,
group: hr.ids.HRNotificationGroup,
label: hr.string.RequestUpdated,
// will be created with different trigger
txClasses: [],
objectClass: hr.class.Request,
providers: {
[notification.providers.EmailNotification]: true,
[notification.providers.PlatformNotification]: true
},
templates: {
textTemplate: 'Request updated: {doc}',
htmlTemplate: 'Request updated: {doc}',
subjectTemplate: 'Request updated'
}
},
hr.ids.UpdateRequestNotification
)
@ -464,11 +496,22 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
core.space.Model,
{
hidden: true,
label: hr.string.Request,
textTemplate: 'Request removed: {doc}',
htmlTemplate: 'Request removed: {doc}',
subjectTemplate: 'Request removed'
hidden: false,
group: hr.ids.HRNotificationGroup,
generated: false,
label: hr.string.RequestRemoved,
// will be created with different trigger
txClasses: [],
objectClass: hr.class.Request,
providers: {
[notification.providers.EmailNotification]: true,
[notification.providers.PlatformNotification]: true
},
templates: {
textTemplate: 'Request removed: {doc}',
htmlTemplate: 'Request removed: {doc}',
subjectTemplate: 'Request removed'
}
},
hr.ids.RemoveRequestNotification
)
@ -477,11 +520,22 @@ export function createModel (builder: Builder): void {
notification.class.NotificationType,
core.space.Model,
{
hidden: true,
hidden: false,
generated: false,
group: hr.ids.HRNotificationGroup,
label: hr.string.PublicHoliday,
textTemplate: 'New public holiday: {doc}',
htmlTemplate: 'New public holiday: {doc}',
subjectTemplate: 'New public holiday'
// will be created with different trigger
txClasses: [],
objectClass: hr.class.PublicHoliday,
providers: {
[notification.providers.EmailNotification]: true,
[notification.providers.PlatformNotification]: true
},
templates: {
textTemplate: 'New public holiday: {doc}',
htmlTemplate: 'New public holiday: {doc}',
subjectTemplate: 'New public holiday'
}
},
hr.ids.CreatePublicHolidayNotification
)

View File

@ -16,6 +16,7 @@
import { Ref } from '@hcengineering/core'
import { hrId } from '@hcengineering/hr'
import hr from '@hcengineering/hr-resources/src/plugin'
import { NotificationGroup } from '@hcengineering/notification'
import { IntlString, mergeIds } from '@hcengineering/platform'
import { AnyComponent } from '@hcengineering/ui'
import { Action, ActionCategory, ViewAction } from '@hcengineering/view'
@ -34,6 +35,9 @@ export default mergeIds(hrId, hr, {
Overtime2: '' as IntlString,
Subscribers: '' as IntlString,
PublicHoliday: '' as IntlString,
RequestCreated: '' as IntlString,
RequestUpdated: '' as IntlString,
RequestRemoved: '' as IntlString,
ConfigLabel: '' as IntlString,
ConfigDescription: '' as IntlString
},
@ -60,5 +64,8 @@ export default mergeIds(hrId, hr, {
},
actionImpl: {
EditRequestType: '' as ViewAction
},
ids: {
HRNotificationGroup: '' as Ref<NotificationGroup>
}
})

View File

@ -41,6 +41,8 @@
"@hcengineering/setting": "^0.6.5",
"@hcengineering/view": "^0.6.4",
"@hcengineering/task": "^0.6.6",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/model-task": "^0.6.0",
"@hcengineering/workbench": "^0.6.4"
}

View File

@ -24,6 +24,7 @@ import {
Mixin,
Model,
Prop,
ReadOnly,
TypeMarkup,
TypeRef,
TypeString,
@ -38,6 +39,8 @@ import view, { createAction, actionTemplates as viewTemplates } from '@hcenginee
import workbench from '@hcengineering/model-workbench'
import setting from '@hcengineering/setting'
import { ViewOptionsModel } from '@hcengineering/view'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import notification from '@hcengineering/notification'
import lead from './plugin'
export { leadId } from '@hcengineering/lead'
@ -62,6 +65,7 @@ export class TFunnel extends TSpaceWithStates implements Funnel {
@UX(lead.string.Lead, lead.icon.Lead, undefined, 'title')
export class TLead extends TTask implements Lead {
@Prop(TypeRef(contact.class.Contact), lead.string.Customer)
@ReadOnly()
declare attachedTo: Ref<Customer>
@Prop(TypeString(), lead.string.Title)
@ -287,6 +291,49 @@ export function createModel (builder: Builder): void {
}
}
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: lead.string.Lead,
icon: lead.icon.Lead,
objectClass: lead.class.Lead
},
lead.ids.LeadNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: task.string.AssignedToMe,
group: lead.ids.LeadNotificationGroup,
field: 'assignee',
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
objectClass: lead.class.Lead,
templates: {
textTemplate: '{doc} was assigned to you by {sender}',
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
subjectTemplate: '{doc} was assigned to you'
},
providers: {
[notification.providers.PlatformNotification]: true,
[notification.providers.EmailNotification]: true
}
},
lead.ids.AssigneeNotification
)
generateClassNotificationTypes(
builder,
lead.class.Lead,
lead.ids.LeadNotificationGroup,
[],
['comments', 'state', 'doneState']
)
builder.createDoc(
view.class.Viewlet,
core.space.Model,

View File

@ -16,6 +16,7 @@
import type { Ref, Space } from '@hcengineering/core'
import { leadId } from '@hcengineering/lead'
import { NotificationGroup, NotificationType } from '@hcengineering/notification'
import lead from '@hcengineering/lead-resources/src/plugin'
import type { IntlString } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
@ -63,5 +64,9 @@ export default mergeIds(leadId, lead, {
},
action: {
CreateGlobalLead: '' as Ref<Action>
},
ids: {
LeadNotificationGroup: '' as Ref<NotificationGroup>,
AssigneeNotification: '' as Ref<NotificationType>
}
})

View File

@ -30,6 +30,7 @@
"@hcengineering/ui": "^0.6.6",
"@hcengineering/platform": "^0.6.8",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/model-preference": "^0.6.0",
"@hcengineering/model-view": "^0.6.0",
"@hcengineering/view": "^0.6.4",
"@hcengineering/workbench": "^0.6.4",

View File

@ -14,25 +14,42 @@
// limitations under the License.
//
import { Account, Class, Doc, Domain, DOMAIN_MODEL, IndexKind, Ref, Timestamp, TxCUD } from '@hcengineering/core'
import {
Account,
AttachedDoc,
Class,
Collection,
Data,
Doc,
Domain,
DOMAIN_MODEL,
Hierarchy,
IndexKind,
Ref,
Timestamp,
Tx,
TxCUD
} from '@hcengineering/core'
import { ArrOf, Builder, Index, Mixin, Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
import core, { TAttachedDoc, TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction } from '@hcengineering/model-view'
import {
AnotherUserNotifications,
DocUpdates,
EmailNotification,
LastView,
Notification,
NotificationGroup,
notificationId,
NotificationObjectPresenter,
NotificationProvider,
NotificationSetting,
NotificationStatus,
NotificationTemplate,
NotificationType,
SpaceLastEdit
} from '@hcengineering/notification'
import type { IntlString } from '@hcengineering/platform'
import type { Asset, IntlString } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
import { AnyComponent } from '@hcengineering/ui'
import workbench from '@hcengineering/workbench'
@ -89,11 +106,22 @@ export class TEmaiNotification extends TDoc implements EmailNotification {
@Model(notification.class.NotificationType, core.class.Doc, DOMAIN_MODEL)
export class TNotificationType extends TDoc implements NotificationType {
generated!: boolean
label!: IntlString
textTemplate!: string
htmlTemplate!: string
subjectTemplate!: string
group!: Ref<NotificationGroup>
txClasses!: Ref<Class<Tx>>[]
providers!: Record<Ref<NotificationProvider>, boolean>
objectClass!: Ref<Class<Doc>>
hidden!: boolean
templates?: NotificationTemplate
}
@Model(notification.class.NotificationGroup, core.class.Doc, DOMAIN_MODEL)
export class TNotificationGroup extends TDoc implements NotificationGroup {
label!: IntlString
icon!: Asset
// using for autogenerated settings
objectClass?: Ref<Class<Doc>>
}
@Model(notification.class.NotificationProvider, core.class.Doc, DOMAIN_MODEL)
@ -102,10 +130,10 @@ export class TNotificationProvider extends TDoc implements NotificationProvider
default!: boolean
}
@Model(notification.class.NotificationSetting, core.class.Doc, DOMAIN_NOTIFICATION)
export class TNotificationSetting extends TDoc implements NotificationSetting {
@Model(notification.class.NotificationSetting, preference.class.Preference)
export class TNotificationSetting extends TPreference implements NotificationSetting {
attachedTo!: Ref<TNotificationProvider>
type!: Ref<TNotificationType>
provider!: Ref<TNotificationProvider>
enabled!: boolean
}
@ -114,11 +142,6 @@ 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.ClassCollaborators, core.class.Class)
export class TClassCollaborators extends TClass {
fields!: string[]
@ -165,8 +188,8 @@ export function createModel (builder: Builder): void {
TNotificationType,
TNotificationProvider,
TNotificationSetting,
TNotificationGroup,
TSpaceLastEdit,
TAnotherUserNotifications,
TClassCollaborators,
TTrackedDoc,
TCollaborators,
@ -174,32 +197,6 @@ export function createModel (builder: Builder): void {
TNotificationObjectPresenter
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: notification.string.MentionNotification,
hidden: false,
textTemplate: '{sender} mentioned you in {doc} {data}',
htmlTemplate: '<p><b>{sender}</b> mentioned you in {doc}</p> {data}',
subjectTemplate: 'You were mentioned in {doc}'
},
notification.ids.MentionNotification
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: notification.string.DM,
hidden: false,
textTemplate: '{sender} has send you a message: {doc} {data}',
htmlTemplate: '<p><b>{sender}</b> has send you a message {doc}</p> {data}',
subjectTemplate: 'You have new DM message in {doc}'
},
notification.ids.DMNotification
)
// Temporarily disabled, we should think about it
// builder.createDoc(
// notification.class.NotificationProvider,
@ -215,10 +212,18 @@ export function createModel (builder: Builder): void {
notification.class.NotificationProvider,
core.space.Model,
{
label: notification.string.EmailNotification,
default: true
label: notification.string.Inbox
},
notification.ids.EmailNotification
notification.providers.PlatformNotification
)
builder.createDoc(
notification.class.NotificationProvider,
core.space.Model,
{
label: notification.string.EmailNotification
},
notification.providers.EmailNotification
)
builder.createDoc(
@ -300,3 +305,49 @@ export function createModel (builder: Builder): void {
actions: [view.action.Delete, view.action.Open]
})
}
export function generateClassNotificationTypes (
builder: Builder,
_class: Ref<Class<Doc>>,
group: Ref<NotificationGroup>,
ignoreKeys: string[] = [],
defaultEnabled: string[] = []
): void {
const txes = builder.getTxes()
const hierarchy = new Hierarchy()
for (const tx of txes) {
hierarchy.tx(tx)
}
const attributes = hierarchy.getAllAttributes(
_class,
hierarchy.isDerived(_class, core.class.AttachedDoc) ? core.class.AttachedDoc : core.class.Doc
)
const filtered = Array.from(attributes.values()).filter((p) => p.hidden !== true && p.readonly !== true)
for (const attribute of filtered) {
if (ignoreKeys.includes(attribute.name)) continue
const isCollection: boolean = core.class.Collection === attribute.type._class
const objectClass = !isCollection ? _class : (attribute.type as Collection<AttachedDoc>).of
const txClasses = !isCollection
? hierarchy.isMixin(attribute.attributeOf)
? [core.class.TxMixin]
: [core.class.TxUpdateDoc]
: [core.class.TxCreateDoc, core.class.TxRemoveDoc]
const data: Data<NotificationType> = {
attribute: attribute._id,
group,
generated: true,
objectClass,
txClasses,
hidden: false,
providers: {
[notification.providers.PlatformNotification]: defaultEnabled.includes(attribute.name)
},
label: attribute.label
}
if (isCollection) {
data.attachedToClass = _class
}
const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref<NotificationType>
builder.createDoc(notification.class.NotificationType, core.space.Model, data, id)
}
}

View File

@ -17,17 +17,18 @@ import core, {
Account,
AttachedDoc,
Class,
Collection,
Data,
Doc,
DOMAIN_TX,
generateId,
Ref,
TxCollectionCUD,
TxCreateDoc,
TxOperations,
TxRemoveDoc
} from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import notification, { LastView, Notification, NotificationType } from '@hcengineering/notification'
import notification, { LastView, NotificationType } from '@hcengineering/notification'
import { DOMAIN_NOTIFICATION } from '.'
async function fillNotificationText (client: MigrationClient): Promise<void> {
@ -51,23 +52,11 @@ async function fillNotificationText (client: MigrationClient): Promise<void> {
)
}
async function fillNotificationType (client: MigrationUpgradeClient): Promise<void> {
const notifications = await client.findAll(notification.class.Notification, { type: { $exists: false } })
const txOp = new TxOperations(client, core.account.System)
const promises = notifications.map(async (doc) => {
const tx = await client.findOne(core.class.TxCUD, { _id: doc.tx })
if (tx === undefined) return
const type =
tx._class === core.class.TxMixin
? ('calendar:ids:ReminderNotification' as Ref<NotificationType>)
: notification.ids.MentionNotification
const objectTx = txOp.update(doc, { type })
const ctx = await client.findOne<TxCreateDoc<Notification>>(core.class.TxCreateDoc, { objectId: doc._id })
if (ctx === undefined) return await objectTx
const updateTx = txOp.update(ctx, { 'attributes.type': type } as any)
return await Promise.all([objectTx, updateTx])
})
await Promise.all(promises)
async function removeSettings (client: MigrationClient): Promise<void> {
const outdatedSettings = await client.find(DOMAIN_NOTIFICATION, { _class: notification.class.NotificationSetting })
for (const setting of outdatedSettings) {
await client.delete(DOMAIN_NOTIFICATION, setting._id)
}
}
async function createSpace (client: MigrationUpgradeClient): Promise<void> {
@ -197,15 +186,66 @@ async function fillDocUpdatesHidder (client: MigrationClient): Promise<void> {
)
}
async function createCustomFieldTypes (client: MigrationUpgradeClient): Promise<void> {
const txop = new TxOperations(client, core.account.System)
const attributes = await client.findAll(core.class.Attribute, { isCustom: true })
const groups = new Map(
(await client.findAll(notification.class.NotificationGroup, {}))
.filter((p) => p.objectClass !== undefined)
.map((p) => [p.objectClass, p])
)
const types = new Set((await client.findAll(notification.class.NotificationType, {})).map((p) => p.attribute))
for (const attribute of attributes) {
if (attribute.hidden === true || attribute.readonly === true) continue
if (types.has(attribute._id)) continue
const group = groups.get(attribute.attributeOf)
if (group === undefined) continue
const isCollection: boolean = core.class.Collection === attribute.type._class
const _class = attribute.attributeOf
const objectClass = !isCollection ? _class : (attribute.type as Collection<AttachedDoc>).of
const txClasses = !isCollection
? [client.getHierarchy().isMixin(_class) ? core.class.TxMixin : core.class.TxUpdateDoc]
: [core.class.TxCreateDoc, core.class.TxRemoveDoc]
const data: Data<NotificationType> = {
attribute: attribute._id,
group: group._id,
generated: true,
objectClass,
txClasses,
hidden: false,
providers: {
[notification.providers.PlatformNotification]: false
},
label: attribute.label
}
if (isCollection) {
data.attachedToClass = _class
}
const id = `${notification.class.NotificationType}_${_class}_${attribute.name}` as Ref<NotificationType>
await txop.createDoc(notification.class.NotificationType, core.space.Model, data, id)
}
}
async function cleanOutdatedSettings (client: MigrationClient): Promise<void> {
const res = await client.find(DOMAIN_NOTIFICATION, {
_class: notification.class.NotificationSetting
})
for (const value of res) {
await client.delete(DOMAIN_NOTIFICATION, value._id)
}
}
export const notificationOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await removeSettings(client)
await fillNotificationText(client)
await migrateLastView(client)
await fillCollaborators(client)
await fillDocUpdatesHidder(client)
await cleanOutdatedSettings(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
await createSpace(client)
await fillNotificationType(client)
await createCustomFieldTypes(client)
}
}

View File

@ -24,9 +24,6 @@ import { Application } from '@hcengineering/workbench'
export default mergeIds(notificationId, notification, {
string: {
LastView: '' as IntlString,
DM: '' as IntlString,
DMNotification: '' as IntlString,
MentionNotification: '' as IntlString,
PlatformNotification: '' as IntlString,
BrowserNotification: '' as IntlString,
EmailNotification: '' as IntlString,
@ -46,6 +43,7 @@ export default mergeIds(notificationId, notification, {
category: {
Notification: '' as Ref<ActionCategory>
},
groups: {},
action: {
Unsubscribe: '' as Ref<Action>,
Hide: '' as Ref<Action>,

View File

@ -47,6 +47,7 @@
"@hcengineering/workbench": "^0.6.4",
"@hcengineering/model-tracker": "^0.6.0",
"@hcengineering/model-presentation": "^0.6.0",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/model-calendar": "^0.6.0",
"@hcengineering/model-tags": "^0.6.0",
"@anticrm/skillset": "^0.6.0"

View File

@ -59,6 +59,7 @@ import { KeyBinding, ViewOptionsModel } from '@hcengineering/view'
import recruit from './plugin'
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
import { TOpinion, TReview } from './review-model'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
export { recruitId } from '@hcengineering/recruit'
export { recruitOperation } from './migration'
@ -87,8 +88,7 @@ export class TVacancy extends TSpaceWithStates implements Vacancy {
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(Collection(chunter.class.Backlink), chunter.string.Comments)
relations!: number
relations!: number
@Prop(TypeString(), recruit.string.Vacancy)
@Index(IndexKind.FullText)
@ -151,6 +151,7 @@ export class TApplicant extends TTask implements Applicant {
// We need to declare, to provide property with label
@Prop(TypeRef(recruit.mixin.Candidate), recruit.string.Talent)
@Index(IndexKind.Indexed)
@ReadOnly()
declare attachedTo: Ref<Candidate>
// We need to declare, to provide property with label
@ -329,7 +330,7 @@ export function createModel (builder: Builder): void {
},
{
id: assignedId,
label: task.string.Assigned,
label: task.string.AssignedToMe,
icon: recruit.icon.AssignedToMe,
component: task.component.AssignedTasks,
position: 'event',
@ -1086,6 +1087,81 @@ export function createModel (builder: Builder): void {
component: recruit.component.ApplicantFilter
})
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: recruit.string.Application,
icon: recruit.icon.Application,
objectClass: recruit.class.Applicant
},
recruit.ids.ApplicationNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: task.string.AssignedToMe,
group: recruit.ids.ApplicationNotificationGroup,
field: 'assignee',
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
objectClass: recruit.class.Applicant,
templates: {
textTemplate: '{doc} was assigned to you by {sender}',
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
subjectTemplate: '{doc} was assigned to you'
},
providers: {
[notification.providers.PlatformNotification]: true,
[notification.providers.EmailNotification]: true
}
},
recruit.ids.AssigneeNotification
)
generateClassNotificationTypes(
builder,
recruit.class.Applicant,
recruit.ids.ApplicationNotificationGroup,
[],
['comments', 'state', 'doneState']
)
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: recruit.string.Vacancy,
icon: recruit.icon.Vacancy,
objectClass: recruit.class.Vacancy
},
recruit.ids.VacancyNotificationGroup
)
generateClassNotificationTypes(builder, recruit.class.Vacancy, recruit.ids.VacancyNotificationGroup, [], ['comments'])
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: recruit.string.Talent,
icon: recruit.icon.CreateCandidate,
objectClass: recruit.mixin.Candidate
},
recruit.ids.CandidateNotificationGroup
)
generateClassNotificationTypes(
builder,
recruit.mixin.Candidate,
recruit.ids.CandidateNotificationGroup,
['vacancyMatch'],
['comments']
)
builder.createDoc(
view.class.FilterMode,
core.space.Model,

View File

@ -14,6 +14,7 @@
//
import type { Client, Doc, Ref } from '@hcengineering/core'
import { NotificationGroup, NotificationType } from '@hcengineering/notification'
import type { IntlString, Resource, Status } from '@hcengineering/platform'
import { mergeIds } from '@hcengineering/platform'
import { recruitId } from '@hcengineering/recruit'
@ -67,6 +68,12 @@ export default mergeIds(recruitId, recruit, {
validator: {
ApplicantValidator: '' as Resource<<T extends Doc>(doc: T, client: Client) => Promise<Status>>
},
ids: {
VacancyNotificationGroup: '' as Ref<NotificationGroup>,
CandidateNotificationGroup: '' as Ref<NotificationGroup>,
ApplicationNotificationGroup: '' as Ref<NotificationGroup>,
AssigneeNotification: '' as Ref<NotificationType>
},
component: {
CreateApplication: '' as AnyComponent,
KanbanCard: '' as AnyComponent,

View File

@ -31,6 +31,7 @@
"@hcengineering/server-chunter": "^0.6.0",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/chunter": "^0.6.5",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/server-notification": "^0.6.0"
}
}

View File

@ -20,6 +20,7 @@ import chunter from '@hcengineering/chunter'
import serverNotification from '@hcengineering/server-notification'
import serverCore, { ObjectDDParticipant } from '@hcengineering/server-core'
import serverChunter from '@hcengineering/server-chunter'
import notification from '@hcengineering/notification'
export { serverChunterId } from '@hcengineering/server-chunter'
export function createModel (builder: Builder): void {
@ -44,7 +45,16 @@ export function createModel (builder: Builder): void {
trigger: serverChunter.trigger.ChunterTrigger
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.DMTrigger
builder.mixin(chunter.ids.DMNotification, notification.class.NotificationType, serverNotification.mixin.TypeMatch, {
func: serverChunter.function.IsDirectMessagee
})
builder.mixin(
chunter.ids.ChannelNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverChunter.function.IsChannelMessagee
}
)
}

View File

@ -31,6 +31,8 @@
"@hcengineering/server-lead": "^0.6.0",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/lead": "^0.6.0",
"@hcengineering/model-lead": "^0.6.0",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/server-notification": "^0.6.0"
}
}

View File

@ -16,8 +16,8 @@
import { Builder } from '@hcengineering/model'
import core from '@hcengineering/core'
import lead from '@hcengineering/lead'
import serverCore from '@hcengineering/server-core'
import lead from '@hcengineering/model-lead'
import notification from '@hcengineering/notification'
import serverLead from '@hcengineering/server-lead'
import serverNotification from '@hcengineering/server-notification'
@ -32,7 +32,12 @@ export function createModel (builder: Builder): void {
presenter: serverLead.function.LeadTextPresenter
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverLead.trigger.OnLeadUpdate
})
builder.mixin(
lead.ids.AssigneeNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserInFieldValue
}
)
}

View File

@ -29,6 +29,8 @@
"@hcengineering/model": "^0.6.2",
"@hcengineering/model-core": "^0.6.0",
"@hcengineering/platform": "^0.6.8",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/server-notification": "^0.6.0",
"@hcengineering/server-core": "^0.6.1"
}

View File

@ -16,11 +16,18 @@
import { Builder, Mixin } from '@hcengineering/model'
import core from '@hcengineering/core'
import core, { Account, Doc, Ref, Tx } from '@hcengineering/core'
import { TClass } from '@hcengineering/model-core'
import { TNotificationType } from '@hcengineering/model-notification'
import notification, { NotificationType } from '@hcengineering/notification'
import { Resource } from '@hcengineering/platform'
import serverCore from '@hcengineering/server-core'
import serverNotification, { HTMLPresenter, Presenter, TextPresenter } from '@hcengineering/server-notification'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import serverNotification, {
HTMLPresenter,
Presenter,
TextPresenter,
TypeMatch
} from '@hcengineering/server-notification'
export { serverNotificationId } from '@hcengineering/server-notification'
@ -34,8 +41,15 @@ export class TTextPresenter extends TClass implements TextPresenter {
presenter!: Resource<Presenter>
}
@Mixin(serverNotification.mixin.TypeMatch, notification.class.NotificationType)
export class TTypeMatch extends TNotificationType implements TypeMatch {
func!: Resource<
(tx: Tx, doc: Doc, user: Ref<Account>, type: NotificationType, control: TriggerControl) => Promise<boolean>
>
}
export function createModel (builder: Builder): void {
builder.createModel(THTMLPresenter, TTextPresenter)
builder.createModel(THTMLPresenter, TTextPresenter, TTypeMatch)
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnBacklinkCreate
@ -56,4 +70,12 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAddCollborator
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAttributeCreate
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverNotification.trigger.OnAttributeUpdate
})
}

View File

@ -30,7 +30,8 @@
"@hcengineering/platform": "^0.6.8",
"@hcengineering/server-recruit": "^0.6.0",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/recruit": "^0.6.11",
"@hcengineering/model-recruit": "^0.6.0",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/server-notification": "^0.6.0"
}
}

View File

@ -16,7 +16,8 @@
import { Builder } from '@hcengineering/model'
import core from '@hcengineering/core'
import recruit from '@hcengineering/recruit'
import recruit from '@hcengineering/model-recruit'
import notification from '@hcengineering/notification'
import serverCore from '@hcengineering/server-core'
import serverNotification from '@hcengineering/server-notification'
import serverRecruit from '@hcengineering/server-recruit'
@ -43,4 +44,13 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverRecruit.trigger.OnRecruitUpdate
})
builder.mixin(
recruit.ids.AssigneeNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserInFieldValue
}
)
}

View File

@ -29,7 +29,9 @@
"@hcengineering/model": "^0.6.2",
"@hcengineering/tracker": "^0.6.6",
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/server-notification": "^0.6.0",
"@hcengineering/model-tracker": "^0.6.0",
"@hcengineering/server-tracker": "^0.6.0"
}
}

View File

@ -15,10 +15,11 @@
import core from '@hcengineering/core'
import { Builder } from '@hcengineering/model'
import tracker from '@hcengineering/model-tracker'
import notification from '@hcengineering/notification'
import serverCore from '@hcengineering/server-core'
import serverNotification from '@hcengineering/server-notification'
import serverTracker from '@hcengineering/server-tracker'
import tracker from '@hcengineering/tracker'
export { serverTrackerId } from '@hcengineering/server-tracker'
@ -38,4 +39,13 @@ export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTracker.trigger.OnComponentRemove
})
builder.mixin(
tracker.ids.AssigneeNotification,
notification.class.NotificationType,
serverNotification.mixin.TypeMatch,
{
func: serverNotification.function.IsUserInFieldValue
}
)
}

View File

@ -322,9 +322,6 @@ export function createModel (builder: Builder): void {
})
builder.mixin(task.class.Task, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(task.class.Task, core.class.Class, notification.mixin.AnotherUserNotifications, {
fields: ['assignee']
})
builder.createDoc(
view.class.ActionCategory,
@ -487,16 +484,16 @@ export function createModel (builder: Builder): void {
task.action.ArchiveState
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
label: task.string.Assigned,
hidden: false,
textTemplate: '{doc} was assigned to you by {sender}',
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
subjectTemplate: '{doc} was assigned to you'
},
task.ids.AssigneedNotification
)
// builder.createDoc(
// notification.class.NotificationType,
// core.space.Model,
// {
// label: task.string.Assigned,
// hidden: false,
// textTemplate: '{doc} was assigned to you by {sender}',
// htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
// subjectTemplate: '{doc} was assigned to you'
// },
// task.ids.AssigneedNotification
// )
}

View File

@ -37,6 +37,7 @@
"@hcengineering/model-contact": "^0.6.1",
"@hcengineering/model-attachment": "^0.6.0",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/tracker": "^0.6.6",
"@hcengineering/tracker-resources": "^0.6.0",
"@hcengineering/model-chunter": "^0.6.0",

View File

@ -80,6 +80,7 @@ import tracker from './plugin'
import presentation from '@hcengineering/model-presentation'
import { defaultPriorities, issuePriorities } from '@hcengineering/tracker-resources/src/types'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
export { trackerId } from '@hcengineering/tracker'
export { trackerOperation } from './migration'
@ -197,6 +198,7 @@ export class TIssue extends TAttachedDoc implements Issue {
@Prop(TypeNumber(), tracker.string.Number)
@Index(IndexKind.FullText)
@ReadOnly()
number!: number
@Prop(TypeRef(contact.class.Employee), tracker.string.Assignee)
@ -939,10 +941,6 @@ export function createModel (builder: Builder): void {
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.TrackedDoc, {})
builder.mixin(tracker.class.Issue, core.class.Class, notification.mixin.AnotherUserNotifications, {
fields: ['assignee']
})
builder.mixin(tracker.class.TypeIssuePriority, core.class.Class, view.mixin.AllValuesFunc, {
func: tracker.function.GetAllPriority
})
@ -1799,6 +1797,49 @@ export function createModel (builder: Builder): void {
tracker.viewlet.SprintList
)
builder.createDoc(
notification.class.NotificationGroup,
core.space.Model,
{
label: tracker.string.Issues,
icon: tracker.icon.Issues,
objectClass: tracker.class.Issue
},
tracker.ids.TrackerNotificationGroup
)
builder.createDoc(
notification.class.NotificationType,
core.space.Model,
{
hidden: false,
generated: false,
label: task.string.AssignedToMe,
group: tracker.ids.TrackerNotificationGroup,
field: 'assignee',
txClasses: [core.class.TxCreateDoc, core.class.TxUpdateDoc],
objectClass: tracker.class.Issue,
templates: {
textTemplate: '{doc} was assigned to you by {sender}',
htmlTemplate: '<p>{doc} was assigned to you by {sender}</p>',
subjectTemplate: '{doc} was assigned to you'
},
providers: {
[notification.providers.PlatformNotification]: true,
[notification.providers.EmailNotification]: true
}
},
tracker.ids.AssigneeNotification
)
generateClassNotificationTypes(
builder,
tracker.class.Issue,
tracker.ids.TrackerNotificationGroup,
[],
['comments', 'status', 'priority']
)
createAction(
builder,
{

View File

@ -23,6 +23,7 @@ import type { AnyComponent } from '@hcengineering/ui/src/types'
import { Action, ViewAction, Viewlet } from '@hcengineering/view'
import { Application } from '@hcengineering/workbench'
import { TxViewlet } from '@hcengineering/activity'
import { NotificationGroup, NotificationType } from '@hcengineering/notification'
export default mergeIds(trackerId, tracker, {
string: {
@ -37,6 +38,7 @@ export default mergeIds(trackerId, tracker, {
SearchIssue: '' as IntlString,
Parent: '' as IntlString,
CreatedOn: '' as IntlString,
ChangeStatus: '' as IntlString,
ConfigLabel: '' as IntlString,
ConfigDescription: '' as IntlString
},
@ -61,7 +63,9 @@ export default mergeIds(trackerId, tracker, {
SprintList: '' as Ref<Viewlet>
},
ids: {
TxIssueCreated: '' as Ref<TxViewlet>
TxIssueCreated: '' as Ref<TxViewlet>,
TrackerNotificationGroup: '' as Ref<NotificationGroup>,
AssigneeNotification: '' as Ref<NotificationType>
},
completion: {
IssueQuery: '' as Resource<ObjectSearchFactory>,

View File

@ -25,6 +25,7 @@
"Reference": "Reference",
"Chat": "Chat",
"In": "In",
"MentionNotification": "Mentioned",
"Replies": "Replies",
"LastReply": "Last reply",
"RepliesCount": "{replies, plural, =1 {# reply} other {# replies}}",
@ -66,6 +67,8 @@
"CopyLink": "Copy link",
"FilterComments": "Comments",
"FilterBacklinks": "Backlinks",
"DM": "Direct message",
"DMNotification": "Sent you a message",
"ConfigLabel": "Chat",
"ConfigDescription": "Extension to perform text communications"
}

View File

@ -22,6 +22,7 @@
"Content": "Содержимое",
"Comment": "Комментарий",
"Message": "Сообщение",
"MentionNotification": "Упомянул",
"Reference": "Ссылка",
"Chat": "Чат",
"In": "в",
@ -66,6 +67,8 @@
"CopyLink": "Копировать ссылку",
"FilterComments": "Коментарии",
"FilterBacklinks": "Упоминания",
"DM": "Личное сообщение",
"DMNotification": "Отправил сообщение",
"ConfigLabel": "Чат",
"ConfigDescription": "Расширение для текстовых переписок"
}

View File

@ -40,7 +40,7 @@
_class,
space,
space,
chunter.class.ChunterSpace,
chunterSpace?._class ?? chunter.class.ChunterSpace,
'messages',
{
content: message,

View File

@ -28,6 +28,7 @@
"dependencies": {
"@hcengineering/platform": "^0.6.8",
"@hcengineering/ui": "^0.6.6",
"@hcengineering/notification": "^0.6.10",
"@hcengineering/contact": "^0.6.14",
"@hcengineering/core": "^0.6.23",
"@hcengineering/preference": "^0.6.4"

View File

@ -13,8 +13,9 @@
// limitations under the License.
//
import type { Account, AttachedDoc, Class, Doc, Ref, RelatedDocument, Space, Timestamp } from '@hcengineering/core'
import type { Employee } from '@hcengineering/contact'
import type { Account, AttachedDoc, Class, Doc, Ref, RelatedDocument, Space, Timestamp } from '@hcengineering/core'
import { NotificationType } from '@hcengineering/notification'
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
import { IntlString, plugin } from '@hcengineering/platform'
import type { Preference } from '@hcengineering/preference'
@ -160,6 +161,12 @@ export default plugin(chunterId, {
resolver: {
Location: '' as Resource<(loc: Location) => Promise<ResolvedLocation | undefined>>
},
ids: {
DMNotification: '' as Ref<NotificationType>,
MentionNotification: '' as Ref<NotificationType>,
ThreadNotification: '' as Ref<NotificationType>,
ChannelNotification: '' as Ref<NotificationType>
},
app: {
Chunter: '' as Ref<Doc>
},

View File

@ -51,6 +51,9 @@
"Export": "Export",
"Separator": "Separator",
"ChooseSeparator": "Choose separator",
"RequestCreated": "Request created",
"RequestUpdated": "Request updated",
"RequestRemoved": "Request removed",
"ConfigLabel": "Human resources",
"ConfigDescription": "Extension to manage organization structure and employee work calendar"
}

View File

@ -51,6 +51,9 @@
"Export": "Экспортировать",
"Separator": "Разделитель",
"ChooseSeparator": "Выберите разделитель",
"RequestCreated": "Запрос создан",
"RequestUpdated": "Запрос изменен",
"RequestRemoved": "Запрос удален",
"ConfigLabel": "Производственный календарь",
"ConfigDescription": "Расширение реализующее производственный календарь"
}

View File

@ -4,10 +4,7 @@
"Notification": "Notification",
"Notifications": "Notifications",
"NoNotifications": "No notifications",
"MentionNotification": "Mentioned",
"DM": "Direct message",
"DMNotification": "Sent you a message",
"EmailNotification": "by email",
"EmailNotification": "Email",
"PlatformNotification": "in platform",
"Track": "Track",
"DontTrack": "Don't track",
@ -19,6 +16,8 @@
"MarkAsUnread": "Mark as unread",
"Archive": "Archive",
"Inbox": "Inbox",
"Collaborators": "Collaborators"
"Collaborators": "Collaborators",
"Change": "Change",
"AddedRemoved": "Added/removed"
}
}

View File

@ -4,10 +4,7 @@
"Notification": "Увдомление",
"Notifications": "Уведомления",
"NoNotifications": "Нет уведомлений",
"MentionNotification": "Упомянул",
"DM": "Личное сообщение",
"DMNotification": "Отправил сообщение",
"EmailNotification": "по email",
"EmailNotification": "Email",
"PlatformNotification": "в системе",
"Track": "Отслеживать",
"DontTrack": "Не отслеживать",
@ -19,6 +16,8 @@
"MarkAsUnread": "Отметить непрочитанным",
"Archive": "Архивировать",
"Inbox": "Входящие",
"Collaborators": "Участники"
"Collaborators": "Участники",
"Change": "Изменено",
"AddedRemoved": "Добавлено/удалено"
}
}

View File

@ -14,13 +14,13 @@
-->
<script lang="ts">
import contact, { EmployeeAccount } from '@hcengineering/contact'
import { Doc, getCurrentAccount, Ref, Space } from '@hcengineering/core'
import { Doc, getCurrentAccount, Ref } from '@hcengineering/core'
import {
Notification as PlatformNotification,
NotificationProvider,
NotificationSetting,
NotificationStatus,
NotificationType
NotificationType,
Notification as PlatformNotification
} from '@hcengineering/notification'
import { createQuery, getClient } from '@hcengineering/presentation'
import { getCurrentLocation, showPanel } from '@hcengineering/ui'
@ -31,8 +31,6 @@
const query = createQuery()
const settingQuery = createQuery()
const providersQuery = createQuery()
const accountId = getCurrentAccount()._id
const space = accountId as string as Ref<Space>
const notificationClient = NotificationClientImpl.getClient()
const lastViews = notificationClient.getLastViews()
const lastViewId: Ref<Doc> = ((getCurrentAccount() as EmployeeAccount).employee + 'notification') as Ref<Doc>
@ -46,27 +44,21 @@
$: enabled &&
providersQuery.query(
notification.class.NotificationProvider,
{ _id: notification.ids.BrowserNotification },
{ _id: notification.providers.BrowserNotification },
(res) => {
provider = res[0]
}
)
$: enabled &&
settingQuery.query(
notification.class.NotificationSetting,
{
space
},
(res) => {
settings = new Map(
res.map((setting) => {
return [setting.type, setting]
})
)
settingsReceived = true
}
)
settingQuery.query(notification.class.NotificationSetting, {}, (res) => {
settings = new Map(
res.map((setting) => {
return [setting.type, setting]
})
)
settingsReceived = true
})
const alreadyShown = new Set<Ref<PlatformNotification>>()
@ -99,7 +91,7 @@
const text = notification.text.replace(/<[^>]*>/g, '').trim()
if (text === '') return
const setting = settings.get(notification.type)
const enabled = setting?.enabled ?? provider?.default
const enabled = setting?.enabled
if (!enabled) return
if ((setting?.modifiedOn ?? notification.modifiedOn) < 0) return

View File

@ -0,0 +1,60 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import type { Asset, IntlString } from '@hcengineering/platform'
import { Icon, Label } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
export let icon: Asset | undefined = undefined
export let label: IntlString | undefined = undefined
export let selected: boolean = false
export let expandable = false
const dispatch = createEventDispatcher()
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="antiNav-element"
class:selected
class:expandable
on:click|stopPropagation={() => {
dispatch('click')
}}
>
<div class="an-element__icon">
{#if icon}
<Icon {icon} size={'small'} />
{/if}
</div>
<span class="an-element__label title">
{#if label}<Label {label} />{/if}
</span>
</div>
<style lang="scss">
.expandable {
position: relative;
&::after {
content: '▶';
position: absolute;
top: 50%;
right: 0.5rem;
font-size: 0.375rem;
color: var(--dark-color);
transform: translateY(-50%);
}
}
</style>

View File

@ -0,0 +1,138 @@
<!--
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//
// See the License for the specific language governing permissions and
// limitations under the License.
-->
<script lang="ts">
import { IdMap, Ref, toIdMap } from '@hcengineering/core'
import type {
NotificationGroup,
NotificationProvider,
NotificationSetting,
NotificationType
} from '@hcengineering/notification'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { Grid, Label, ToggleWithLabel } from '@hcengineering/ui'
import notification from '../plugin'
export let group: Ref<NotificationGroup>
export let settings: Map<Ref<NotificationType>, NotificationSetting[]>
const client = getClient()
let types: NotificationType[] = []
let typesMap: IdMap<NotificationType> = new Map()
let providers: NotificationProvider[] = []
let providersMap: IdMap<NotificationProvider> = new Map()
$: load(group)
async function load (group: Ref<NotificationGroup>) {
types = await client.findAll(notification.class.NotificationType, { group })
typesMap = toIdMap(types)
providers = await client.findAll(notification.class.NotificationProvider, {})
providersMap = toIdMap(providers)
}
$: column = providers.length + 1
function getStatus (
settings: Map<Ref<NotificationType>, NotificationSetting[]>,
type: Ref<NotificationType>,
provider: Ref<NotificationProvider>
): boolean {
const setting = getSetting(settings, type, provider)
if (setting !== undefined) return setting.enabled
const prov = providersMap.get(provider)
if (prov === undefined) return false
const typeValue = typesMap.get(type)
if (typeValue === undefined) return false
return typeValue?.providers?.[provider] ?? false
}
function createHandler (type: Ref<NotificationType>, provider: Ref<NotificationProvider>): (evt: any) => void {
return (evt: any) => change(type, provider, evt.detail)
}
async function change (
type: Ref<NotificationType>,
provider: Ref<NotificationProvider>,
value: boolean
): Promise<void> {
const current = getSetting(settings, type, provider)
if (current === undefined) {
await client.createDoc(notification.class.NotificationSetting, notification.space.Notifications, {
attachedTo: provider,
type,
enabled: value
})
} else {
await client.update(current, {
enabled: value
})
}
}
function getSetting (
map: Map<Ref<NotificationType>, NotificationSetting[]>,
type: Ref<NotificationType>,
provider: Ref<NotificationProvider>
): NotificationSetting | undefined {
const typeMap = map.get(type)
if (typeMap === undefined) return
return typeMap.find((p) => p.attachedTo === provider)
}
function getLabel (type: NotificationType): IntlString {
if (type.attachedToClass !== undefined) {
return notification.string.AddedRemoved
}
return notification.string.Change
}
</script>
<div class="flex-grow container">
<Grid {column} columnGap={5} rowGap={1.5}>
{#each types as type}
<div class="flex">
{#if type.generated}
<Label label={getLabel(type)} />:
{/if}
<Label label={type.label} />
</div>
{#each providers as provider (provider._id)}
{#if type.providers[provider._id] !== undefined}
<div class="toggle">
<ToggleWithLabel
label={provider.label}
on={getStatus(settings, type._id, provider._id)}
on:change={createHandler(type._id, provider._id)}
/>
</div>
{:else}
<div />
{/if}
{/each}
{/each}
</Grid>
</div>
<style lang="scss">
.container {
padding: 3rem;
width: fit-content;
}
.toggle {
width: fit-content;
}
</style>

View File

@ -1,5 +1,5 @@
<!--
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2023 Hardcore Engineering Inc.
//
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. You may
@ -13,166 +13,61 @@
// limitations under the License.
-->
<script lang="ts">
import { getCurrentAccount, Ref, Space, TxProcessor } from '@hcengineering/core'
import type { NotificationProvider, NotificationSetting, NotificationType } from '@hcengineering/notification'
import presentation, { createQuery, getClient } from '@hcengineering/presentation'
import { Button, Grid, Icon, Label, ToggleWithLabel } from '@hcengineering/ui'
import { Ref } from '@hcengineering/core'
import type { NotificationGroup, NotificationSetting, NotificationType } from '@hcengineering/notification'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Label } from '@hcengineering/ui'
import notification from '../plugin'
import GroupElement from './GroupElement.svelte'
import NotificationGroupSetting from './NotificationGroupSetting.svelte'
const accountId = getCurrentAccount()._id
const typeQuery = createQuery()
const providersQuery = createQuery()
const settingsQuery = createQuery()
const client = getClient()
const space = accountId as string as Ref<Space>
let disabled = true
let types: NotificationType[] = []
let providers: NotificationProvider[] = []
let settings: Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>> = new Map<
Ref<NotificationType>,
Map<Ref<NotificationProvider>, NotificationSetting>
>()
let oldSettings: Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>> = new Map<
Ref<NotificationType>,
Map<Ref<NotificationProvider>, NotificationSetting>
>()
typeQuery.query(notification.class.NotificationType, { hidden: false }, (res) => (types = res))
providersQuery.query(notification.class.NotificationProvider, {}, (res) => (providers = res))
settingsQuery.query(notification.class.NotificationSetting, { space }, (res) => {
settings = convertToMap(res)
oldSettings = convertToMap(client.getHierarchy().clone(res))
let groups: NotificationGroup[] = []
client.findAll(notification.class.NotificationGroup, {}).then((res) => {
groups = res
group = res[0]._id
})
function convertToMap (
settings: NotificationSetting[]
): Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>> {
const result = new Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>>()
for (const setting of settings) {
setSetting(result, setting)
let settings: Map<Ref<NotificationType>, NotificationSetting[]> = new Map()
const query = createQuery()
query.query(notification.class.NotificationSetting, {}, (res) => {
console.log('settings updated')
settings = new Map()
for (const value of res) {
const arr = settings.get(value.type) ?? []
arr.push(value)
settings.set(value.type, arr)
}
return result
}
settings = settings
})
function change (type: Ref<NotificationType>, provider: Ref<NotificationProvider>, value: boolean): void {
const current = getSetting(settings, type, provider)
if (current === undefined) {
const tx = client.txFactory.createTxCreateDoc(notification.class.NotificationSetting, space, {
provider,
type,
enabled: value
})
const setting = TxProcessor.createDoc2Doc(tx)
setSetting(settings, setting)
} else {
current.enabled = value
}
disabled = false
}
function getSetting (
map: Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>>,
type: Ref<NotificationType>,
provider: Ref<NotificationProvider>
): NotificationSetting | undefined {
const typeMap = map.get(type)
if (typeMap === undefined) return
return typeMap.get(provider)
}
function setSetting (
result: Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>>,
setting: NotificationSetting
): void {
let typeMap = result.get(setting.type)
if (typeMap === undefined) {
typeMap = new Map<Ref<NotificationProvider>, NotificationSetting>()
result.set(setting.type, typeMap)
}
typeMap.set(setting.provider, setting)
}
async function save (): Promise<void> {
disabled = true
const promises: Promise<any>[] = []
for (const type of settings.values()) {
for (const setting of type.values()) {
const old = getSetting(oldSettings, setting.type, setting.provider)
if (old === undefined) {
promises.push(client.createDoc(setting._class, setting.space, setting))
} else if (old.enabled !== setting.enabled) {
promises.push(
client.updateDoc(old._class, old.space, old._id, {
enabled: setting.enabled
})
)
}
}
}
try {
await Promise.all(promises)
} catch (e) {
console.log(e)
}
}
$: column = providers.length + 1
function getStatus (
map: Map<Ref<NotificationType>, Map<Ref<NotificationProvider>, NotificationSetting>>,
type: Ref<NotificationType>,
provider: Ref<NotificationProvider>
): boolean {
const setting = getSetting(map, type, provider)
if (setting !== undefined) return setting.enabled
const prov = providers.find((p) => p._id === provider)
if (prov === undefined) return false
return prov.default
}
const createHandler = (type: Ref<NotificationType>, provider: Ref<NotificationProvider>): ((evt: any) => void) => {
return (evt: any) => change(type, provider, evt.detail)
}
let group: Ref<NotificationGroup> | undefined = undefined
</script>
<div class="antiComponent">
<div class="ac-header short divide">
<div class="ac-header__icon"><Icon icon={notification.icon.Notifications} size={'medium'} /></div>
<div class="ac-header__title"><Label label={notification.string.Notifications} /></div>
</div>
<div class="flex-row-stretch flex-grow container">
<div class="flex-col flex-grow">
<div class="flex-grow">
<Grid {column} columnGap={5} rowGap={1.5}>
{#each types as type (type._id)}
<Label label={type.label} />
{#each providers as provider (provider._id)}
<ToggleWithLabel
label={provider.label}
on={getStatus(settings, type._id, provider._id)}
on:change={createHandler(type._id, provider._id)}
/>
{/each}
{/each}
</Grid>
</div>
<div class="flex-row-reverse">
<Button
label={presentation.string.Save}
{disabled}
kind={'primary'}
on:click={() => {
save()
}}
/>
</div>
<div class="flex h-full">
<div class="antiPanel-navigator filled indent">
<div class="antiNav-header">
<span class="fs-title overflow-label">
<Label label={notification.string.Notifications} />
</span>
</div>
{#each groups as gr}
<GroupElement
icon={gr.icon}
label={gr.label}
selected={gr._id === group}
on:click={() => {
group = gr._id
}}
/>
{/each}
</div>
<div class="antiPanel-component border-left filled">
{#if group}
<NotificationGroupSetting {group} {settings} />
{/if}
</div>
</div>
<style lang="scss">
.container {
padding: 3rem;
}
</style>

View File

@ -26,6 +26,8 @@ export default mergeIds(notificationId, notification, {
Remove: '' as IntlString,
RemoveAll: '' as IntlString,
MarkAsRead: '' as IntlString,
MarkAllAsRead: '' as IntlString
MarkAllAsRead: '' as IntlString,
Change: '' as IntlString,
AddedRemoved: '' as IntlString
}
})

View File

@ -34,6 +34,7 @@
"dependencies": {
"@hcengineering/platform": "^0.6.8",
"@hcengineering/core": "^0.6.23",
"@hcengineering/preference": "^0.6.4",
"@hcengineering/setting": "^0.6.5",
"@hcengineering/ui": "^0.6.6"
},

View File

@ -13,12 +13,25 @@
// limitations under the License.
//
import { Account, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestamp, TxCUD } from '@hcengineering/core'
import {
Account,
AnyAttribute,
AttachedDoc,
Class,
Doc,
Mixin,
Ref,
Space,
Timestamp,
Tx,
TxCUD
} from '@hcengineering/core'
import type { Asset, IntlString, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { IntegrationType } from '@hcengineering/setting'
import { AnyComponent } from '@hcengineering/ui'
import { Writable } from './types'
import { Preference } from '@hcengineering/preference'
export * from './types'
/**
@ -64,9 +77,17 @@ export enum NotificationStatus {
/**
* @public
*/
export interface NotificationType extends Doc {
hidden: boolean
export interface NotificationGroup extends Doc {
label: IntlString
icon: Asset
// using for autogenerated settings
objectClass?: Ref<Class<Doc>>
}
/**
* @public
*/
export interface NotificationTemplate {
textTemplate: string
htmlTemplate: string
subjectTemplate: string
@ -75,17 +96,40 @@ export interface NotificationType extends Doc {
/**
* @public
*/
export interface NotificationProvider extends Doc {
export interface NotificationType extends Doc {
// For show/hide with attributes
attribute?: Ref<AnyAttribute>
// Is autogenerated
generated: boolean
// allowed to to change setting (probably we should show it, but disable toggle??)
hidden: boolean
label: IntlString
default: boolean
group: Ref<NotificationGroup>
txClasses: Ref<Class<Tx>>[]
objectClass: Ref<Class<Doc>>
// check parent doc class
attachedToClass?: Ref<Class<Doc>>
// use for update/mixin txes
field?: string
// allowed providers and default value for it
providers: Record<Ref<NotificationProvider>, boolean>
// templates for email (and browser/push?)
templates?: NotificationTemplate
}
/**
* @public
*/
export interface NotificationSetting extends Doc {
export interface NotificationProvider extends Doc {
label: IntlString
}
/**
* @public
*/
export interface NotificationSetting extends Preference {
attachedTo: Ref<NotificationProvider>
type: Ref<NotificationType>
provider: Ref<NotificationProvider>
enabled: boolean
}
@ -181,15 +225,16 @@ const notification = plugin(notificationId, {
NotificationType: '' as Ref<Class<NotificationType>>,
NotificationProvider: '' as Ref<Class<NotificationProvider>>,
NotificationSetting: '' as Ref<Class<NotificationSetting>>,
DocUpdates: '' as Ref<Class<DocUpdates>>
DocUpdates: '' as Ref<Class<DocUpdates>>,
NotificationGroup: '' as Ref<Class<NotificationGroup>>
},
ids: {
MentionNotification: '' as Ref<NotificationType>,
DMNotification: '' as Ref<NotificationType>,
NotificationSettings: '' as Ref<Doc>
},
providers: {
PlatformNotification: '' as Ref<NotificationProvider>,
BrowserNotification: '' as Ref<NotificationProvider>,
EmailNotification: '' as Ref<NotificationProvider>,
NotificationSettings: '' as Ref<Doc>
EmailNotification: '' as Ref<NotificationProvider>
},
integrationType: {
MobileApp: '' as Ref<IntegrationType>

View File

@ -71,7 +71,7 @@
"CantStatusDelete": "Can't delete status",
"CantStatusDeleteError": "There are objects in the given state. Move or delete them first.",
"Tasks": "Tasks",
"Assigned": "Assigned to me",
"AssignedToMe": "Assigned to me",
"TodoItems": "Todos",
"Dashboard": "Dashboard",
"AllTime": "All time",

View File

@ -71,7 +71,7 @@
"CantStatusDelete": "Невозможно удалить статус",
"CantStatusDeleteError": "Есть объекты с данным статусом. Сначала переместите или удалите их. ",
"Tasks": "Задачи",
"Assigned": "Назначения",
"AssignedToMe": "Назначено на меня",
"TodoItems": "Todos",
"Dashboard": "Дашборд",
"AllTime": "Все время",

View File

@ -68,7 +68,6 @@ export default mergeIds(taskId, task, {
RelatedIssues: '' as IntlString,
Tasks: '' as IntlString,
Assigned: '' as IntlString,
Task: '' as IntlString,
AllTime: '' as IntlString
},

View File

@ -229,6 +229,7 @@ const task = plugin(taskId, {
Projects: '' as IntlString,
ManageProjectStatues: '' as IntlString,
TodoItems: '' as IntlString,
AssignedToMe: '' as IntlString,
Dashboard: '' as IntlString
},
class: {

View File

@ -288,6 +288,7 @@
"Saved": "Saved...",
"CreatedIssue": "Created issue",
"CreatedSubIssue": "Created sub-issue",
"ChangeStatus": "Change status",
"ConfigLabel": "Tracker",
"ConfigDescription": "Extension to manage work items and do all jobs done."
},

View File

@ -288,6 +288,7 @@
"Saved": "Сохранено...",
"CreatedIssue": "Создал(а) задачу",
"CreatedSubIssue": "Создал(а) подзадачу",
"ChangeStatus": "Изменение статуса",
"ConfigLabel": "Трекер",
"ConfigDescription": "Расширение по управлению задачами, чтобы все было в срок."
},

View File

@ -150,7 +150,8 @@
notificationQuery.query(
notification.class.DocUpdates,
{
user: account._id
user: account._id,
hidden: false
},
(res) => {
hasNotification = res.some((p) => p.txes.length > 0)

View File

@ -13,16 +13,10 @@
// limitations under the License.
//
import chunter, {
chunterId,
ChunterMessage,
ChunterSpace,
Comment,
Message,
ThreadMessage
} from '@hcengineering/chunter'
import chunter, { chunterId, ChunterSpace, Comment, Message, ThreadMessage } from '@hcengineering/chunter'
import { EmployeeAccount } from '@hcengineering/contact'
import core, {
Account,
Class,
concatLink,
Doc,
@ -39,11 +33,10 @@ import core, {
TxRemoveDoc,
TxUpdateDoc
} from '@hcengineering/core'
import notification, { Collaborators } from '@hcengineering/notification'
import notification, { Collaborators, NotificationType } from '@hcengineering/notification'
import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { getEmployeeAccountById } from '@hcengineering/server-notification'
import { createNotificationTxes, getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources'
import { getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources'
import { workbenchId } from '@hcengineering/workbench'
/**
@ -288,52 +281,41 @@ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<
/**
* @public
*/
export async function DMTrigger (tx: Tx, control: TriggerControl): Promise<Tx[]> {
if (tx._class !== core.class.TxCollectionCUD) return []
const hierarchy = control.hierarchy
const ctx = tx as TxCollectionCUD<ChunterSpace, Message>
if (!hierarchy.isDerived(ctx.tx.objectClass, chunter.class.Message)) {
return []
}
const actualTx = TxProcessor.extractTx(tx)
if (actualTx._class !== core.class.TxCreateDoc) {
return []
}
const doc = TxProcessor.createDoc2Doc(actualTx as TxCreateDoc<ChunterMessage>)
const dms = await control.findAll(chunter.class.DirectMessage, { _id: doc.space })
if (dms.length === 0) {
return []
}
const sender = await getEmployeeAccountById(ctx.tx.modifiedBy, control)
if (sender === undefined) return []
const res: Tx[] = []
for (const member of dms[0].members) {
const receiver = await getEmployeeAccountById(member, control)
if (receiver === undefined) continue
if (receiver._id === sender._id) continue
const createNotificationTx = await createNotificationTxes(
control,
ctx,
notification.ids.DMNotification,
doc,
sender,
receiver,
doc.content
)
res.push(...createNotificationTx)
}
return res
export async function IsDirectMessagee (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const space = (await control.findAll(chunter.class.DirectMessage, { _id: doc.space }))[0]
return space !== undefined
}
/**
* @public
*/
export async function IsChannelMessagee (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
const space = (await control.findAll(chunter.class.Channel, { _id: doc.space }))[0]
return space !== undefined
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
ChunterTrigger,
DMTrigger
ChunterTrigger
},
function: {
CommentRemove,
ChannelHTMLPresenter: channelHTMLPresenter,
ChannelTextPresenter: channelTextPresenter
ChannelTextPresenter: channelTextPresenter,
IsDirectMessagee,
IsChannelMessagee
}
})

View File

@ -17,7 +17,7 @@ import { Class, Doc, DocumentQuery, FindOptions, FindResult, Hierarchy, Ref } fr
import type { Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { TriggerFunc } from '@hcengineering/server-core'
import { Presenter } from '@hcengineering/server-notification'
import { Presenter, TypeMatchFunc } from '@hcengineering/server-notification'
/**
* @public
@ -29,8 +29,7 @@ export const serverChunterId = 'server-chunter' as Plugin
*/
export default plugin(serverChunterId, {
trigger: {
ChunterTrigger: '' as Resource<TriggerFunc>,
DMTrigger: '' as Resource<TriggerFunc>
ChunterTrigger: '' as Resource<TriggerFunc>
},
function: {
CommentRemove: '' as Resource<
@ -45,6 +44,8 @@ export default plugin(serverChunterId, {
) => Promise<Doc[]>
>,
ChannelHTMLPresenter: '' as Resource<Presenter>,
ChannelTextPresenter: '' as Resource<Presenter>
ChannelTextPresenter: '' as Resource<Presenter>,
IsDirectMessagee: '' as TypeMatchFunc,
IsChannelMessagee: '' as TypeMatchFunc
}
})

View File

@ -39,7 +39,7 @@ import notification, { NotificationType } from '@hcengineering/notification'
import { translate } from '@hcengineering/platform'
import { TriggerControl } from '@hcengineering/server-core'
import { getEmployee, getEmployeeAccountById } from '@hcengineering/server-notification'
import { getContent } from '@hcengineering/server-notification-resources'
import { getContent, isAllowed } from '@hcengineering/server-notification-resources'
async function getOldDepartment (
currentTx: TxMixin<Employee, Staff> | TxUpdateDoc<Employee>,
@ -209,6 +209,17 @@ async function getEmailNotification (
}
}
// should respect employee settings
const accounts = await control.modelDb.findAll(contact.class.EmployeeAccount, {
employee: { $in: Array.from(contacts.values()) as Ref<Employee>[] }
})
for (const account of accounts) {
const allowed = await isAllowed(control, account._id, type, notification.providers.EmailNotification)
if (!allowed) {
contacts.delete(account.employee)
}
}
const channels = await control.findAll(contact.class.Channel, {
provider: contact.channelProvider.Email,
attachedTo: { $in: Array.from(contacts) }

View File

@ -13,21 +13,10 @@
// limitations under the License.
//
import core, {
AttachedDoc,
concatLink,
Doc,
Tx,
TxCollectionCUD,
TxCreateDoc,
TxCUD,
TxProcessor,
TxUpdateDoc
} from '@hcengineering/core'
import lead, { Lead, leadId } from '@hcengineering/lead'
import { concatLink, Doc } from '@hcengineering/core'
import { Lead, leadId } from '@hcengineering/lead'
import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { addAssigneeNotification } from '@hcengineering/server-task-resources'
import view from '@hcengineering/view'
import { workbenchId } from '@hcengineering/workbench'
@ -50,68 +39,10 @@ export async function leadTextPresenter (doc: Doc): Promise<string> {
return `LEAD-${lead.number}`
}
/**
* @public
*/
export async function OnLeadUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx)
const res: Tx[] = []
const cud = actualTx as TxCUD<Doc>
if (actualTx._class === core.class.TxCreateDoc) {
await handleLeadCreate(control, cud, res, tx)
}
if (actualTx._class === core.class.TxUpdateDoc) {
await handleLeadUpdate(control, cud, res, tx)
}
return res
}
async function handleLeadCreate (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[], tx: Tx): Promise<void> {
if (control.hierarchy.isDerived(cud.objectClass, lead.class.Lead)) {
const createTx = cud as TxCreateDoc<Lead>
const leadValue = TxProcessor.createDoc2Doc(createTx)
if (leadValue.assignee != null) {
await addAssigneeNotification(
control,
res,
leadValue,
leadValue.assignee,
tx as TxCollectionCUD<Lead, AttachedDoc>
)
}
}
}
async function handleLeadUpdate (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[], tx: Tx): Promise<void> {
if (control.hierarchy.isDerived(cud.objectClass, lead.class.Lead)) {
const updateTx = cud as TxUpdateDoc<Lead>
if (updateTx.operations.assignee != null) {
const leadValue = (await control.findAll(lead.class.Lead, { _id: updateTx.objectId }, { limit: 1 })).shift()
if (leadValue?.assignee != null) {
await addAssigneeNotification(
control,
res,
leadValue,
leadValue.assignee,
tx as TxCollectionCUD<Lead, AttachedDoc>
)
}
}
}
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
function: {
LeadHTMLPresenter: leadHTMLPresenter,
LeadTextPresenter: leadTextPresenter
},
trigger: {
OnLeadUpdate
}
})

View File

@ -15,7 +15,6 @@
import type { Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { TriggerFunc } from '@hcengineering/server-core'
import { Presenter } from '@hcengineering/server-notification'
/**
@ -30,8 +29,5 @@ export default plugin(serverLeadId, {
function: {
LeadHTMLPresenter: '' as Resource<Presenter>,
LeadTextPresenter: '' as Resource<Presenter>
},
trigger: {
OnLeadUpdate: '' as Resource<TriggerFunc>
}
})

View File

@ -22,13 +22,14 @@ import core, {
ArrOf,
AttachedDoc,
Class,
Collection,
Data,
Doc,
DocumentUpdate,
Hierarchy,
MixinUpdate,
Ref,
RefTo,
Space,
Tx,
TxCUD,
TxCollectionCUD,
@ -92,28 +93,9 @@ export async function OnBacklinkCreate (tx: Tx, control: TriggerControl): Promis
}
)
res.push(collabTx)
res = res.concat(
await createCollabDocInfo(
[receiver._id],
tx as TxCUD<Doc>,
doc._id,
doc._class,
control,
tx._id as Ref<TxCUD<Doc>>
)
)
}
res = res.concat(await createCollabDocInfo([receiver._id], control, tx as TxCUD<Doc>, doc))
}
const notifyTx = await createNotificationTxes(
control,
ptx,
notification.ids.MentionNotification,
doc,
sender,
receiver,
backlink.message
)
res = [...res, ...notifyTx]
return res
}
@ -132,18 +114,22 @@ function checkTx (ptx: TxCollectionCUD<Doc, Backlink>, hierarchy: Hierarchy): bo
return true
}
async function isAllowed (
/**
* @public
*/
export async function isAllowed (
control: TriggerControl,
receiver: EmployeeAccount,
receiver: Ref<EmployeeAccount>,
typeId: Ref<NotificationType>,
providerId: Ref<NotificationProvider>
): Promise<boolean> {
const setting = (
await control.findAll(
notification.class.NotificationSetting,
{
provider: providerId,
type: notification.ids.MentionNotification,
space: receiver._id as unknown as Ref<Space>
attachedTo: providerId,
type: typeId,
modifiedBy: receiver
},
{ limit: 1 }
)
@ -151,13 +137,13 @@ async function isAllowed (
if (setting !== undefined) {
return setting.enabled
}
const provider = (
await control.modelDb.findAll(notification.class.NotificationProvider, {
_id: providerId
const type = (
await control.modelDb.findAll(notification.class.NotificationType, {
_id: typeId
})
)[0]
if (provider === undefined) return false
return provider.default
if (type === undefined) return false
return type.providers[providerId] ?? false
}
async function getTextPart (doc: Doc, control: TriggerControl): Promise<string | undefined> {
@ -205,10 +191,11 @@ export async function getContent (
const textPart = await getTextPart(doc, control)
if (textPart === undefined) return
const text = fillTemplate(notificationType.textTemplate, sender, textPart, data)
if (notificationType.templates === undefined) return
const text = fillTemplate(notificationType.templates.textTemplate, sender, textPart, data)
const htmlPart = await getHtmlPart(doc, control)
const html = fillTemplate(notificationType.htmlTemplate, sender, htmlPart ?? textPart, data)
const subject = fillTemplate(notificationType.subjectTemplate, sender, textPart, data)
const html = fillTemplate(notificationType.templates.htmlTemplate, sender, htmlPart ?? textPart, data)
const subject = fillTemplate(notificationType.templates.subjectTemplate, sender, textPart, data)
return {
text,
html,
@ -216,36 +203,33 @@ export async function getContent (
}
}
/**
* @public
*/
export async function createNotificationTxes (
async function createEmailNotificationTxes (
control: TriggerControl,
ptx: TxCollectionCUD<Doc, AttachedDoc>,
tx: Tx,
type: Ref<NotificationType>,
doc: Doc | undefined,
sender: EmployeeAccount,
receiver: EmployeeAccount,
senderId: Ref<EmployeeAccount>,
receiverId: Ref<EmployeeAccount>,
data: string = ''
): Promise<Tx[]> {
const res: Tx[] = []
): Promise<Tx | undefined> {
const sender = (await control.modelDb.findAll(contact.class.EmployeeAccount, { _id: senderId }))[0]
if (sender === undefined) return
const receiver = (await control.modelDb.findAll(contact.class.EmployeeAccount, { _id: receiverId }))[0]
if (receiver === undefined) return
const senderName = formatName(sender.name)
const content = await getContent(doc, senderName, type, control, data)
if (content !== undefined && (await isAllowed(control, receiver, notification.ids.EmailNotification))) {
const emailTx = await getEmailNotificationTx(ptx, senderName, content.text, content.html, content.subject, receiver)
if (emailTx !== undefined) {
res.push(emailTx)
}
if (content !== undefined) {
return await getEmailNotificationTx(tx, senderName, content.text, content.html, content.subject, receiver)
}
return res
}
async function getEmailNotificationTx (
ptx: TxCollectionCUD<Doc, AttachedDoc>,
tx: Tx,
sender: string,
text: string,
html: string,
@ -259,8 +243,8 @@ async function getEmailNotificationTx (
space: core.space.DerivedTx,
objectClass: notification.class.EmailNotification,
objectSpace: notification.space.Notifications,
modifiedOn: ptx.modifiedOn,
modifiedBy: ptx.modifiedBy,
modifiedOn: tx.modifiedOn,
modifiedBy: tx.modifiedBy,
attributes: {
status: 'new',
sender,
@ -410,47 +394,152 @@ export async function getDocCollaborators (
return Array.from(collaborators.values())
}
function fieldUpdated (field: string, ops: DocumentUpdate<Doc> | MixinUpdate<Doc, Doc>): boolean {
if ((ops as any)[field] !== undefined) return true
if ((ops.$pull as any)?.[field] !== undefined) return true
if ((ops.$push as any)?.[field] !== undefined) return true
return false
}
function isTypeMatched (
control: TriggerControl,
type: NotificationType,
tx: TxCUD<Doc>,
extractedTx: TxCUD<Doc>
): boolean {
const targetClass = control.hierarchy.getBaseClass(type.objectClass)
if (!type.txClasses.includes(extractedTx._class)) return false
if (!control.hierarchy.isDerived(extractedTx.objectClass, targetClass)) return false
if (tx._class === core.class.TxCollectionCUD && type.attachedToClass !== undefined) {
if (!control.hierarchy.isDerived(tx.objectClass, type.attachedToClass)) return false
}
if (type.field !== undefined) {
if (extractedTx._class === core.class.TxUpdateDoc) {
if (!fieldUpdated(type.field, (extractedTx as TxUpdateDoc<Doc>).operations)) return false
}
if (extractedTx._class === core.class.TxMixin) {
if (!fieldUpdated(type.field, (extractedTx as TxMixin<Doc, Doc>).attributes)) return false
}
}
return true
}
async function getMatchedTypes (control: TriggerControl, tx: TxCUD<Doc>): Promise<NotificationType[]> {
const allTypes = await control.modelDb.findAll(notification.class.NotificationType, {})
const extractedTx = TxProcessor.extractTx(tx) as TxCUD<Doc>
const filtered: NotificationType[] = []
for (const type of allTypes) {
if (isTypeMatched(control, type, tx, extractedTx)) {
filtered.push(type)
}
}
return filtered
}
interface NotifyResult {
allowed: boolean
emails: NotificationType[]
}
async function isShouldNotify (
control: TriggerControl,
tx: TxCUD<Doc>,
object: Doc,
user: Ref<Account>
): Promise<NotifyResult> {
let allowed = false
const emailTypes: NotificationType[] = []
const types = await getMatchedTypes(control, tx)
for (const type of types) {
if (control.hierarchy.hasMixin(type, serverNotification.mixin.TypeMatch)) {
const mixin = control.hierarchy.as(type, serverNotification.mixin.TypeMatch)
if (mixin.func !== undefined) {
const f = await getResource(mixin.func)
const res = await f(tx, object, user, type, control)
if (!res) continue
}
}
if (await isAllowed(control, user as Ref<EmployeeAccount>, type._id, notification.providers.PlatformNotification)) {
allowed = true
}
if (await isAllowed(control, user as Ref<EmployeeAccount>, type._id, notification.providers.EmailNotification)) {
emailTypes.push(type)
}
}
return {
allowed,
emails: emailTypes
}
}
async function getNotificationTxes (
control: TriggerControl,
object: Doc,
originTx: TxCUD<Doc>,
target: Ref<Account>,
docUpdates: DocUpdates[]
): Promise<Tx[]> {
if (originTx.modifiedBy === target) return []
const res: Tx[] = []
const allowed = await isShouldNotify(control, originTx, object, target)
if (allowed.allowed) {
const current = docUpdates.find((p) => p.user === target)
if (current === undefined) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, {
user: target,
attachedTo: object._id,
attachedToClass: object._class,
hidden: false,
lastTx: originTx._id,
lastTxTime: originTx.modifiedOn,
txes: [[originTx._id, originTx.modifiedOn]]
})
)
} else {
res.push(
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
$push: {
txes: [originTx._id, originTx.modifiedOn]
}
})
)
res.push(
control.txFactory.createTxUpdateDoc(current._class, current.space, current._id, {
lastTx: originTx._id,
lastTxTime: originTx.modifiedOn,
hidden: false
})
)
}
}
for (const type of allowed.emails) {
const emailTx = await createEmailNotificationTxes(
control,
originTx,
type._id,
object,
originTx.modifiedBy as Ref<EmployeeAccount>,
target as Ref<EmployeeAccount>
)
if (emailTx !== undefined) {
res.push(emailTx)
}
}
return res
}
async function createCollabDocInfo (
collaborators: Ref<Account>[],
tx: TxCUD<Doc>,
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>,
control: TriggerControl,
txId?: Ref<TxCUD<Doc>>
): Promise<TxCUD<DocUpdates>[]> {
const res: TxCUD<DocUpdates>[] = []
originTx: TxCUD<Doc>,
object: Doc
): Promise<Tx[]> {
let res: Tx[] = []
const targets = new Set(collaborators)
const docs = await control.findAll(notification.class.DocUpdates, { attachedTo: objectId })
for (const doc of docs) {
if (tx.modifiedBy === doc.user || !targets.delete(doc.user)) continue
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
$push: {
txes: [txId ?? tx._id, tx.modifiedOn]
}
})
)
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
lastTx: txId ?? tx._id,
lastTxTime: tx.modifiedOn,
hidden: false
})
)
}
const docUpdates = await control.findAll(notification.class.DocUpdates, { attachedTo: object._id })
for (const target of targets) {
if (tx.modifiedBy === target) continue
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocUpdates, notification.space.Notifications, {
user: target,
attachedTo: objectId,
attachedToClass: objectClass,
hidden: false,
lastTx: txId ?? tx._id,
lastTxTime: tx.modifiedOn,
txes: [[txId ?? tx._id, tx.modifiedOn]]
})
)
res = res.concat(await getNotificationTxes(control, object, originTx, target, docUpdates))
}
return res
}
@ -481,7 +570,7 @@ export function getMixinTx (
export async function createCollaboratorDoc (
tx: TxCreateDoc<Doc>,
control: TriggerControl,
txId?: Ref<TxCUD<Doc>>
originTx: TxCUD<Doc>
): Promise<Tx[]> {
const res: Tx[] = []
const hierarchy = control.hierarchy
@ -491,7 +580,7 @@ export async function createCollaboratorDoc (
const collaborators = await getDocCollaborators(doc, mixin, control)
const mixinTx = getMixinTx(tx, control, collaborators)
const notificationTxes = await createCollabDocInfo(collaborators, tx, doc._id, doc._class, control, txId)
const notificationTxes = await createCollabDocInfo(collaborators, control, originTx, doc)
res.push(mixinTx)
res.push(...notificationTxes)
}
@ -501,15 +590,19 @@ export async function createCollaboratorDoc (
/**
* @public
*/
export async function collaboratorDocHandler (tx: Tx, control: TriggerControl, txId?: Ref<TxCUD<Doc>>): Promise<Tx[]> {
export async function collaboratorDocHandler (
tx: TxCUD<Doc>,
control: TriggerControl,
originTx?: TxCUD<Doc>
): Promise<Tx[]> {
switch (tx._class) {
case core.class.TxCreateDoc:
if (tx.space === core.space.DerivedTx) return []
return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, txId)
return await createCollaboratorDoc(tx as TxCreateDoc<Doc>, control, originTx ?? tx)
case core.class.TxUpdateDoc:
case core.class.TxMixin:
if (tx.space === core.space.DerivedTx) return []
return await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, txId)
return await updateCollaboratorDoc(tx as TxUpdateDoc<Doc>, control, originTx ?? tx)
case core.class.TxRemoveDoc:
return await removeCollaboratorDoc(tx as TxRemoveDoc<Doc>, control)
case core.class.TxCollectionCUD:
@ -521,15 +614,13 @@ export async function collaboratorDocHandler (tx: Tx, control: TriggerControl, t
async function collectionCollabDoc (tx: TxCollectionCUD<Doc, AttachedDoc>, control: TriggerControl): Promise<Tx[]> {
const actualTx = TxProcessor.extractTx(tx)
let res = await collaboratorDocHandler(actualTx, control, tx._id)
let res = await collaboratorDocHandler(actualTx as TxCUD<Doc>, control, tx)
if ([core.class.TxCreateDoc, core.class.TxRemoveDoc].includes(actualTx._class)) {
const doc = (await control.findAll(tx.objectClass, { _id: tx.objectId }, { limit: 1 }))[0]
if (doc !== undefined) {
if (control.hierarchy.hasMixin(doc, notification.mixin.Collaborators)) {
const collabMixin = control.hierarchy.as(doc, notification.mixin.Collaborators)
res = res.concat(
await createCollabDocInfo(collabMixin.collaborators, tx, tx.objectId, tx.objectClass, control, tx._id)
)
res = res.concat(await createCollabDocInfo(collabMixin.collaborators, control, tx, doc))
}
}
}
@ -591,7 +682,7 @@ function isMixinTx (tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>): tx is TxMixin<Doc
async function updateCollaboratorDoc (
tx: TxUpdateDoc<Doc> | TxMixin<Doc, Doc>,
control: TriggerControl,
txId?: Ref<TxCUD<Doc>>
originTx: TxCUD<Doc>
): Promise<Tx[]> {
const hierarchy = control.hierarchy
let res: Tx[] = []
@ -619,19 +710,12 @@ async function updateCollaboratorDoc (
)
}
res = res.concat(
await createCollabDocInfo(
[...collabMixin.collaborators, ...newCollaborators],
tx,
doc._id,
doc._class,
control,
txId
)
await createCollabDocInfo([...collabMixin.collaborators, ...newCollaborators], control, originTx, doc)
)
} else {
const collaborators = await getDocCollaborators(doc, mixin, control)
res.push(getMixinTx(tx, control, collaborators))
res = res.concat(await createCollabDocInfo(collaborators, tx, doc._id, doc._class, control, txId))
res = res.concat(await createCollabDocInfo(collaborators, control, originTx, doc))
}
return res
@ -675,6 +759,77 @@ export async function OnAddCollborator (tx: Tx, control: TriggerControl): Promis
return result
}
/**
* @public
*/
export async function isUserInFieldValue (
tx: Tx,
doc: Doc,
user: Ref<Account>,
type: NotificationType,
control: TriggerControl
): Promise<boolean> {
if (type.field === undefined) return false
const value = (doc as any)[type.field]
if (value === undefined) return false
if (Array.isArray(value)) {
return value.includes(user)
} else {
return value === user
}
}
/**
* @public
*/
export async function OnAttributeCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
if (tx._class !== core.class.TxCreateDoc) return []
const ctx = tx as TxCreateDoc<AnyAttribute>
if (ctx.objectClass !== core.class.Attribute) return []
const attribute = TxProcessor.createDoc2Doc(ctx)
const group = (await control.findAll(notification.class.NotificationGroup, { objectClass: attribute.attributeOf }))[0]
if (group === undefined) return []
const isCollection: boolean = core.class.Collection === attribute.type._class
const objectClass = !isCollection ? attribute.attributeOf : (attribute.type as Collection<AttachedDoc>).of
const txClasses = !isCollection
? [control.hierarchy.isMixin(attribute.attributeOf) ? core.class.TxMixin : core.class.TxUpdateDoc]
: [core.class.TxCreateDoc, core.class.TxRemoveDoc]
const data: Data<NotificationType> = {
group: group._id,
generated: true,
objectClass,
txClasses,
hidden: false,
providers: {
[notification.providers.PlatformNotification]: false
},
label: attribute.label
}
if (isCollection) {
data.attachedToClass = attribute.attributeOf
}
const id =
`${notification.class.NotificationType}_${attribute.attributeOf}_${attribute.name}` as Ref<NotificationType>
const res = control.txFactory.createTxCreateDoc(notification.class.NotificationType, core.space.Model, data, id)
return [res]
}
/**
* @public
*/
export async function OnAttributeUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
if (tx._class !== core.class.TxUpdateDoc) return []
const ctx = tx as TxUpdateDoc<AnyAttribute>
if (ctx.objectClass !== core.class.Attribute) return []
if (ctx.operations.hidden === undefined) return []
const type = (await control.findAll(notification.class.NotificationType, { attribute: ctx.objectId }))[0]
if (type === undefined) return []
const res = control.txFactory.createTxUpdateDoc(type._class, type.space, type._id, {
hidden: ctx.operations.hidden
})
return [res]
}
export * from './types'
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -684,6 +839,11 @@ export default async () => ({
CollaboratorDocHandler: collaboratorDocHandler,
OnUpdateLastView,
UpdateLastView,
OnAddCollborator
OnAddCollborator,
OnAttributeCreate,
OnAttributeUpdate
},
function: {
IsUserInFieldValue: isUserInFieldValue
}
})

View File

@ -15,8 +15,8 @@
//
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
import { Account, Class, Doc, Mixin, Ref, TxCreateDoc, TxFactory, TxUpdateDoc } from '@hcengineering/core'
import notification, { LastView } from '@hcengineering/notification'
import { Account, Class, Doc, Mixin, Ref, Tx, TxCreateDoc, TxFactory, TxUpdateDoc } from '@hcengineering/core'
import notification, { LastView, NotificationType } from '@hcengineering/notification'
import { Plugin, Resource, plugin } from '@hcengineering/platform'
import type { TriggerControl, TriggerFunc } from '@hcengineering/server-core'
@ -166,19 +166,39 @@ export interface TextPresenter extends Class<Doc> {
presenter: Resource<Presenter>
}
/**
* @public
*/
export type TypeMatchFunc = Resource<
(tx: Tx, doc: Doc, user: Ref<Account>, type: NotificationType, control: TriggerControl) => Promise<boolean>
>
/**
* @public
*/
export interface TypeMatch extends NotificationType {
func: TypeMatchFunc
}
/**
* @public
*/
export default plugin(serverNotificationId, {
mixin: {
HTMLPresenter: '' as Ref<Mixin<HTMLPresenter>>,
TextPresenter: '' as Ref<Mixin<TextPresenter>>
TextPresenter: '' as Ref<Mixin<TextPresenter>>,
TypeMatch: '' as Ref<Mixin<TypeMatch>>
},
trigger: {
OnBacklinkCreate: '' as Resource<TriggerFunc>,
UpdateLastView: '' as Resource<TriggerFunc>,
OnUpdateLastView: '' as Resource<TriggerFunc>,
CollaboratorDocHandler: '' as Resource<TriggerFunc>,
OnAddCollborator: '' as Resource<TriggerFunc>
OnAddCollborator: '' as Resource<TriggerFunc>,
OnAttributeCreate: '' as Resource<TriggerFunc>,
OnAttributeUpdate: '' as Resource<TriggerFunc>
},
function: {
IsUserInFieldValue: '' as TypeMatchFunc
}
})

View File

@ -15,11 +15,9 @@
import contact from '@hcengineering/contact'
import core, {
AttachedDoc,
concatLink,
Doc,
Tx,
TxCollectionCUD,
TxCreateDoc,
TxCUD,
TxProcessor,
@ -29,7 +27,6 @@ import core, {
import { getMetadata } from '@hcengineering/platform'
import recruit, { Applicant, recruitId, Vacancy } from '@hcengineering/recruit'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { addAssigneeNotification } from '@hcengineering/server-task-resources'
import { workbenchId } from '@hcengineering/workbench'
function getSequenceId (doc: Vacancy | Applicant, control: TriggerControl): string {
@ -96,12 +93,10 @@ export async function OnRecruitUpdate (tx: Tx, control: TriggerControl): Promise
if (actualTx._class === core.class.TxCreateDoc) {
handleVacancyCreate(control, cud, actualTx, res)
await handleApplicantCreate(control, cud, res, tx)
}
if (actualTx._class === core.class.TxUpdateDoc) {
await handleVacancyUpdate(control, cud, res)
await handleApplicantUpdate(control, cud, res, tx)
}
if (actualTx._class === core.class.TxRemoveDoc) {
await handleVacancyRemove(control, cud, actualTx)
@ -189,43 +184,6 @@ async function handleVacancyRemove (control: TriggerControl, cud: TxCUD<Doc>, ac
}
}
async function handleApplicantUpdate (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[], tx: Tx): Promise<void> {
if (control.hierarchy.isDerived(cud.objectClass, recruit.class.Applicant)) {
const updateTx = cud as TxUpdateDoc<Applicant>
if (updateTx.operations.assignee != null) {
const applicant = (
await control.findAll(recruit.class.Applicant, { _id: updateTx.objectId }, { limit: 1 })
).shift()
if (applicant?.assignee != null) {
await addAssigneeNotification(
control,
res,
applicant,
applicant.assignee,
tx as TxCollectionCUD<Applicant, AttachedDoc>
)
}
}
}
}
async function handleApplicantCreate (control: TriggerControl, cud: TxCUD<Doc>, res: Tx[], tx: Tx): Promise<void> {
if (control.hierarchy.isDerived(cud.objectClass, recruit.class.Applicant)) {
const createTx = cud as TxCreateDoc<Applicant>
const applicant = TxProcessor.createDoc2Doc(createTx)
if (applicant.assignee != null) {
await addAssigneeNotification(
control,
res,
applicant,
applicant.assignee,
tx as TxCollectionCUD<Applicant, AttachedDoc>
)
}
}
}
function handleVacancyCreate (control: TriggerControl, cud: TxCUD<Doc>, actualTx: Tx, res: Tx[]): void {
if (control.hierarchy.isDerived(cud.objectClass, recruit.class.Vacancy)) {
const createTx = actualTx as TxCreateDoc<Vacancy>

View File

@ -13,39 +13,6 @@
// limitations under the License.
//
import { Employee } from '@hcengineering/contact'
import { AttachedDoc, Doc, Ref, Tx, TxCollectionCUD } from '@hcengineering/core'
import { TriggerControl } from '@hcengineering/server-core'
import { getEmployeeAccount, getEmployeeAccountById } from '@hcengineering/server-notification'
import { createNotificationTxes } from '@hcengineering/server-notification-resources'
import task from '@hcengineering/task'
/**
* @public
*/
export async function addAssigneeNotification (
control: TriggerControl,
res: Tx[],
issue: Doc,
assignee: Ref<Employee>,
ptx: TxCollectionCUD<AttachedDoc, AttachedDoc>
): Promise<void> {
const sender = await getEmployeeAccountById(ptx.modifiedBy, control)
if (sender === undefined) {
return
}
const receiver = await getEmployeeAccount(assignee, control)
if (receiver === undefined) {
return
}
if (sender._id === receiver._id) return
const result = await createNotificationTxes(control, ptx, task.ids.AssigneedNotification, issue, sender, receiver)
res.push(...result)
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
function: {}

View File

@ -29,6 +29,7 @@
"dependencies": {
"@hcengineering/server-notification": "^0.6.0",
"@hcengineering/platform": "^0.6.8",
"@hcengineering/server-core": "^0.6.1"
"@hcengineering/server-core": "^0.6.1",
"@hcengineering/core": "^0.6.23"
}
}

View File

@ -13,7 +13,6 @@
// limitations under the License.
//
import { Employee } from '@hcengineering/contact'
import core, {
AttachedDoc,
concatLink,
@ -32,7 +31,6 @@ import core, {
} from '@hcengineering/core'
import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { addAssigneeNotification } from '@hcengineering/server-task-resources'
import tracker, { Component, Issue, IssueParentInfo, TimeSpendReport, trackerId } from '@hcengineering/tracker'
import { workbenchId } from '@hcengineering/workbench'
@ -71,19 +69,6 @@ export async function issueTextPresenter (doc: Doc, control: TriggerControl): Pr
return issueName
}
/**
* @public
*/
export async function addTrackerAssigneeNotification (
control: TriggerControl,
res: Tx[],
issue: Issue,
assignee: Ref<Employee>,
ptx: TxCollectionCUD<Issue, AttachedDoc>
): Promise<void> {
await addAssigneeNotification(control, res, issue, assignee, ptx)
}
/**
* @public
*/
@ -137,15 +122,6 @@ export async function OnIssueUpdate (tx: Tx, control: TriggerControl): Promise<T
const res: Tx[] = []
updateIssueParentEstimations(issue, res, control, [], issue.parents)
if (issue.assignee != null) {
await addTrackerAssigneeNotification(
control,
res,
issue,
issue.assignee,
tx as TxCollectionCUD<Issue, AttachedDoc>
)
}
return res
}
}
@ -279,10 +255,6 @@ async function doIssueUpdate (
return currentIssue
}
if (updateTx.operations.assignee != null) {
await addTrackerAssigneeNotification(control, res, await getCurrentIssue(), updateTx.operations.assignee, tx)
}
if (Object.prototype.hasOwnProperty.call(updateTx.operations, 'attachedTo')) {
const [newParent] = await control.findAll(
tracker.class.Issue,

View File

@ -285,7 +285,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar
}
if (typeof query === 'string') {
if (!spaces.includes(query)) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
return { $in: [] }
}
} else if (query.$in != null) {
query.$in = query.$in.filter((p) => spaces.includes(p))