[UBER-781] Adding chats to inbox after sending messages (#3621)

Signed-off-by: Oleg Solodkov <oleg.solodkov@xored.com>
This commit is contained in:
Oleg Solodkov 2023-08-23 18:23:58 +07:00 committed by GitHub
parent 77f6b3dfd6
commit 04274d1167
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 116 additions and 55 deletions

View File

@ -46,10 +46,11 @@ export function createModel (builder: Builder): void {
}) })
builder.createDoc(serverCore.class.Trigger, core.space.Model, { builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverChunter.trigger.OnDmCreate, trigger: serverChunter.trigger.OnMessageSent,
txMatch: { txMatch: {
objectClass: chunter.class.DirectMessage, objectClass: chunter.class.DirectMessage,
_class: core.class.TxCreateDoc _class: core.class.TxCollectionCUD,
collection: 'messages'
} }
}) })

View File

@ -73,6 +73,7 @@
"ConfigDescription": "Extension to perform text communications", "ConfigDescription": "Extension to perform text communications",
"LastMessage": "Last message", "LastMessage": "Last message",
"You": "You", "You": "You",
"YouHaveStartedAConversation": "You have started a conversation" "YouHaveJoinedTheConversation": "You have joined the conversation",
"NoMessages": "There are no messages yet"
} }
} }

View File

@ -73,6 +73,7 @@
"ConfigDescription": "Расширение для текстовых переписок", "ConfigDescription": "Расширение для текстовых переписок",
"LastMessage": "Последнее сообщение", "LastMessage": "Последнее сообщение",
"You": "Вы", "You": "Вы",
"YouHaveStartedAConversation": "Вы начали диалог" "YouHaveJoinedTheConversation": "Вы присоединились к диалогу",
"NoMessages": "Сообщений пока нет"
} }
} }

View File

@ -55,7 +55,7 @@
<MessagePreview value={message} /> <MessagePreview value={message} />
{/each} {/each}
{:else} {:else}
<Label label={chunterResources.string.YouHaveStartedAConversation} /> <Label label={chunterResources.string.NoMessages} />
{/if} {/if}
</div> </div>

View File

@ -89,6 +89,7 @@ export default mergeIds(chunterId, chunter, {
NoResults: '' as IntlString, NoResults: '' as IntlString,
CopyLink: '' as IntlString, CopyLink: '' as IntlString,
You: '' as IntlString, You: '' as IntlString,
YouHaveStartedAConversation: '' as IntlString YouHaveJoinedTheConversation: '' as IntlString,
NoMessages: '' as IntlString
} }
}) })

View File

@ -190,8 +190,7 @@ export default plugin(chunterId, {
DMNotification: '' as Ref<NotificationType>, DMNotification: '' as Ref<NotificationType>,
MentionNotification: '' as Ref<NotificationType>, MentionNotification: '' as Ref<NotificationType>,
ThreadNotification: '' as Ref<NotificationType>, ThreadNotification: '' as Ref<NotificationType>,
ChannelNotification: '' as Ref<NotificationType>, ChannelNotification: '' as Ref<NotificationType>
DMCreationNotification: '' as Ref<NotificationType>
}, },
app: { app: {
Chunter: '' as Ref<Doc> Chunter: '' as Ref<Doc>

View File

@ -19,7 +19,7 @@
"Change": "Change", "Change": "Change",
"AddedRemoved": "Added/removed", "AddedRemoved": "Added/removed",
"YouAddedCollaborators": "You have been added to collaborators", "YouAddedCollaborators": "You have been added to collaborators",
"YouHaveStartedAConversation": "You have started a conversation", "YouHaveJoinedTheConversation": "You have joined the conversation",
"ChangeCollaborators": "changed collaborators", "ChangeCollaborators": "changed collaborators",
"Activity": "Activity", "Activity": "Activity",
"People": "People", "People": "People",

View File

@ -19,7 +19,7 @@
"Change": "Изменено", "Change": "Изменено",
"AddedRemoved": "Добавлено/удалено", "AddedRemoved": "Добавлено/удалено",
"YouAddedCollaborators": "Вы были добавлены как участник", "YouAddedCollaborators": "Вы были добавлены как участник",
"YouHaveStartedAConversation": "Вы начали диалог", "YouHaveJoinedTheConversation": "Вы присоединились к диалогу",
"ChangeCollaborators": "изменил(а) участники", "ChangeCollaborators": "изменил(а) участники",
"Activity": "Активность", "Activity": "Активность",
"People": "Люди", "People": "Люди",

View File

@ -28,6 +28,7 @@
import EmployeeInbox from './EmployeeInbox.svelte' import EmployeeInbox from './EmployeeInbox.svelte'
import Filter from './Filter.svelte' import Filter from './Filter.svelte'
import People from './People.svelte' import People from './People.svelte'
import { subscribe } from '../utils'
export let visibileNav: boolean export let visibileNav: boolean
let filter: 'all' | 'read' | 'unread' = 'all' let filter: 'all' | 'read' | 'unread' = 'all'
@ -100,6 +101,10 @@
const personAccount = await client.findOne(contact.class.PersonAccount, { person: employee._id }) const personAccount = await client.findOne(contact.class.PersonAccount, { person: employee._id })
if (personAccount !== undefined) { if (personAccount !== undefined) {
const channel = await getDirectChannel(client, me._id as Ref<PersonAccount>, personAccount._id) const channel = await getDirectChannel(client, me._id as Ref<PersonAccount>, personAccount._id)
// re-subscribing in case DM was removed from notifications
await subscribe(chunter.class.DirectMessage, channel)
openDM(channel) openDM(channel)
} }
} }

View File

@ -23,4 +23,4 @@
export let value: DirectMessage export let value: DirectMessage
</script> </script>
<Label label={notification.string.YouHaveStartedAConversation} /> <Label label={notification.string.YouHaveJoinedTheConversation} />

View File

@ -30,7 +30,7 @@ export default mergeIds(notificationId, notification, {
Change: '' as IntlString, Change: '' as IntlString,
AddedRemoved: '' as IntlString, AddedRemoved: '' as IntlString,
YouAddedCollaborators: '' as IntlString, YouAddedCollaborators: '' as IntlString,
YouHaveStartedAConversation: '' as IntlString, YouHaveJoinedTheConversation: '' as IntlString,
ChangeCollaborators: '' as IntlString, ChangeCollaborators: '' as IntlString,
Activity: '' as IntlString, Activity: '' as IntlString,
People: '' as IntlString, People: '' as IntlString,

View File

@ -14,7 +14,7 @@
// limitations under the License. // limitations under the License.
// //
import { Account, Class, Doc, getCurrentAccount, Ref } from '@hcengineering/core' import { Account, Class, Doc, DocumentUpdate, getCurrentAccount, Ref, TxOperations } from '@hcengineering/core'
import notification, { Collaborators, DocUpdates, NotificationClient } from '@hcengineering/notification' import notification, { Collaborators, DocUpdates, NotificationClient } from '@hcengineering/notification'
import { createQuery, getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
@ -119,29 +119,69 @@ export async function hasntNotifications (object: DocUpdates): Promise<boolean>
return !object.txes.some((p) => p.isNew) return !object.txes.some((p) => p.isNew)
} }
enum OpWithMe {
Add = 'add',
Remove = 'remove'
}
async function updateMeInCollaborators (
client: TxOperations,
docClass: Ref<Class<Doc>>,
docId: Ref<Doc>,
op: OpWithMe
): Promise<void> {
const me = getCurrentAccount()._id
const hierarchy = client.getHierarchy()
const target = await client.findOne(docClass, { _id: docId })
if (target !== undefined) {
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
const collab = hierarchy.as(target, notification.mixin.Collaborators)
let collabUpdate: DocumentUpdate<Collaborators> | undefined
if (collab.collaborators.includes(me) && op === OpWithMe.Remove) {
collabUpdate = {
$pull: {
collaborators: me
}
}
} else if (!collab.collaborators.includes(me) && op === OpWithMe.Add) {
collabUpdate = {
$push: {
collaborators: me
}
}
}
if (collabUpdate !== undefined) {
await client.updateMixin(
collab._id,
collab._class,
collab.space,
notification.mixin.Collaborators,
collabUpdate
)
}
}
}
}
/** /**
* @public * @public
*/ */
export async function unsubscribe (object: DocUpdates): Promise<void> { export async function unsubscribe (object: DocUpdates): Promise<void> {
const me = getCurrentAccount()._id
const client = getClient() const client = getClient()
const hierarchy = client.getHierarchy() await updateMeInCollaborators(client, object.attachedToClass, object.attachedTo, OpWithMe.Remove)
const target = await client.findOne(object.attachedToClass, { _id: object.attachedTo })
if (target !== undefined) {
if (hierarchy.hasMixin(target, notification.mixin.Collaborators)) {
const collab = hierarchy.as(target, notification.mixin.Collaborators)
if (collab.collaborators.includes(me)) {
await client.updateMixin(collab._id, collab._class, collab.space, notification.mixin.Collaborators, {
$pull: {
collaborators: me
}
})
}
}
}
await client.remove(object) await client.remove(object)
} }
/**
* @public
*/
export async function subscribe (docClass: Ref<Class<Doc>>, docId: Ref<Doc>): Promise<void> {
const client = getClient()
await updateMeInCollaborators(client, docClass, docId, OpWithMe.Add)
}
/** /**
* @public * @public
*/ */

View File

@ -13,15 +13,7 @@
// limitations under the License. // limitations under the License.
// //
import chunter, { import chunter, { Backlink, chunterId, ChunterSpace, Comment, Message, ThreadMessage } from '@hcengineering/chunter'
Backlink,
chunterId,
ChunterSpace,
Comment,
DirectMessage,
Message,
ThreadMessage
} from '@hcengineering/chunter'
import contact, { Employee, PersonAccount } from '@hcengineering/contact' import contact, { Employee, PersonAccount } from '@hcengineering/contact'
import core, { import core, {
Account, Account,
@ -44,7 +36,7 @@ import core, {
import notification, { Collaborators, NotificationType } from '@hcengineering/notification' import notification, { Collaborators, NotificationType } from '@hcengineering/notification'
import { getMetadata } from '@hcengineering/platform' import { getMetadata } from '@hcengineering/platform'
import serverCore, { TriggerControl } from '@hcengineering/server-core' import serverCore, { TriggerControl } from '@hcengineering/server-core'
import { pushNotification, getDocCollaborators, getMixinTx } from '@hcengineering/server-notification-resources' import { getDocCollaborators, getMixinTx, pushNotification } from '@hcengineering/server-notification-resources'
import { workbenchId } from '@hcengineering/workbench' import { workbenchId } from '@hcengineering/workbench'
/** /**
@ -216,29 +208,50 @@ export async function ChunterTrigger (tx: Tx, control: TriggerControl): Promise<
/** /**
* @public * @public
* Sends notification to the message sender in case when DM
* notifications are deleted or hidden. This is required for
* the DM to re-appear in the sender's inbox.
*/ */
export async function OnDmCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> { export async function OnMessageSent (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const ptx = tx as TxCreateDoc<DirectMessage> const ptx = TxProcessor.extractTx(tx) as TxCreateDoc<Message>
if (ptx._class !== core.class.TxCreateDoc) return []
const message = TxProcessor.createDoc2Doc(ptx)
if (message.createdBy === undefined) return []
if (!control.hierarchy.isDerived(message.attachedToClass, chunter.class.DirectMessage)) return []
const channel = (await control.findAll(chunter.class.DirectMessage, { _id: message.attachedTo })).shift()
if (channel === undefined || channel.members.length !== 2 || !channel.private) return []
const res: Tx[] = [] const res: Tx[] = []
if (tx.createdBy == null) return [] const docUpdates = await control.findAll(notification.class.DocUpdates, { attachedTo: channel._id })
const dm = TxProcessor.createDoc2Doc(ptx) // binding notification to the DM creation tx to properly display it in inbox
const dmCreationTx = (
await control.findAll(core.class.TxCreateDoc, { objectClass: channel._class, objectId: channel._id })
).shift()
if (dmCreationTx === undefined) return []
if (dm.members.length > 2) return [] const sender = message.createdBy
const docUpdate = docUpdates.find((du) => du.user === sender)
let dmWithPerson: Ref<Account> | undefined if (docUpdate === undefined) {
for (const person of dm.members) { let anotherPerson: Ref<Account> | undefined
if (person !== tx.createdBy) { for (const person of channel.members) {
dmWithPerson = person if (person !== sender) {
break anotherPerson = person
break
}
} }
if (anotherPerson == null) return []
pushNotification(control, res, sender, channel, dmCreationTx, docUpdates, anotherPerson)
} else if (docUpdate.hidden) {
res.push(control.txFactory.createTxUpdateDoc(docUpdate._class, docUpdate.space, docUpdate._id, { hidden: false }))
} }
if (dmWithPerson == null) return []
pushNotification(control, res, tx.createdBy, dm, ptx, [], dmWithPerson)
return res return res
} }
@ -309,7 +322,7 @@ export async function IsChannelMessage (
export default async () => ({ export default async () => ({
trigger: { trigger: {
ChunterTrigger, ChunterTrigger,
OnDmCreate OnMessageSent
}, },
function: { function: {
CommentRemove, CommentRemove,

View File

@ -30,7 +30,7 @@ export const serverChunterId = 'server-chunter' as Plugin
export default plugin(serverChunterId, { export default plugin(serverChunterId, {
trigger: { trigger: {
ChunterTrigger: '' as Resource<TriggerFunc>, ChunterTrigger: '' as Resource<TriggerFunc>,
OnDmCreate: '' as Resource<TriggerFunc> OnMessageSent: '' as Resource<TriggerFunc>
}, },
function: { function: {
CommentRemove: '' as Resource< CommentRemove: '' as Resource<