Move notifications to person spaces (#6263)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2024-08-06 13:44:23 +04:00 committed by GitHub
parent b997b0c753
commit 1c1d0bc538
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 729 additions and 500 deletions

1
.vscode/launch.json vendored
View File

@ -192,6 +192,7 @@
"MINIO_ACCESS_KEY": "minioadmin",
"MINIO_SECRET_KEY": "minioadmin",
"MINIO_ENDPOINT": "localhost",
"TRANSACTOR_URL": "ws://localhost:3333",
"MONGO_URL": "mongodb://localhost:27017",
"ACCOUNTS_URL": "http://localhost:3000",
"TELEGRAM_DATABASE": "telegram-service",

View File

@ -1,6 +1,6 @@
{
"name": "desktop",
"version": "0.6.266",
"version": "0.6.271",
"main": "dist/main/electron.js",
"author": "Hardcore Engineering <hey@huly.io>",
"template": "@hcengineering/default-package",

View File

@ -1,6 +1,6 @@
{
"name": "@hcengineering/desktop",
"version": "0.6.266",
"version": "0.6.271",
"main": "dist/main/electron.js",
"template": "@hcengineering/webpack-package",
"scripts": {

View File

@ -44,14 +44,14 @@ export const DOMAIN_COMMENT = 'comment' as Domain
export async function createDocNotifyContexts (
client: MigrationUpgradeClient,
tx: TxOperations,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>,
objectSpace: Ref<Space>
): Promise<void> {
const users = await client.findAll(core.class.Account, {})
const docNotifyContexts = await client.findAll(notification.class.DocNotifyContext, {
user: { $in: users.map((it) => it._id) },
attachedTo,
attachedToClass
objectId
})
for (const user of users) {
if (user._id === core.account.System) {
@ -62,8 +62,9 @@ export async function createDocNotifyContexts (
if (docNotifyContext === undefined) {
await tx.createDoc(notification.class.DocNotifyContext, core.space.Space, {
user: user._id,
attachedTo,
attachedToClass,
objectId,
objectClass,
objectSpace,
isPinned: false
})
}
@ -94,7 +95,7 @@ export async function createGeneral (client: MigrationUpgradeClient, tx: TxOpera
topic: 'General Channel',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
members: await getAllPersonAccounts(tx),
autoJoin: true
},
chunter.space.General
@ -102,10 +103,10 @@ export async function createGeneral (client: MigrationUpgradeClient, tx: TxOpera
}
}
await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel)
await createDocNotifyContexts(client, tx, chunter.space.General, chunter.class.Channel, core.space.Space)
}
async function getAllEmployeeAccounts (tx: TxOperations): Promise<Ref<PersonAccount>[]> {
async function getAllPersonAccounts (tx: TxOperations): Promise<Ref<PersonAccount>[]> {
const employees = await tx.findAll(contactPlugin.mixin.Employee, { active: true })
const accounts = await tx.findAll(contactPlugin.class.PersonAccount, {
person: { $in: employees.map((it) => it._id) }
@ -114,7 +115,7 @@ async function getAllEmployeeAccounts (tx: TxOperations): Promise<Ref<PersonAcco
}
async function joinEmployees (current: Space, tx: TxOperations): Promise<void> {
const accs = await getAllEmployeeAccounts(tx)
const accs = await getAllPersonAccounts(tx)
const newMembers: Ref<Account>[] = [...current.members]
for (const acc of accs) {
if (!newMembers.includes(acc)) {
@ -150,7 +151,7 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat
topic: 'Random Talks',
private: false,
archived: false,
members: await getAllEmployeeAccounts(tx),
members: await getAllPersonAccounts(tx),
autoJoin: true
},
chunter.space.Random
@ -158,7 +159,7 @@ export async function createRandom (client: MigrationUpgradeClient, tx: TxOperat
}
}
await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel)
await createDocNotifyContexts(client, tx, chunter.space.Random, chunter.class.Channel, core.space.Space)
}
async function convertCommentsToChatMessages (client: MigrationClient): Promise<void> {

View File

@ -29,7 +29,8 @@ import {
type Organization,
type Person,
type PersonAccount,
type Status
type Status,
type PersonSpace
} from '@hcengineering/contact'
import {
AccountRole,
@ -64,7 +65,7 @@ import {
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import core, { TAccount, TAttachedDoc, TDoc } from '@hcengineering/model-core'
import core, { TAccount, TAttachedDoc, TDoc, TSpace } from '@hcengineering/model-core'
import { createPublicLinkAction } from '@hcengineering/model-guest'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
@ -219,6 +220,12 @@ export class TContactsTab extends TDoc implements ContactsTab {
index!: number
}
@Model(contact.class.PersonSpace, core.class.Space)
export class TPersonSpace extends TSpace implements PersonSpace {
@Prop(TypeRef(contact.class.Person), contact.string.Person)
person!: Ref<Person>
}
export function createModel (builder: Builder): void {
builder.createModel(
TAvatarProvider,
@ -231,7 +238,8 @@ export function createModel (builder: Builder): void {
TChannel,
TStatus,
TMember,
TContactsTab
TContactsTab,
TPersonSpace
)
builder.mixin(contact.class.Contact, core.class.Class, activity.mixin.ActivityDoc, {})

View File

@ -1,23 +1,32 @@
//
import { DOMAIN_TX, TxOperations, type Class, type Doc, type Domain, type Ref, type Space } from '@hcengineering/core'
import {
type Class,
type Doc,
type Domain,
DOMAIN_TX,
generateId,
type Ref,
type Space,
TxOperations
} from '@hcengineering/core'
import {
createDefaultSpace,
tryMigrate,
tryUpgrade,
type MigrateOperation,
type MigrateUpdate,
type MigrationClient,
type MigrationDocumentQuery,
type MigrationUpgradeClient,
type ModelLogger
type ModelLogger,
tryMigrate,
tryUpgrade
} from '@hcengineering/model'
import activity, { DOMAIN_ACTIVITY } from '@hcengineering/model-activity'
import core from '@hcengineering/model-core'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_VIEW } from '@hcengineering/model-view'
import { AvatarType, type Contact, type Person, type PersonSpace } from '@hcengineering/contact'
import { AvatarType, type Contact } from '@hcengineering/contact'
import contact, { DOMAIN_CONTACT, contactId } from './index'
import contact, { contactId, DOMAIN_CONTACT } from './index'
async function createEmployeeEmail (client: TxOperations): Promise<void> {
const employees = await client.findAll(contact.mixin.Employee, {})
@ -100,23 +109,55 @@ async function migrateAvatars (client: MigrationClient): Promise<void> {
)
}
async function createPersonSpaces (client: MigrationClient): Promise<void> {
const spaces = await client.find<PersonSpace>(DOMAIN_SPACE, { _class: contact.class.PersonSpace })
if (spaces.length > 0) {
return
}
const accounts = await client.model.findAll(contact.class.PersonAccount, {})
const employees = await client.find(DOMAIN_CONTACT, { [contact.mixin.Employee]: { $exists: true } })
const newSpaces = new Map<Ref<Person>, PersonSpace>()
const now = Date.now()
for (const account of accounts) {
const employee = employees.find(({ _id }) => _id === account.person)
if (employee === undefined) continue
const space = newSpaces.get(account.person)
if (space !== undefined) {
space.members.push(account._id)
} else {
newSpaces.set(account.person, {
_id: generateId(),
_class: contact.class.PersonSpace,
space: core.space.Space,
name: 'Personal space',
description: '',
private: true,
archived: false,
members: [account._id],
person: account.person,
modifiedBy: core.account.System,
createdBy: core.account.System,
modifiedOn: now,
createdOn: now
})
}
}
await client.create(DOMAIN_SPACE, Array.from(newSpaces.values()))
}
export const contactOperation: MigrateOperation = {
async migrate (client: MigrationClient, logger: ModelLogger): Promise<void> {
await tryMigrate(client, contactId, [
{
state: 'employees',
func: async (client) => {
await client.update(
DOMAIN_TX,
{
objectClass: 'contact:class:EmployeeAccount'
},
{
$rename: { 'attributes.employee': 'attributes.person' },
$set: { objectClass: contact.class.PersonAccount }
}
)
await client.update(
DOMAIN_TX,
{
@ -251,6 +292,10 @@ export const contactOperation: MigrateOperation = {
{ $rename: { avatarKind: 'avatarType' } }
)
}
},
{
state: 'create-person-spaces-v1',
func: createPersonSpaces
}
])
},

View File

@ -30,6 +30,7 @@
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/chunter": "^0.6.20",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/model": "^0.6.11",
"@hcengineering/model-attachment": "^0.6.0",
@ -39,10 +40,10 @@
"@hcengineering/model-workbench": "^0.6.1",
"@hcengineering/notification": "^0.6.23",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/preference": "^0.6.13",
"@hcengineering/setting": "^0.6.17",
"@hcengineering/ui": "^0.6.15",
"@hcengineering/view": "^0.6.13",
"@hcengineering/workbench": "^0.6.16",
"@hcengineering/preference": "^0.6.13"
"@hcengineering/workbench": "^0.6.16"
}
}

View File

@ -49,6 +49,7 @@ import {
UX,
type Builder
} from '@hcengineering/model'
import { type PersonSpace } from '@hcengineering/contact'
import core, { TClass, TDoc } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
import view, { createAction, template } from '@hcengineering/model-view'
@ -195,13 +196,18 @@ export class TDocNotifyContext extends TDoc implements DocNotifyContext {
@Index(IndexKind.Indexed)
user!: Ref<Account>
@Prop(TypeRef(core.class.Doc), core.string.AttachedTo)
@Prop(TypeRef(core.class.Doc), core.string.Object)
@Index(IndexKind.Indexed)
attachedTo!: Ref<Doc>
objectId!: Ref<Doc>
@Prop(TypeRef(core.class.Class), core.string.AttachedToClass)
@Prop(TypeRef(core.class.Class), core.string.Class)
@Index(IndexKind.Indexed)
attachedToClass!: Ref<Class<Doc>>
objectClass!: Ref<Class<Doc>>
@Prop(TypeRef(core.class.Space), core.string.Space)
objectSpace!: Ref<Space>
declare space: Ref<PersonSpace>
@Prop(TypeDate(), core.string.Date)
lastViewedTimestamp?: Timestamp
@ -230,6 +236,8 @@ export class TInboxNotification extends TDoc implements InboxNotification {
@Prop(TypeBoolean(), core.string.Boolean)
archived!: boolean
declare space: Ref<PersonSpace>
title?: IntlString
body?: IntlString
intlParams?: Record<string, string | number>

View File

@ -29,8 +29,10 @@ import notification, {
type InboxNotification
} from '@hcengineering/notification'
import { DOMAIN_PREFERENCE } from '@hcengineering/preference'
import contact, { type PersonSpace } from '@hcengineering/contact'
import { DOMAIN_DOC_NOTIFY, DOMAIN_NOTIFICATION, DOMAIN_USER_NOTIFY } from './index'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
export async function removeNotifications (
client: MigrationClient,
@ -74,6 +76,115 @@ export async function removeNotifications (
}
}
export async function migrateNotificationsSpace (client: MigrationClient): Promise<void> {
const personSpaces = await client.find<PersonSpace>(DOMAIN_SPACE, { _class: contact.class.PersonSpace }, {})
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
objectSpace: { $exists: false }
},
{ $rename: { space: 'objectSpace' } }
)
for (const space of personSpaces) {
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.ActivityInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.CommonInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
await client.update(
DOMAIN_NOTIFICATION,
{
_class: notification.class.MentionInboxNotification,
user: { $in: space.members }
},
{ space: space._id }
)
}
await client.deleteMany(DOMAIN_DOC_NOTIFY, { space: { $nin: personSpaces.map(({ _id }) => _id) } })
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.ActivityInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.CommonInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
await client.deleteMany(DOMAIN_NOTIFICATION, {
_class: notification.class.MentionInboxNotification,
space: { $nin: personSpaces.map(({ _id }) => _id) }
})
while (true) {
const contexts = await client.find<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
attachedTo: { $exists: true }
},
{ limit: 500 }
)
if (contexts.length === 0) {
break
}
const classesOfSpace = new Set<Ref<Class<Doc>>>()
for (const context of contexts) {
const _class = (context as any).attachedToClass
if (client.hierarchy.isDerived(_class, core.class.Space)) {
classesOfSpace.add(_class)
}
}
if (classesOfSpace.size > 0) {
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ objectClass: { $in: Array.from(classesOfSpace) } },
{ objectSpace: core.space.Space }
)
await client.update<DocNotifyContext>(
DOMAIN_DOC_NOTIFY,
{ objectClass: { $in: Array.from(classesOfSpace) } },
{ $rename: { attachedTo: 'objectId', attachedToClass: 'objectClass' } }
)
}
await client.update(
DOMAIN_DOC_NOTIFY,
{
_class: notification.class.DocNotifyContext,
_id: { $in: contexts.map(({ _id }) => _id) }
},
{ $rename: { attachedTo: 'objectId', attachedToClass: 'objectClass' } }
)
}
await client.deleteMany(DOMAIN_NOTIFICATION, { _class: notification.class.BrowserNotification })
await client.deleteMany(DOMAIN_USER_NOTIFY, { _class: notification.class.BrowserNotification })
}
export async function migrateSettings (client: MigrationClient): Promise<void> {
await client.update(
DOMAIN_PREFERENCE,
@ -197,8 +308,13 @@ export const notificationOperation: MigrateOperation = {
{ isPinned: false }
)
}
},
{
state: 'migrate-notifications-space-v1',
func: migrateNotificationsSpace
}
])
await client.deleteMany<BrowserNotification>(DOMAIN_USER_NOTIFY, {
_class: notification.class.BrowserNotification,
status: NotificationStatus.Notified

View File

@ -26,6 +26,7 @@
export let colorsSchema: 'default' | 'lumia' = 'default'
export let updateOnMouse = true
export let lazy = false
export let minHeight: string | null = null
export let highlightIndex: number | undefined = undefined
const getKey: (index: number) => string = (index) => index.toString()
@ -76,35 +77,37 @@
>
{#each Array(count) as _, row (getKey(row))}
{#if lazy}
<Lazy>
<ListViewItem
bind:element={refs[row]}
{colorsSchema}
{addClass}
{row}
{kind}
isHighlighted={row === highlightIndex}
selected={row === selection}
on:click={() => dispatch('click', row)}
on:mouseover={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
on:mouseenter={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
>
<svelte:fragment slot="category" let:item={itemIndex}>
<slot name="category" item={itemIndex} />
</svelte:fragment>
<svelte:fragment slot="item" let:item={itemIndex}>
<slot name="item" item={itemIndex} />
</svelte:fragment>
</ListViewItem>
</Lazy>
<div style="min-height: {minHeight}">
<Lazy>
<ListViewItem
bind:element={refs[row]}
{colorsSchema}
{addClass}
{row}
{kind}
isHighlighted={row === highlightIndex}
selected={row === selection}
on:click={() => dispatch('click', row)}
on:mouseover={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
on:mouseenter={mouseAttractor(() => {
if (updateOnMouse) {
onRow(row)
}
})}
>
<svelte:fragment slot="category" let:item={itemIndex}>
<slot name="category" item={itemIndex} />
</svelte:fragment>
<svelte:fragment slot="item" let:item={itemIndex}>
<slot name="item" item={itemIndex} />
</svelte:fragment>
</ListViewItem>
</Lazy>
</div>
{:else}
<ListViewItem
bind:element={refs[row]}

View File

@ -77,7 +77,7 @@
const ctx =
context ??
(await client.findOne(notification.class.DocNotifyContext, {
attachedTo: object._id,
objectId: object._id,
user: getCurrentAccount()._id
}))
const hasRefs = ((object as WithReferences<Doc>).references ?? 0) > 0

View File

@ -36,7 +36,7 @@
$: threadId = context ? undefined : (_id as Ref<ActivityMessage>)
$: context &&
objectQuery.query(context.attachedToClass, { _id: context.attachedTo }, (res) => {
objectQuery.query(context.objectClass, { _id: context.objectId }, (res) => {
;[object] = res
})
</script>

View File

@ -18,6 +18,7 @@
import presentation, { getClient } from '@hcengineering/presentation'
import core, { getCurrentAccount } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import contact, { PersonAccount } from '@hcengineering/contact'
import Lock from '../../icons/Lock.svelte'
import chunter from '../../../plugin'
@ -51,19 +52,27 @@
$: canSave = !!channelName
async function save (): Promise<void> {
const accountId = getCurrentAccount()._id
const account = getCurrentAccount() as PersonAccount
const space = await client.findOne(
contact.class.PersonSpace,
{ person: account.person },
{ projection: { _id: 1 } }
)
if (!space) return
const channelId = await client.createDoc(chunter.class.Channel, core.space.Space, {
name: channelName,
description: '',
private: selectedVisibilityId === 'private',
archived: false,
members: [accountId],
members: [account._id],
topic: description
})
await client.createDoc(notification.class.DocNotifyContext, channelId, {
user: accountId,
attachedTo: channelId,
attachedToClass: chunter.class.Channel,
await client.createDoc(notification.class.DocNotifyContext, space._id, {
user: account._id,
objectId: channelId,
objectClass: chunter.class.Channel,
objectSpace: core.space.Space,
isPinned: false
})

View File

@ -31,7 +31,7 @@
const dispatch = createEventDispatcher()
const client = getClient()
const myAccId = getCurrentAccount()._id
const myAcc = getCurrentAccount() as PersonAccount
const query = createQuery()
let employeeIds: Ref<Employee>[] = []
@ -52,7 +52,7 @@
async function createDirectMessage (): Promise<void> {
const employeeAccounts = await client.findAll(contact.class.PersonAccount, { person: { $in: employeeIds } })
const accIds = [myAccId, ...employeeAccounts.filter(({ _id }) => _id !== myAccId).map(({ _id }) => _id)].sort()
const accIds = [myAcc._id, ...employeeAccounts.filter(({ _id }) => _id !== myAcc._id).map(({ _id }) => _id)].sort()
const existingDms = await client.findAll(chunter.class.DirectMessage, {})
@ -75,9 +75,9 @@
}))
const context = await client.findOne(notification.class.DocNotifyContext, {
user: myAccId,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage
person: myAcc.person,
objectId: dmId,
objectClass: chunter.class.DirectMessage
})
if (context !== undefined) {
@ -86,10 +86,13 @@
return
}
await client.createDoc(notification.class.DocNotifyContext, dmId, {
user: myAccId,
attachedTo: dmId,
attachedToClass: chunter.class.DirectMessage,
const space = await client.findOne(contact.class.PersonSpace, { person: myAcc.person }, { projection: { _id: 1 } })
if (!space) return
await client.createDoc(notification.class.DocNotifyContext, space._id, {
user: myAcc._id,
objectId: dmId,
objectClass: chunter.class.DirectMessage,
objectSpace: core.space.Space,
isPinned: false
})

View File

@ -50,11 +50,11 @@
let sections: Section[] = []
$: contexts = $contextsStore.filter(({ attachedToClass, isPinned }) => {
$: contexts = $contextsStore.filter(({ objectClass, isPinned }) => {
if (model.isPinned !== isPinned) return false
if (model._class !== undefined && model._class !== attachedToClass) return false
if (model.skipClasses !== undefined && model.skipClasses.includes(attachedToClass)) return false
if (hierarchy.classHierarchyMixin(attachedToClass, activity.mixin.ActivityDoc) === undefined) return false
if (model._class !== undefined && model._class !== objectClass) return false
if (model.skipClasses !== undefined && model.skipClasses.includes(objectClass)) return false
if (hierarchy.classHierarchyMixin(objectClass, activity.mixin.ActivityDoc) === undefined) return false
return true
})
@ -72,10 +72,10 @@
object !== undefined && getObjectGroup(object) === model.id && !$contextByDocStore.has(object._id)
function loadObjects (contexts: DocNotifyContext[]): void {
const contextsByClass = groupByArray(contexts, ({ attachedToClass }) => attachedToClass)
const contextsByClass = groupByArray(contexts, ({ objectClass }) => objectClass)
for (const [_class, ctx] of contextsByClass.entries()) {
const ids = ctx.map(({ attachedTo }) => attachedTo)
const ids = ctx.map(({ objectId }) => objectId)
const { query, limit } = objectsQueryByClass.get(_class) ?? {
query: createQuery(),
limit: hierarchy.isDerived(_class, chunter.class.ChunterSpace) ? -1 : model.maxSectionItems ?? 5
@ -187,7 +187,7 @@
if (_class === undefined) {
return model.getActionsFn(contexts)
} else {
return model.getActionsFn(contexts.filter(({ attachedToClass }) => attachedToClass === _class))
return model.getActionsFn(contexts.filter(({ objectClass }) => objectClass === _class))
}
}
</script>

View File

@ -120,7 +120,7 @@
noDivider
>
{#each sortedItems as item (item.id)}
{@const context = contexts.find(({ attachedTo }) => attachedTo === item.id)}
{@const context = contexts.find(({ objectId }) => objectId === item.id)}
<ChatNavItem {context} isSelected={objectId === item.id} {item} type={'type-object'} on:select />
{/each}
{#if canShowMore}
@ -130,7 +130,7 @@
{/if}
<svelte:fragment slot="visible" let:isOpen>
{#if visibleItem !== undefined && !isOpen}
{@const context = contexts.find(({ attachedTo }) => attachedTo === visibleItem?.id)}
{@const context = contexts.find(({ objectId }) => objectId === visibleItem?.id)}
<ChatNavItem {context} isSelected item={visibleItem} type={'type-object'} on:select />
{/if}
</svelte:fragment>

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { AccountRole, Doc, Ref, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { AccountRole, Doc, getCurrentAccount, hasAccountRole } from '@hcengineering/core'
import { Scroller, SearchEdit, Label, ButtonIcon, IconAdd, showPopup, Menu } from '@hcengineering/ui'
import { DocNotifyContext } from '@hcengineering/notification'
import { SpecialNavModel } from '@hcengineering/workbench'

View File

@ -251,7 +251,7 @@ function sortDirects (items: ChatNavItemModel[], option: SortFnOptions): ChatNav
function sortActivityChannels (items: ChatNavItemModel[], option: SortFnOptions): ChatNavItemModel[] {
const { contexts } = option
const contextByDoc = new Map(contexts.map((context) => [context.attachedTo, context]))
const contextByDoc = new Map(contexts.map((context) => [context.objectId, context]))
return items.sort((i1, i2) => {
const context1 = contextByDoc.get(i1.id)

View File

@ -35,7 +35,7 @@
let title: string | undefined = undefined
let channel: Doc | undefined = undefined
$: isThread = hierarchy.isDerived(context.attachedToClass, chunter.class.ThreadMessage)
$: isThread = hierarchy.isDerived(context.objectClass, chunter.class.ThreadMessage)
$: loadChannel(object, isThread)
$: channel &&
@ -46,7 +46,7 @@
function loadChannel (object: ChatMessage, isThread: boolean): void {
const _class = isThread ? (object as ThreadMessage).objectClass : object.attachedToClass
const _id = isThread ? (object as ThreadMessage).objectId : object.attachedTo
console.log({ _class, _id, isThread, object })
void client.findOne(_class, { _id, ...(isThread ? { space: object.space } : {}) }).then((res) => {
channel = res
})

View File

@ -491,7 +491,7 @@ export async function leaveChannelAction (
}
const client = getClient()
const channel =
props?.object ?? (await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> }))
props?.object ?? (await client.findOne(chunter.class.Channel, { _id: context.objectId as Ref<Channel> }))
if (channel === undefined) {
return
@ -501,37 +501,33 @@ export async function leaveChannelAction (
await resetChunterLocIfEqual(channel._id, channel._class, channel)
}
export async function removeChannelAction (
context?: DocNotifyContext,
_?: Event,
props?: { object?: Doc }
): Promise<void> {
export async function removeChannelAction (context?: DocNotifyContext, _?: Event): Promise<void> {
if (context === undefined) {
return
}
const client = getClient()
const hierarchy = client.getHierarchy()
const inboxClient = InboxNotificationsClientImpl.getClient()
const { objectId, objectClass, objectSpace } = context
if (hierarchy.isDerived(context.attachedToClass, chunter.class.Channel)) {
const channel = await client.findOne(chunter.class.Channel, { _id: context.attachedTo as Ref<Channel> })
if (hierarchy.isDerived(objectClass, chunter.class.Channel)) {
const channel = await client.findOne(chunter.class.Channel, { _id: objectId as Ref<Channel>, space: objectSpace })
await leaveChannel(channel, getCurrentAccount()._id)
} else {
const object = await client.findOne(context.attachedToClass, { _id: context.attachedTo })
const account = getCurrentAccount() as PersonAccount
const object = await client.findOne(objectClass, { _id: objectId, space: objectSpace })
// const account = getCurrentAccount() as PersonAccount
await client.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true })
const chatInfo = await client.findOne(chunter.class.ChatInfo, { user: account.person })
if (chatInfo !== undefined) {
await client.update(chatInfo, { hidden: chatInfo.hidden.concat([context._id]) })
}
await resetChunterLocIfEqual(context.attachedTo, context.attachedToClass, object)
// await client.createMixin(context._id, context._class, context.space, chunter.mixin.ChannelInfo, { hidden: true })
//
// const chatInfo = await client.findOne(chunter.class.ChatInfo, { user: account.person })
//
// if (chatInfo !== undefined) {
// await client.update(chatInfo, { hidden: chatInfo.hidden.concat([context._id]) })
// }
await resetChunterLocIfEqual(objectId, objectClass, object)
}
void inboxClient.readDoc(client, context.attachedTo)
await client.remove(context)
}
export function isThreadMessage (message: ActivityMessage): message is ThreadMessage {

View File

@ -179,6 +179,10 @@ export interface ContactsTab extends Doc {
*/
export const contactId = 'contact' as Plugin
export interface PersonSpace extends Space {
person: Ref<Person>
}
/**
* @public
*/
@ -193,7 +197,8 @@ export const contactPlugin = plugin(contactId, {
Organization: '' as Ref<Class<Organization>>,
PersonAccount: '' as Ref<Class<PersonAccount>>,
Status: '' as Ref<Class<Status>>,
ContactsTab: '' as Ref<Class<ContactsTab>>
ContactsTab: '' as Ref<Class<ContactsTab>>,
PersonSpace: '' as Ref<Class<PersonSpace>>
},
mixin: {
Employee: '' as Ref<Class<Employee>>

View File

@ -55,6 +55,7 @@
"CommonNotificationCollectionAdded": "{senderName} added {collection}",
"CommonNotificationCollectionRemoved": "{senderName} removed {collection}",
"Sound": "Sound",
"SoundNotificationsDescription": "Receive sound notifications for events."
"SoundNotificationsDescription": "Receive sound notifications for events.",
"NoAccessToObject": "You no longer have access to this object"
}
}

View File

@ -54,6 +54,7 @@
"Sound": "Sonido",
"SoundNotificationsDescription": "Reciba notificaciones de sonido para eventos.",
"CommonNotificationCollectionAdded": "{senderName} añadió {collection}",
"CommonNotificationCollectionRemoved": "{senderName} eliminó {collection}"
"CommonNotificationCollectionRemoved": "{senderName} eliminó {collection}",
"NoAccessToObject": "Ya no tienes acceso a este objeto"
}
}

View File

@ -55,6 +55,7 @@
"Sound": "Son",
"SoundNotificationsDescription": "Recevez des notifications sonores pour les événements.",
"CommonNotificationCollectionAdded": "{senderName} a ajouté {collection}",
"CommonNotificationCollectionRemoved": "{senderName} a supprimé {collection}"
"CommonNotificationCollectionRemoved": "{senderName} a supprimé {collection}",
"NoAccessToObject": "Vous n'avez plus accès à cet objet"
}
}

View File

@ -54,6 +54,7 @@
"Sound": "Som",
"SoundNotificationsDescription": "Receba notificações sonoras para eventos.",
"CommonNotificationCollectionAdded": "{senderName} adicionou {collection}",
"CommonNotificationCollectionRemoved": "{senderName} removeu {collection}"
"CommonNotificationCollectionRemoved": "{senderName} removeu {collection}",
"NoAccessToObject": "Você não tem mais acesso a este objeto"
}
}

View File

@ -55,6 +55,7 @@
"Sound": "Звук",
"SoundNotificationsDescription": "Получайте звуковые уведомления о событиях.",
"CommonNotificationCollectionAdded": "{senderName} добавил {collection}",
"CommonNotificationCollectionRemoved": "{senderName} удалил {collection}"
"CommonNotificationCollectionRemoved": "{senderName} удалил {collection}",
"NoAccessToObject": "У вас больше нет доступа к этому объекту"
}
}

View File

@ -55,6 +55,7 @@
"Sound": "声音",
"SoundNotificationsDescription": "接收事件的声音通知。",
"CommonNotificationCollectionAdded": "{senderName} 添加了 {collection}",
"CommonNotificationCollectionRemoved": "{senderName} 移除了 {collection}"
"CommonNotificationCollectionRemoved": "{senderName} 移除了 {collection}",
"NoAccessToObject": "您不再可以访问此对象"
}
}

View File

@ -13,17 +13,17 @@
// limitations under the License.
-->
<script lang="ts">
import { ButtonIcon, CheckBox, Component, IconMoreV, Label, showPopup, Spinner } from '@hcengineering/ui'
import { ButtonIcon, CheckBox, Component, IconMoreV, Label, Loading, showPopup, Spinner } from '@hcengineering/ui'
import notification, {
ActivityNotificationViewlet,
DisplayInboxNotification,
DocNotifyContext,
InboxNotification
} from '@hcengineering/notification'
import { createQuery, getClient, isSpace, isSpaceClass } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import { getDocTitle, getDocIdentifier, Menu } from '@hcengineering/view-resources'
import { createEventDispatcher } from 'svelte'
import core, { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import { Class, Doc, IdMap, Ref, WithLookup } from '@hcengineering/core'
import chunter from '@hcengineering/chunter'
import { personAccountByIdStore } from '@hcengineering/contact-resources'
import { Person, PersonAccount } from '@hcengineering/contact'
@ -50,17 +50,19 @@
const query = createQuery()
let object: Doc | undefined = undefined
let isLoading = true
$: query.query(
value.attachedToClass,
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
value.objectClass,
{ _id: value.objectId, space: value.objectSpace },
(res) => {
object = res[0]
isLoading = false
},
{ limit: 1 }
)
$: if (object?._id !== value.attachedTo) {
$: if (object !== undefined && object?._id !== value.objectId) {
object = undefined
}
@ -82,10 +84,7 @@
title = res
})
$: presenterMixin = hierarchy.classHierarchyMixin(
value.attachedToClass,
notification.mixin.NotificationContextPresenter
)
$: presenterMixin = hierarchy.classHierarchyMixin(value.objectClass, notification.mixin.NotificationContextPresenter)
let groupedNotifications: Array<InboxNotification[]> = []
@ -202,7 +201,11 @@
dispatch('click', { context: value })
}}
>
{#if object}
{#if isLoading}
<div class="loading">
<Loading />
</div>
{:else if object}
<div class="header">
<NotifyContextIcon {value} notifyCount={unreadCount} {object} />
@ -213,13 +216,13 @@
{#if idTitle}
{idTitle}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
<Label label={hierarchy.getClass(value.objectClass).label} />
{/if}
<span class="title overflow-label clear-mins" {title}>
{#if title}
{title}
{:else}
<Label label={hierarchy.getClass(value.attachedToClass).label} />
<Label label={hierarchy.getClass(value.objectClass).label} />
{/if}
</span>
{/if}
@ -270,6 +273,35 @@
{/each}
</div>
</div>
{:else}
<div class="header">
<NotifyContextIcon {value} notifyCount={unreadCount} {object} />
<div class="labels">
<Label label={hierarchy.getClass(value.objectClass).label} />
</div>
<div class="actions clear-mins">
<div class="flex-center">
{#if archivingPromise !== undefined}
<Spinner size="small" />
{:else}
<CheckBox checked={archived} kind="todo" size="medium" on:value={checkContext} />
{/if}
</div>
<ButtonIcon
icon={IconMoreV}
size="small"
kind="tertiary"
inheritColor
pressed={isActionMenuOpened}
on:click={showMenu}
/>
</div>
</div>
<div class="content mt-2">
<Label label={notification.string.NoAccessToObject} />
</div>
{/if}
</div>
@ -369,4 +401,10 @@
display: flex;
width: 100%;
}
.loading {
display: flex;
align-items: center;
flex: 1;
}
</style>

View File

@ -14,9 +14,9 @@
-->
<script lang="ts">
import { DocNotifyContext } from '@hcengineering/notification'
import core, { Doc } from '@hcengineering/core'
import { Doc } from '@hcengineering/core'
import { getDocLinkTitle, getDocTitle } from '@hcengineering/view-resources'
import { createQuery, getClient, isSpaceClass } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import chunter from '@hcengineering/chunter'
import NotifyContextIcon from './NotifyContextIcon.svelte'
@ -27,13 +27,9 @@
let object: Doc | undefined
$: objectQuery.query(
value.attachedToClass,
{ _id: value.attachedTo, space: isSpaceClass(value.attachedToClass) ? core.space.Space : value.space },
(res) => {
object = res[0]
}
)
$: objectQuery.query(value.objectClass, { _id: value.objectId, space: value.objectSpace }, (res) => {
object = res[0]
})
async function getTitle (object: Doc) {
if (object._class === chunter.class.DirectMessage) {

View File

@ -30,14 +30,14 @@
const client = getClient()
const hierarchy = client.getHierarchy()
$: iconMixin = hierarchy.classHierarchyMixin(value.attachedToClass, view.mixin.ObjectIcon)
$: iconMixin = hierarchy.classHierarchyMixin(value.objectClass, view.mixin.ObjectIcon)
</script>
<div class="container">
{#if iconMixin && object}
<Component is={iconMixin.component} props={{ value: object, size }} />
{:else if !iconMixin}
<Icon icon={classIcon(client, value.attachedToClass) ?? notification.icon.Notifications} {size} />
{:else}
<Icon icon={classIcon(client, value.objectClass) ?? notification.icon.Notifications} {size} />
{/if}
<div class="notifyMarker">

View File

@ -188,7 +188,7 @@
async function updateTabItems (inboxData: InboxData, notifyContexts: DocNotifyContext[]): Promise<void> {
const displayClasses = new Set(
notifyContexts.filter(({ _id }) => inboxData.has(_id)).map(({ attachedToClass }) => attachedToClass)
notifyContexts.filter(({ _id }) => inboxData.has(_id)).map(({ objectClass }) => objectClass)
)
const classes = Array.from(displayClasses)
@ -249,8 +249,8 @@
return
}
const isChunterChannel = hierarchy.isDerived(selectedContext.attachedToClass, chunter.class.ChunterSpace)
const panelComponent = hierarchy.classHierarchyMixin(selectedContext.attachedToClass, view.mixin.ObjectPanel)
const isChunterChannel = hierarchy.isDerived(selectedContext.objectClass, chunter.class.ChunterSpace)
const panelComponent = hierarchy.classHierarchyMixin(selectedContext.objectClass, view.mixin.ObjectPanel)
selectedComponent = panelComponent?.component ?? view.component.EditDoc
@ -315,10 +315,10 @@
if (
selectedTabId === activity.class.ActivityMessage &&
hierarchy.isDerived(context.attachedToClass, activity.class.ActivityMessage)
hierarchy.isDerived(context.objectClass, activity.class.ActivityMessage)
) {
result.set(key, resNotifications)
} else if (context.attachedToClass === selectedTabId) {
} else if (context.objectClass === selectedTabId) {
result.set(key, resNotifications)
}
}
@ -411,8 +411,8 @@
<Component
is={selectedComponent}
props={{
_id: selectedContext.attachedTo,
_class: selectedContext.attachedToClass,
_id: selectedContext.objectId,
_class: selectedContext.objectClass,
context: selectedContext,
activityMessage: selectedMessage,
props: { context: selectedContext }

View File

@ -104,6 +104,7 @@
count={displayData.length}
highlightIndex={displayData.findIndex(([context]) => context === selectedContext)}
noScroll
minHeight="5.625rem"
kind="full-size"
colorsSchema="lumia"
lazy={true}

View File

@ -90,6 +90,10 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
private _contextByDoc = new Map<Ref<Doc>, DocNotifyContext>()
private constructor () {
void this.init()
}
private async init (): Promise<void> {
this.contextsQuery.query(
notification.class.DocNotifyContext,
{
@ -97,7 +101,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient {
},
(result: DocNotifyContext[]) => {
this.contexts.set(result)
this._contextByDoc = new Map(result.map((updates) => [updates.attachedTo, updates]))
this._contextByDoc = new Map(result.map((updates) => [updates.objectId, updates]))
this.contextByDoc.set(this._contextByDoc)
}
)

View File

@ -273,9 +273,9 @@ async function updateMeInCollaborators (
/**
* @public
*/
export async function unsubscribe (object: DocNotifyContext): Promise<void> {
export async function unsubscribe (context: DocNotifyContext): Promise<void> {
const client = getClient()
await updateMeInCollaborators(client, object.attachedToClass, object.attachedTo, OpWithMe.Remove)
await updateMeInCollaborators(client, context.objectClass, context.objectId, OpWithMe.Remove)
}
/**
@ -586,50 +586,39 @@ export async function selectInboxContext (
): Promise<void> {
const client = getClient()
const hierarchy = client.getHierarchy()
const { objectId, objectClass } = context
if (isMentionNotification(notification) && isActivityMessageClass(notification.mentionedInClass)) {
const selectedMsg = notification.mentionedIn as Ref<ActivityMessage>
void navigateToInboxDoc(
linkProviders,
context.attachedTo,
context.attachedToClass,
isActivityMessageClass(context.attachedToClass) ? (context.attachedTo as Ref<ActivityMessage>) : undefined,
objectId,
objectClass,
isActivityMessageClass(objectClass) ? (objectId as Ref<ActivityMessage>) : undefined,
selectedMsg
)
return
}
if (hierarchy.isDerived(context.attachedToClass, activity.class.ActivityMessage)) {
if (hierarchy.isDerived(objectClass, activity.class.ActivityMessage)) {
const message = (notification as WithLookup<ActivityInboxNotification>)?.$lookup?.attachedTo
if (context.attachedToClass === chunter.class.ThreadMessage) {
if (objectClass === chunter.class.ThreadMessage) {
const thread = await client.findOne(
chunter.class.ThreadMessage,
{
_id: context.attachedTo as Ref<ThreadMessage>
_id: objectId as Ref<ThreadMessage>
},
{ projection: { _id: 1, attachedTo: 1 } }
)
void navigateToInboxDoc(
linkProviders,
context.attachedTo,
context.attachedToClass,
thread?.attachedTo,
thread?._id
)
void navigateToInboxDoc(linkProviders, objectId, objectClass, thread?.attachedTo, thread?._id)
return
}
if (isReactionMessage(message)) {
void navigateToInboxDoc(
linkProviders,
context.attachedTo,
context.attachedToClass,
undefined,
context.attachedTo as Ref<ActivityMessage>
)
void navigateToInboxDoc(linkProviders, objectId, objectClass, undefined, objectId as Ref<ActivityMessage>)
return
}
@ -637,18 +626,18 @@ export async function selectInboxContext (
void navigateToInboxDoc(
linkProviders,
context.attachedTo,
context.attachedToClass,
selectedMsg !== undefined ? (context.attachedTo as Ref<ActivityMessage>) : undefined,
selectedMsg ?? (context.attachedTo as Ref<ActivityMessage>)
objectId,
objectClass,
selectedMsg !== undefined ? (objectId as Ref<ActivityMessage>) : undefined,
selectedMsg ?? (objectId as Ref<ActivityMessage>)
)
return
}
void navigateToInboxDoc(
linkProviders,
context.attachedTo,
context.attachedToClass,
objectId,
objectClass,
undefined,
(notification as ActivityInboxNotification)?.attachedTo
)

View File

@ -40,6 +40,7 @@
},
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/contact": "^0.6.24",
"@hcengineering/core": "^0.6.32",
"@hcengineering/platform": "^0.6.11",
"@hcengineering/preference": "^0.6.13",

View File

@ -24,6 +24,7 @@ import {
Markup,
Mixin,
Ref,
Space,
Timestamp,
Tx,
TxOperations
@ -34,6 +35,8 @@ import { Preference } from '@hcengineering/preference'
import { IntegrationType } from '@hcengineering/setting'
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
import { Action } from '@hcengineering/view'
import { PersonSpace } from '@hcengineering/contact'
import { Readable, Writable } from './types'
export * from './types'
@ -226,7 +229,7 @@ export interface NotificationContextPresenter extends Class<Doc> {
/**
* @public
*/
export interface InboxNotification extends Doc {
export interface InboxNotification extends Doc<PersonSpace> {
user: Ref<Account>
isViewed: boolean
@ -273,12 +276,12 @@ export type DisplayInboxNotification = DisplayActivityInboxNotification | InboxN
/**
* @public
*/
export interface DocNotifyContext extends Doc {
export interface DocNotifyContext extends Doc<PersonSpace> {
user: Ref<Account>
// Context
attachedTo: Ref<Doc>
attachedToClass: Ref<Class<Doc>>
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>
objectSpace: Ref<Space>
isPinned: boolean
lastViewedTimestamp?: Timestamp
@ -423,7 +426,8 @@ const notification = plugin(notificationId, {
CommonNotificationCollectionAdded: '' as IntlString,
CommonNotificationCollectionRemoved: '' as IntlString,
SoundNotificationsDescription: '' as IntlString,
Sound: '' as IntlString
Sound: '' as IntlString,
NoAccessToObject: '' as IntlString
},
function: {
Notify: '' as Resource<NotifyFunc>,

View File

@ -46,7 +46,7 @@ import {
} from '@hcengineering/text'
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
import activity, { ActivityMessage, ActivityReference, UserMentionInfo } from '@hcengineering/activity'
import contact, { Person, PersonAccount } from '@hcengineering/contact'
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import {
getCommonNotificationTxes,
getPushCollaboratorTx,
@ -54,7 +54,8 @@ import {
isShouldNotifyTx,
NotifyResult,
applyNotificationProviders,
getNotificationContent
getNotificationContent,
toReceiverInfo
} from '@hcengineering/server-notification-resources'
async function getPersonAccount (person: Ref<Person>, control: TriggerControl): Promise<PersonAccount | undefined> {
@ -118,6 +119,14 @@ export async function getPersonNotificationTxes (
const doc = (await control.findAll(reference.srcDocClass, { _id: reference.srcDocId }))[0]
const receiverPerson = (
await control.findAll(contact.mixin.Employee, { _id: receiver.person as Ref<Employee>, active: true }, { limit: 1 })
)[0]
if (receiverPerson === undefined) return res
const receiverSpace = (await control.findAll(contact.class.PersonSpace, { person: receiver.person }, { limit: 1 }))[0]
if (receiverSpace === undefined) return res
const collaboratorsTx = await getCollaboratorsTxes(reference, control, receiver, doc)
res.push(...collaboratorsTx)
@ -164,17 +173,19 @@ export async function getPersonNotificationTxes (
const sender = (
await control.modelDb.findAll(contact.class.PersonAccount, { _id: senderId as Ref<PersonAccount> }, { limit: 1 })
)[0]
const receiverPerson = (await control.findAll(contact.class.Person, { _id: receiver.person }, { limit: 1 }))[0]
const senderPerson =
sender !== undefined
? (await control.findAll(contact.class.Person, { _id: sender.person }, { limit: 1 }))[0]
: undefined
const receiverInfo = {
const receiverInfo = toReceiverInfo(control.hierarchy, {
_id: receiver._id,
account: receiver,
person: receiverPerson
}
person: receiverPerson,
space: receiverSpace._id
})
if (receiverInfo === undefined) return res
const senderInfo = {
_id: senderId,
@ -200,7 +211,7 @@ export async function getPersonNotificationTxes (
senderInfo,
reference.srcDocId,
reference.srcDocClass,
space,
doc.space,
originTx.modifiedOn,
notifyResult,
notification.class.MentionInboxNotification
@ -210,7 +221,7 @@ export async function getPersonNotificationTxes (
const context = (
await control.findAll(
notification.class.DocNotifyContext,
{ attachedTo: reference.srcDocId, user: receiver._id },
{ objectId: reference.srcDocId, user: receiver._id },
{ projection: { _id: 1 } }
)
)[0]
@ -222,7 +233,7 @@ export async function getPersonNotificationTxes (
docNotifyContext: context._id,
_id: generateId(),
_class: notification.class.CommonInboxNotification,
space,
space: receiverSpace._id,
modifiedOn: originTx.modifiedOn,
modifiedBy: sender._id
}

View File

@ -394,7 +394,7 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc<Channel>, control: Trigg
const removed = combineAttributes([tx.operations], 'members', '$pull', '$in')
const res: Tx[] = []
const allContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: tx.objectId })
const allContexts = await control.findAll(notification.class.DocNotifyContext, { objectId: tx.objectId })
if (removed.length > 0) {
res.push(
@ -416,13 +416,25 @@ async function OnChannelMembersChanged (tx: TxUpdateDoc<Channel>, control: Trigg
)
}
const accounts =
added.length > 0 ? await control.modelDb.findAll(contact.class.PersonAccount, { _id: { $in: added } }) : []
const spaces =
accounts.length > 0
? await control.findAll(contact.class.PersonSpace, { person: { $in: accounts.map((x) => x.person) } })
: []
for (const addedMember of added) {
const context = allContexts.find(({ user }) => user === addedMember)
if (context === undefined) {
const createTx = control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, tx.objectSpace, {
attachedTo: tx.objectId,
attachedToClass: tx.objectClass,
const account = accounts.find(({ _id }) => _id === addedMember)
if (account === undefined) continue
const space = spaces.find(({ person }) => person === account.person)
if (space === undefined) continue
const createTx = control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, space._id, {
objectId: tx.objectId,
objectClass: tx.objectClass,
objectSpace: tx.objectSpace,
user: addedMember,
lastViewedTimestamp: tx.modifiedOn,
isPinned: false
@ -563,14 +575,14 @@ export async function updateChatInfo (control: TriggerControl, status: UserStatu
const { hierarchy } = control
const res: Tx[] = []
const directContexts = contexts.filter(({ attachedToClass }) =>
hierarchy.isDerived(attachedToClass, chunter.class.DirectMessage)
const directContexts = contexts.filter(({ objectClass }) =>
hierarchy.isDerived(objectClass, chunter.class.DirectMessage)
)
const activityContexts = contexts.filter(
({ attachedToClass }) =>
!hierarchy.isDerived(attachedToClass, chunter.class.DirectMessage) &&
!hierarchy.isDerived(attachedToClass, chunter.class.Channel) &&
!hierarchy.isDerived(attachedToClass, chunter.class.Channel)
({ objectClass }) =>
!hierarchy.isDerived(objectClass, chunter.class.DirectMessage) &&
!hierarchy.isDerived(objectClass, chunter.class.Channel) &&
!hierarchy.isDerived(objectClass, chunter.class.Channel)
)
const directTxes = await hideOldDirects(directContexts, control, date)

View File

@ -26,7 +26,8 @@ import contact, {
formatName,
getFirstName,
getLastName,
getName
getName,
PersonSpace
} from '@hcengineering/contact'
import core, {
Account,
@ -41,7 +42,8 @@ import core, {
TxProcessor,
TxRemoveDoc,
TxUpdateDoc,
concatLink
concatLink,
TxCUD
} from '@hcengineering/core'
import notification, { Collaborators } from '@hcengineering/notification'
import { getMetadata } from '@hcengineering/platform'
@ -87,6 +89,10 @@ export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promis
if (acc === undefined) return []
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace(acc._id, mixinTx.objectId, control)
result.push(...txes)
for (const space of spaces) {
if (space.members.includes(acc._id)) continue
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {
@ -99,6 +105,35 @@ export async function OnEmployeeCreate (tx: Tx, control: TriggerControl): Promis
return result
}
async function createPersonSpace (
account: Ref<Account>,
person: Ref<Person>,
control: TriggerControl
): Promise<TxCUD<PersonSpace>[]> {
const personSpace = (await control.findAll(contact.class.PersonSpace, { person }, { limit: 1 })).shift()
if (personSpace !== undefined) {
if (personSpace.members.includes(account)) return []
return [
control.txFactory.createTxUpdateDoc(personSpace._class, personSpace.space, personSpace._id, {
$push: {
members: account
}
})
]
}
return [
control.txFactory.createTxCreateDoc(contact.class.PersonSpace, core.space.Space, {
name: 'Personal space',
description: '',
private: true,
archived: false,
person,
members: [account]
})
]
}
export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc<PersonAccount>)
const person = (
@ -106,7 +141,12 @@ export async function OnPersonAccountCreate (tx: Tx, control: TriggerControl): P
)[0]
if (person === undefined) return []
const spaces = await control.findAll(core.class.Space, { autoJoin: true })
const result: Tx[] = []
const txes = await createPersonSpace(acc._id, person._id, control)
result.push(...txes)
for (const space of spaces) {
if (space.members.includes(acc._id)) continue
const pushTx = control.txFactory.createTxUpdateDoc(space._class, space.space, space._id, {

View File

@ -30,8 +30,12 @@ import {
} from '@hcengineering/core'
import gmail, { Message } from '@hcengineering/gmail'
import { TriggerControl } from '@hcengineering/server-core'
import notification, { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
import serverNotification, { NotificationProviderFunc, UserInfo } from '@hcengineering/server-notification'
import { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
import serverNotification, {
NotificationProviderFunc,
ReceiverInfo,
SenderInfo
} from '@hcengineering/server-notification'
import { getContentByTemplate } from '@hcengineering/server-notification-resources'
import { getMetadata } from '@hcengineering/platform'
@ -74,47 +78,6 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
})
res.push(tx)
}
if (message.incoming) {
const docs = await control.findAll(notification.class.DocNotifyContext, {
attachedTo: channel._id,
user: message.modifiedBy
})
for (const doc of docs) {
// TODO: push inbox notification
// res.push(
// control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
// $push: {
// txes: {
// _id: tx._id as Ref<TxCUD<Doc>>,
// modifiedOn: tx.modifiedOn,
// modifiedBy: tx.modifiedBy,
// isNew: true
// }
// }
// })
// )
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
lastUpdateTimestamp: tx.modifiedOn
})
)
}
if (docs.length === 0) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, channel.space, {
user: tx.modifiedBy,
attachedTo: channel._id,
attachedToClass: channel._class,
lastUpdateTimestamp: tx.modifiedOn,
isPinned: false
// TODO: push inbox notification
// txes: [
// { _id: tx._id as Ref<TxCUD<Doc>>, modifiedOn: tx.modifiedOn, modifiedBy: tx.modifiedBy, isNew: true }
// ]
})
)
}
}
}
return res
@ -167,8 +130,8 @@ async function notifyByEmail (
control: TriggerControl,
type: Ref<BaseNotificationType>,
doc: Doc | undefined,
sender: UserInfo,
receiver: UserInfo,
sender: SenderInfo,
receiver: ReceiverInfo,
data: InboxNotification
): Promise<void> {
const account = receiver.account
@ -192,26 +155,14 @@ const SendEmailNotifications: NotificationProviderFunc = async (
types: BaseNotificationType[],
object: Doc,
data: InboxNotification,
receiver: UserInfo,
sender: UserInfo
receiver: ReceiverInfo,
sender: SenderInfo
): Promise<Tx[]> => {
if (types.length === 0) {
return []
}
if (receiver.person === undefined) {
return []
}
const isEmployee = control.hierarchy.hasMixin(receiver.person, contact.mixin.Employee)
if (!isEmployee) {
return []
}
const employee = control.hierarchy.as(receiver.person, contact.mixin.Employee)
if (!employee.active) {
if (!receiver.person.active) {
return []
}

View File

@ -76,7 +76,8 @@ import serverNotification, {
getPersonAccountById,
NOTIFICATION_BODY_SIZE,
NOTIFICATION_TITLE_SIZE,
UserInfo
ReceiverInfo,
SenderInfo
} from '@hcengineering/server-notification'
import serverView from '@hcengineering/server-view'
import { stripTags } from '@hcengineering/text'
@ -96,6 +97,7 @@ import {
isUserEmployeeInFieldValue,
isUserInFieldValue,
replaceAll,
toReceiverInfo,
updateNotifyContextsSpace
} from './utils'
@ -121,8 +123,8 @@ export async function getCommonNotificationTxes (
control: TriggerControl,
doc: Doc,
data: Partial<Data<CommonInboxNotification>>,
receiver: UserInfo,
sender: UserInfo,
receiver: ReceiverInfo,
sender: SenderInfo,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
space: Ref<Space>,
@ -135,7 +137,7 @@ export async function getCommonNotificationTxes (
}
const res: Tx[] = []
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo })
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { objectId: attachedTo })
const notificationTx = await pushInboxNotifications(
control,
@ -306,70 +308,38 @@ export async function getDocCollaborators (
return Array.from(collaborators.values())
}
function getDocNotifyContext (
docNotifyContexts: DocNotifyContext[],
targetUser: Ref<Account>,
attachedTo: Ref<Doc>,
res: Tx[]
): DocNotifyContext | undefined {
const context = docNotifyContexts.find((context) => context.user === targetUser && context.attachedTo === attachedTo)
if (context !== undefined) {
return context
}
const contextTx = (res as TxCUD<Doc>[]).find((tx) => {
if (tx._class === core.class.TxCreateDoc && tx.objectClass === notification.class.DocNotifyContext) {
const createTx = tx as TxCreateDoc<DocNotifyContext>
return createTx.attributes.attachedTo === attachedTo && createTx.attributes.user === targetUser
}
return false
}) as TxCreateDoc<DocNotifyContext> | undefined
if (contextTx !== undefined) {
return TxProcessor.createDoc2Doc(contextTx)
}
return undefined
}
export async function pushInboxNotifications (
control: TriggerControl,
res: Tx[],
target: UserInfo,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
space: Ref<Space>,
receiver: ReceiverInfo,
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>,
objectSpace: Ref<Space>,
contexts: DocNotifyContext[],
data: Partial<Data<InboxNotification>>,
_class: Ref<Class<InboxNotification>>,
modifiedOn: Timestamp,
shouldUpdateTimestamp = true
): Promise<TxCreateDoc<InboxNotification> | undefined> {
const account = target.account
if (account === undefined) {
return
}
const context = getDocNotifyContext(contexts, account._id, attachedTo, res)
const account = receiver.account
const context = contexts.find((context) => context.user === receiver._id && context.objectId === objectId)
let docNotifyContextId: Ref<DocNotifyContext>
if (context === undefined) {
const createContextTx = control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, space, {
user: account._id,
attachedTo,
attachedToClass,
lastUpdateTimestamp: shouldUpdateTimestamp ? modifiedOn : undefined,
isPinned: false
const createContextTx = control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, receiver.space, {
user: receiver._id,
objectId,
objectClass,
objectSpace,
isPinned: false,
lastUpdateTimestamp: shouldUpdateTimestamp ? modifiedOn : undefined
})
await control.apply([createContextTx])
if (target.account?.email !== undefined) {
if (receiver.account?.email !== undefined) {
control.operationContext.derived.targets['docNotifyContext' + createContextTx._id] = (it) => {
if (it._id === createContextTx._id) {
return [target.account?.email as string]
return [receiver.account?.email]
}
}
}
@ -385,7 +355,7 @@ export async function pushInboxNotifications (
archived: false,
...data
}
const notificationTx = control.txFactory.createTxCreateDoc(_class, space, notificationData)
const notificationTx = control.txFactory.createTxCreateDoc(_class, receiver.space, notificationData)
res.push(notificationTx)
return notificationTx
@ -490,12 +460,12 @@ export async function getTranslatedNotificationContent (
export async function createPushFromInbox (
control: TriggerControl,
target: UserInfo,
receiver: ReceiverInfo,
attachedTo: Ref<Doc>,
attachedToClass: Ref<Class<Doc>>,
data: Data<InboxNotification>,
_class: Ref<Class<InboxNotification>>,
sender: UserInfo,
sender: SenderInfo,
_id: Ref<Doc>,
cache: Map<Ref<Doc>, Doc> = new Map<Ref<Doc>, Doc>()
): Promise<Tx | undefined> {
@ -524,9 +494,9 @@ export async function createPushFromInbox (
}
const path = [workbenchId, control.workspace.workspaceUrl, notificationId, encodeObjectURI(id, attachedToClass)]
await createPushNotification(control, target._id as Ref<PersonAccount>, title, body, _id, senderPerson, path)
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, core.space.Workspace, {
user: target._id,
await createPushNotification(control, receiver._id as Ref<PersonAccount>, title, body, _id, senderPerson, path)
return control.txFactory.createTxCreateDoc(notification.class.BrowserNotification, receiver.space, {
user: receiver._id,
status: NotificationStatus.New,
title,
body,
@ -606,18 +576,14 @@ export async function pushActivityInboxNotifications (
originTx: TxCUD<Doc>,
control: TriggerControl,
res: Tx[],
target: UserInfo,
sender: UserInfo,
receiver: ReceiverInfo,
sender: SenderInfo,
object: Doc,
docNotifyContexts: DocNotifyContext[],
activityMessage: ActivityMessage,
shouldUpdateTimestamp: boolean
): Promise<TxCreateDoc<InboxNotification> | undefined> {
if (target.account === undefined) {
return
}
const content = await getNotificationContent(originTx, target.account, sender, object, control, activityMessage)
const content = await getNotificationContent(originTx, receiver.account, sender, object, control, activityMessage)
const data: Partial<Data<ActivityInboxNotification>> = {
...content,
attachedTo: activityMessage._id,
@ -627,10 +593,10 @@ export async function pushActivityInboxNotifications (
return await pushInboxNotifications(
control,
res,
target,
receiver,
activityMessage.attachedTo,
activityMessage.attachedToClass,
activityMessage.space,
object.space,
docNotifyContexts,
data,
notification.class.ActivityInboxNotification,
@ -647,8 +613,8 @@ export async function applyNotificationProviders (
control: TriggerControl,
res: Tx[],
object: Doc,
receiver: UserInfo,
sender: UserInfo
receiver: ReceiverInfo,
sender: SenderInfo
): Promise<void> {
const resources = await control.modelDb.findAll(serverNotification.class.NotificationProviderResources, {})
for (const [provider, types] of notifyResult.entries()) {
@ -690,8 +656,8 @@ export async function getNotificationTxes (
object: Doc,
tx: TxCUD<Doc>,
originTx: TxCUD<Doc>,
receiver: UserInfo,
sender: UserInfo,
receiver: ReceiverInfo,
sender: SenderInfo,
params: NotifyParams,
docNotifyContexts: DocNotifyContext[],
activityMessages: ActivityMessage[]
@ -805,7 +771,7 @@ export async function createCollabDocInfo (
return res
}
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { attachedTo: object._id })
const notifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, { objectId: object._id })
await updateContextsTimestamp(notifyContexts, originTx.modifiedOn, control, originTx.modifiedBy)
@ -833,12 +799,15 @@ export async function createCollabDocInfo (
{},
async (ctx) => await getUsersInfo(ctx, [...Array.from(targets), originTx.modifiedBy as Ref<PersonAccount>], control)
)
const sender = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
const sender: SenderInfo = usersInfo.find(({ _id }) => _id === originTx.modifiedBy) ?? {
_id: originTx.modifiedBy
}
for (const target of targets) {
const info = usersInfo.find(({ _id }) => _id === target)
const info: ReceiverInfo | undefined = toReceiverInfo(
control.hierarchy,
usersInfo.find(({ _id }) => _id === target)
)
if (info === undefined) continue
@ -858,7 +827,7 @@ export async function createCollabDocInfo (
const id = generateId() as string
control.operationContext.derived.targets[id] = (it) => {
if (ids.has(it._id)) {
return [info.account?.email as string]
return [info.account?.email]
}
}
}
@ -1048,7 +1017,7 @@ async function updateCollaboratorsMixin (
if (newCollabs.length > 0) {
const docNotifyContexts = await control.findAllCtx(ctx, notification.class.DocNotifyContext, {
user: { $in: newCollabs },
attachedTo: tx.objectId
objectId: tx.objectId
})
const infos = await ctx.with(
@ -1056,11 +1025,13 @@ async function updateCollaboratorsMixin (
{},
async (ctx) => await getUsersInfo(ctx, [...newCollabs, originTx.modifiedBy] as Ref<PersonAccount>[], control)
)
const sender = infos.find(({ _id }) => _id === originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
const sender: SenderInfo = infos.find(({ _id }) => _id === originTx.modifiedBy) ?? { _id: originTx.modifiedBy }
for (const collab of newCollabs) {
const target = infos.find(({ _id }) => _id === collab)
const target = toReceiverInfo(
hierarchy,
infos.find(({ _id }) => _id === collab)
)
if (target === undefined) continue
for (const message of activityMessages) {
@ -1154,7 +1125,7 @@ async function removeCollaboratorDoc (tx: TxRemoveDoc<Doc>, control: TriggerCont
const res: Tx[] = []
const notifyContexts = await control.findAll(
notification.class.DocNotifyContext,
{ attachedTo: tx.objectId },
{ objectId: tx.objectId },
{
projection: {
_id: 1,
@ -1545,7 +1516,7 @@ async function OnActivityMessageRemove (message: ActivityMessage, control: Trigg
return []
}
const contexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: message.attachedTo })
const contexts = await control.findAll(notification.class.DocNotifyContext, { objectId: message.attachedTo })
if (contexts.length === 0) return []
const isLastUpdate = contexts.some((context) => {

View File

@ -46,8 +46,9 @@ import serverNotification, {
getPersonAccountById,
HTMLPresenter,
NotificationPresenter,
TextPresenter,
UserInfo
ReceiverInfo,
SenderInfo,
TextPresenter
} from '@hcengineering/server-notification'
import { ActivityMessage, DocUpdateMessage } from '@hcengineering/activity'
@ -300,10 +301,12 @@ export async function updateNotifyContextsSpace (
return []
}
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { attachedTo: tx.objectId })
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, { objectId: tx.objectId })
return notifyContexts.map((value) =>
control.txFactory.createTxUpdateDoc(value._class, value.space, value._id, { space: updateTx.operations.space })
control.txFactory.createTxUpdateDoc(value._class, value.space, value._id, {
objectSpace: updateTx.operations.space
})
)
}
@ -319,7 +322,7 @@ export function getTextPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy)
return hierarchy.classHierarchyMixin(_class, serverNotification.mixin.TextPresenter)
}
async function getSenderName (control: TriggerControl, sender: UserInfo): Promise<string> {
async function getSenderName (control: TriggerControl, sender: SenderInfo): Promise<string> {
if (sender._id === core.account.System) {
return await translate(core.string.System, {})
}
@ -340,7 +343,7 @@ async function getFallbackNotificationFullfillment (
object: Doc,
originTx: TxCUD<Doc>,
control: TriggerControl,
sender: UserInfo,
sender: SenderInfo,
message?: ActivityMessage
): Promise<NotificationContent> {
const title: IntlString = notification.string.CommonNotificationTitle
@ -419,7 +422,7 @@ function getNotificationPresenter (_class: Ref<Class<Doc>>, hierarchy: Hierarchy
export async function getNotificationContent (
originTx: TxCUD<Doc>,
targetUser: PersonAccount,
sender: UserInfo,
sender: SenderInfo,
object: Doc,
control: TriggerControl,
message?: ActivityMessage
@ -471,19 +474,51 @@ export async function getUsersInfo (
ctx: MeasureContext,
ids: Ref<PersonAccount>[],
control: TriggerControl
): Promise<UserInfo[]> {
): Promise<(ReceiverInfo | SenderInfo)[]> {
const accounts = await control.modelDb.findAll(contact.class.PersonAccount, { _id: { $in: ids } })
const personIds = accounts.map((it) => it.person)
const accountById = toIdMap(accounts)
const persons = toIdMap(
await ctx.with(
'query-find',
'find-persons',
{},
async () => await control.findAll(contact.class.Person, { _id: { $in: accounts.map((it) => it.person) } })
async () => await control.findAll(contact.class.Person, { _id: { $in: personIds } })
)
)
const spaces = await ctx.with('find-person-spaces', {}, async () => {
const res = await control.findAll(contact.class.PersonSpace, { person: { $in: personIds } })
return accounts.map((account) => ({
_id: account._id,
account,
person: persons.get(account.person)
}))
return new Map(res.map((s) => [s.person, s]))
})
return ids.map((_id) => {
const account = accountById.get(_id)
return {
_id,
account,
person: account !== undefined ? persons.get(account.person) : undefined,
space: account !== undefined ? spaces.get(account.person)?._id : undefined
}
})
}
export function toReceiverInfo (hierarchy: Hierarchy, info?: SenderInfo | ReceiverInfo): ReceiverInfo | undefined {
if (info === undefined) return undefined
if (info.person === undefined) return undefined
if (info.account === undefined) return undefined
if (!('space' in info)) return undefined
if (info.space === undefined) return undefined
const isEmployee = hierarchy.hasMixin(info.person, contact.mixin.Employee)
if (!isEmployee) return undefined
const employee = hierarchy.as(info.person, contact.mixin.Employee)
if (!employee.active) return undefined
return {
_id: info._id,
account: info.account,
person: employee,
space: info.space
}
}

View File

@ -14,7 +14,7 @@
// limitations under the License.
//
import contact, { Employee, Person, PersonAccount } from '@hcengineering/contact'
import contact, { Employee, Person, PersonAccount, PersonSpace } from '@hcengineering/contact'
import { Account, Class, Doc, Mixin, Ref, Tx, TxCUD } from '@hcengineering/core'
import {
BaseNotificationType,
@ -135,7 +135,14 @@ export interface NotificationPresenter extends Class<Doc> {
presenter: Resource<NotificationContentProvider>
}
export interface UserInfo {
export interface ReceiverInfo {
_id: Ref<Account>
account: PersonAccount
person: Employee
space: Ref<PersonSpace>
}
export interface SenderInfo {
_id: Ref<Account>
account?: PersonAccount
person?: Person
@ -146,8 +153,8 @@ export type NotificationProviderFunc = (
types: BaseNotificationType[],
object: Doc,
data: InboxNotification,
receiver: UserInfo,
sender: UserInfo
receiver: ReceiverInfo,
sender: SenderInfo
) => Promise<Tx[]>
export interface NotificationProviderResources extends Doc {

View File

@ -24,7 +24,8 @@ import {
getNotificationTxes,
getCollaborators,
getTextPresenter,
getUsersInfo
getUsersInfo,
toReceiverInfo
} from '@hcengineering/server-notification-resources'
import { PersonAccount } from '@hcengineering/contact'
@ -144,7 +145,7 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
if (collaborators.length === 0) return res
const notifyContexts = await control.findAll(notification.class.DocNotifyContext, {
attachedTo: doc._id
objectId: doc._id
})
const usersInfo = await getUsersInfo(control.ctx, [...collaborators, tx.modifiedBy] as Ref<PersonAccount>[], control)
const senderInfo = usersInfo.find(({ _id }) => _id === tx.modifiedBy) ?? {
@ -152,7 +153,10 @@ async function getRequestNotificationTx (tx: TxCollectionCUD<Doc, Request>, cont
}
for (const target of collaborators) {
const targetInfo = usersInfo.find(({ _id }) => _id === target)
const targetInfo = toReceiverInfo(
control.hierarchy,
usersInfo.find(({ _id }) => _id === target)
)
if (targetInfo === undefined) continue
const txes = await getNotificationTxes(

View File

@ -31,9 +31,9 @@ import {
} from '@hcengineering/core'
import { TriggerControl } from '@hcengineering/server-core'
import telegram, { TelegramMessage, TelegramNotificationRecord } from '@hcengineering/telegram'
import notification, { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
import { BaseNotificationType, InboxNotification, NotificationType } from '@hcengineering/notification'
import setting, { Integration } from '@hcengineering/setting'
import { NotificationProviderFunc, UserInfo } from '@hcengineering/server-notification'
import { NotificationProviderFunc, ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
import { getMetadata, getResource } from '@hcengineering/platform'
import serverTelegram from '@hcengineering/server-telegram'
import { getTranslatedNotificationContent, getTextPresenter } from '@hcengineering/server-notification-resources'
@ -78,48 +78,6 @@ export async function OnMessageCreate (tx: Tx, control: TriggerControl): Promise
})
res.push(tx)
}
if (message.incoming) {
const docs = await control.findAll(notification.class.DocNotifyContext, {
attachedTo: channel._id,
user: message.modifiedBy
})
for (const doc of docs) {
// TODO: push inbox notifications
// res.push(
// control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
// $push: {
// txes: {
// _id: tx._id as Ref<TxCUD<Doc>>,
// modifiedOn: tx.modifiedOn,
// modifiedBy: tx.modifiedBy,
// isNew: true
// }
// }
// })
// )
res.push(
control.txFactory.createTxUpdateDoc(doc._class, doc.space, doc._id, {
lastUpdateTimestamp: tx.modifiedOn
})
)
}
if (docs.length === 0) {
res.push(
control.txFactory.createTxCreateDoc(notification.class.DocNotifyContext, channel.space, {
user: tx.modifiedBy,
attachedTo: channel._id,
attachedToClass: channel._class,
lastUpdateTimestamp: tx.modifiedOn,
isPinned: false
// TODO: push inbox notifications
// txes: [
// { _id: tx._id as Ref<TxCUD<Doc>>, modifiedOn: tx.modifiedOn, modifiedBy: tx.modifiedBy, isNew: true }
// ]
})
)
}
}
}
return res
@ -228,17 +186,13 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
types: BaseNotificationType[],
doc: Doc,
data: InboxNotification,
receiver: UserInfo,
sender: UserInfo
receiver: ReceiverInfo,
sender: SenderInfo
): Promise<Tx[]> => {
if (types.length === 0) {
return []
}
if (receiver.person === undefined || receiver.account?.email === undefined) {
return []
}
const botUrl = getMetadata(serverTelegram.metadata.BotUrl)
if (botUrl === undefined || botUrl === '') {
@ -246,15 +200,7 @@ const SendTelegramNotifications: NotificationProviderFunc = async (
return []
}
const isEmployee = control.hierarchy.hasMixin(receiver.person, contact.mixin.Employee)
if (!isEmployee) {
return []
}
const employee = control.hierarchy.as(receiver.person, contact.mixin.Employee)
if (!employee.active) {
if (!receiver.person.active) {
return []
}

View File

@ -44,7 +44,7 @@ import { jsonToMarkup, nodeDoc, nodeParagraph, nodeText } from '@hcengineering/t
import tracker, { Issue, IssueStatus, Project, TimeSpendReport } from '@hcengineering/tracker'
import serverTime, { OnToDo, ToDoFactory } from '@hcengineering/server-time'
import time, { ProjectToDo, ToDo, ToDoPriority, TodoAutomationHelper, WorkSlot } from '@hcengineering/time'
import { UserInfo } from '@hcengineering/server-notification'
import { ReceiverInfo, SenderInfo } from '@hcengineering/server-notification'
/**
* @public
@ -186,9 +186,23 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
}
const object = (await control.findAll(todo.attachedToClass, { _id: todo.attachedTo }))[0]
if (object === undefined) return []
const person = (
await control.findAll(contact.mixin.Employee, { _id: account.person as Ref<Employee>, active: true }, { limit: 1 })
)[0]
if (person === undefined) return []
const personSpace = (await control.findAll(contact.class.PersonSpace, { person: account.person }, { limit: 1 }))[0]
if (personSpace === undefined) return []
const receiverInfo: ReceiverInfo = {
_id: account._id,
account,
person,
space: personSpace._id
}
const senderAccount = await control.modelDb.findOne(contact.class.PersonAccount, {
_id: tx.modifiedBy as Ref<PersonAccount>
})
@ -197,13 +211,12 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
? (await control.findAll(contact.class.Person, { _id: senderAccount.person }))[0]
: undefined
const senderInfo: UserInfo = {
const senderInfo: SenderInfo = {
_id: tx.modifiedBy,
account: senderAccount,
person: senderPerson
}
const res: Tx[] = []
const notifyResult = await isShouldNotifyTx(control, createTx, tx, todo, account, true, false)
const content = await getNotificationContent(tx, account, senderInfo, todo, control)
const data: Partial<Data<CommonInboxNotification>> = {
@ -215,30 +228,18 @@ export async function OnToDoCreate (tx: TxCUD<Doc>, control: TriggerControl): Pr
messageHtml: jsonToMarkup(nodeDoc(nodeParagraph(nodeText(todo.title))))
}
const person = (await control.modelDb.findAll(contact.class.Person, { _id: account.person }))[0]
const receiverInfo: UserInfo = {
_id: account._id,
account,
person
}
res.push(
...(await getCommonNotificationTxes(
control,
object,
data,
receiverInfo,
senderInfo,
object._id,
object._class,
object.space,
createTx.modifiedOn,
notifyResult
))
return await getCommonNotificationTxes(
control,
object,
data,
receiverInfo,
senderInfo,
object._id,
object._class,
object.space,
createTx.modifiedOn,
notifyResult
)
return res
}
/**

View File

@ -1953,7 +1953,7 @@ async function createPersonAccount (
const ops = new TxOperations(connection, core.account.System)
const name = combineName(account.first, account.last)
// Check if EmployeeAccount is not exists
// Check if PersonAccount is not exists
if (shouldReplaceCurrent) {
const currentAccount = await ops.findOne(contact.class.PersonAccount, {})
if (currentAccount !== undefined) {

View File

@ -1520,8 +1520,7 @@ class MongoTxAdapter extends MongoAdapterBase implements TxAdapter {
(tx._class === core.class.TxCreateDoc ||
tx._class === core.class.TxUpdateDoc ||
tx._class === core.class.TxRemoveDoc) &&
((tx as TxCUD<Doc>).objectClass === 'contact:class:PersonAccount' ||
(tx as TxCUD<Doc>).objectClass === 'contact:class:EmployeeAccount')
(tx as TxCUD<Doc>).objectClass === 'contact:class:PersonAccount'
)
}
model.forEach((tx) => (tx.modifiedBy === core.account.System && !isPersonAccount(tx) ? systemTx : userTx).push(tx))

View File

@ -332,7 +332,7 @@ export async function upgradeModel (
(it) =>
it.modifiedBy !== core.account.System ||
(it as TxCUD<Doc>).objectClass === contact.class.Person ||
(it as TxCUD<Doc>).objectClass === 'contact:class:EmployeeAccount'
(it as TxCUD<Doc>).objectClass === 'contact:class:PersonAccount'
)
)
]

View File

@ -1,19 +1,21 @@
import { Account, Doc, Ref, TxOperations } from '@hcengineering/core'
import notification, { DocNotifyContext } from '@hcengineering/notification'
import { IntlString } from '@hcengineering/platform'
import { PersonSpace } from '@hcengineering/contact'
import github from '@hcengineering/github'
export async function createNotification (
client: TxOperations,
forDoc: Doc,
data: { user: Ref<Account>, message: IntlString, props: Record<string, any> }
data: { user: Ref<Account>, space: Ref<PersonSpace>, message: IntlString, props: Record<string, any> }
): Promise<void> {
let docNotifyContext = await client.findOne(notification.class.DocNotifyContext, { attachedTo: forDoc._id })
let docNotifyContext = await client.findOne(notification.class.DocNotifyContext, { objectId: forDoc._id })
if (docNotifyContext?._id === undefined) {
const docNotifyContextId = await client.createDoc(notification.class.DocNotifyContext, forDoc.space, {
attachedTo: forDoc._id,
attachedToClass: forDoc._class,
const docNotifyContextId = await client.createDoc(notification.class.DocNotifyContext, data.space, {
objectId: forDoc._id,
objectClass: forDoc._class,
objectSpace: forDoc.space,
user: data.user,
isPinned: false
})
@ -21,7 +23,6 @@ export async function createNotification (
}
// Check if we had already same notification send, and just unmark it viewed.
const existing = await client.findOne(notification.class.CommonInboxNotification, {
user: data.user,
message: data.message,
@ -32,7 +33,7 @@ export async function createNotification (
isViewed: false
})
} else {
await client.createDoc(notification.class.CommonInboxNotification, forDoc.space, {
await client.createDoc(notification.class.CommonInboxNotification, data.space, {
user: data.user,
icon: github.icon.Github,
message: data.message,

View File

@ -409,13 +409,17 @@ export class PlatformWorker {
const person = (await client.findOne(contact.class.Person, { _id: account.person })) as Person
if (person !== undefined) {
if (!revoke) {
await createNotification(client, person, {
user: account._id,
message: github.string.AuthenticatedWithGithub,
props: {
login: update.login
}
})
const personSpace = await client.findOne(contact.class.PersonSpace, { person: person._id })
if (personSpace !== undefined) {
await createNotification(client, person, {
user: account._id,
space: personSpace._id,
message: github.string.AuthenticatedWithGithub,
props: {
login: update.login
}
})
}
const githubAccount = (await client.findOne(core.class.Account, {
email: 'github:' + update.login
@ -427,23 +431,31 @@ export class PlatformWorker {
const dPerson = (await client.findOne(contact.class.Person, { _id: dummyPerson })) as Person
if (person !== undefined && dPerson !== undefined) {
await createNotification(client, dPerson, {
user: account._id,
message: github.string.AuthenticatedWithGithubEmployee,
props: {
login: update.login
}
})
const personSpace = await client.findOne(contact.class.PersonSpace, { person: person._id })
if (personSpace !== undefined) {
await createNotification(client, dPerson, {
user: githubAccount._id,
space: personSpace._id,
message: github.string.AuthenticatedWithGithubEmployee,
props: {
login: update.login
}
})
}
}
}
} else {
await createNotification(client, person, {
user: account._id,
message: github.string.AuthenticationRevokedGithub,
props: {
login: update.login
}
})
const personSpace = await client.findOne(contact.class.PersonSpace, { person: person._id })
if (personSpace !== undefined) {
await createNotification(client, person, {
user: account._id,
space: personSpace._id,
message: github.string.AuthenticationRevokedGithub,
props: {
login: update.login
}
})
}
}
}

View File

@ -543,11 +543,15 @@ export class GithubWorker implements IntegrationManager {
if (accountRef !== undefined) {
const person = await this.client.findOne(contact.class.Person, { _id: accountRef.person })
if (person !== undefined) {
await createNotification(this._client, person, {
user: account,
message: github.string.AuthenticatedWithGithubRequired,
props: {}
})
const personSpace = await this.client.findOne(contact.class.PersonSpace, { person: person._id })
if (personSpace !== undefined) {
await createNotification(this._client, person, {
user: account,
space: personSpace._id,
message: github.string.AuthenticatedWithGithubRequired,
props: {}
})
}
}
}
this.ctx.info('get octokit: return bot', { account })