UBERF-4707: fix activity messages updating (#4238)

Signed-off-by: Kristina Fefelova <kristin.fefelova@gmail.com>
This commit is contained in:
Kristina 2023-12-21 19:31:32 +04:00 committed by GitHub
parent 93551a71b0
commit ba49e4d66e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 237 additions and 129 deletions

View File

@ -468,7 +468,6 @@ export function createModel (builder: Builder): void {
objectClass: notification.mixin.Collaborators,
action: 'update',
icon: notification.icon.Notifications,
component: notification.activity.TxCollaboratorsChange,
label: notification.string.ChangeCollaborators
},
notification.ids.NotificationCollaboratorsChanged

View File

@ -140,7 +140,7 @@ export function createModel (builder: Builder): void {
objectClass: telegram.class.Message,
action: 'create',
icon: contact.icon.Telegram,
component: telegram.notification.NotificationMessageCreated,
component: telegram.activity.TelegramMessageCreated,
label: telegram.string.SharedMessages
},
telegram.ids.TelegramMessageCreatedActivityViewlet

View File

@ -52,6 +52,7 @@ export default mergeIds(telegramId, telegram, {
},
activity: {
TxMessage: '' as AnyComponent,
TxSharedCreate: '' as AnyComponent
TxSharedCreate: '' as AnyComponent,
TelegramMessageCreated: '' as AnyComponent
}
})

View File

@ -22,17 +22,11 @@ import core, {
groupByArray,
type Hierarchy,
type Ref,
SortingOrder,
type TxCollectionCUD,
type TxCreateDoc,
type TxCUD,
type TxMixin,
TxProcessor,
type TxUpdateDoc
SortingOrder
} from '@hcengineering/core'
import view, { type AttributeModel } from '@hcengineering/view'
import { getClient, getFiltredKeys } from '@hcengineering/presentation'
import { getAttributePresenter, getDocLinkTitle } from '@hcengineering/view-resources'
import { buildRemovedDoc, getAttributePresenter, getDocLinkTitle } from '@hcengineering/view-resources'
import { type Person } from '@hcengineering/contact'
import { type IntlString } from '@hcengineering/platform'
import { type AnyComponent } from '@hcengineering/ui'
@ -60,36 +54,6 @@ const valueTypes: ReadonlyArray<Ref<Class<Doc>>> = [
core.class.TypeHyperlink
]
async function buildRemovedDoc (client: Client, objectId: Ref<Doc>, _class: Ref<Class<Doc>>): Promise<Doc | undefined> {
const isAttached = client.getHierarchy().isDerived(_class, core.class.AttachedDoc)
const txes = await client.findAll<TxCUD<Doc>>(
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
isAttached
? { 'tx.objectId': objectId as Ref<AttachedDoc> }
: {
objectId
},
{ sort: { modifiedOn: 1 } }
)
const createTx = isAttached
? txes.map((tx) => (tx as TxCollectionCUD<Doc, AttachedDoc>).tx).find((tx) => tx._class === core.class.TxCreateDoc)
: txes.find((tx) => tx._class === core.class.TxCreateDoc)
if (createTx === undefined) return
let doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Doc>)
for (let tx of txes) {
tx = TxProcessor.extractTx(tx) as TxCUD<Doc>
if (tx._class === core.class.TxUpdateDoc) {
doc = TxProcessor.updateDoc2Doc(doc, tx as TxUpdateDoc<Doc>)
} else if (tx._class === core.class.TxMixin) {
const mixinTx = tx as TxMixin<Doc, Doc>
doc = TxProcessor.updateMixin4Doc(doc, mixinTx)
}
}
return doc
}
export async function getAttributeValues (client: Client, values: any[], attrClass: Ref<Class<Doc>>): Promise<any[]> {
if (values.some((value) => typeof value !== 'string')) {
return values
@ -127,23 +91,6 @@ export function getCollectionAttribute (
return undefined
}
export async function getActivityObject (
client: Client,
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>
): Promise<{ isRemoved: boolean, object?: Doc }> {
const object = await client.findOne(objectClass, { _id: objectId })
if (object !== undefined) {
return { isRemoved: false, object }
}
return {
isRemoved: true,
object: await buildRemovedDoc(client, objectId, objectClass)
}
}
export async function getAttributeModel (
client: Client,
attributeUpdates: DocAttributeUpdates | undefined,

View File

@ -20,7 +20,7 @@
import view from '@hcengineering/view'
import activity, { DisplayDocUpdateMessage, DocUpdateMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import NotificationObjectValue from './DocUpdateMessageObjectValue.svelte'
import DocUpdateMessageObjectValue from './DocUpdateMessageObjectValue.svelte'
export let message: DisplayDocUpdateMessage
export let viewlet: DocUpdateMessageViewlet | undefined
@ -42,7 +42,6 @@
$: valueMessages = message.previousMessages?.length ? [...message.previousMessages, message] : [message]
$: hasDifferentActions = message.previousMessages?.some(({ action }) => action !== message.action)
// TODO: use AcrivityIcon
$: icon = viewlet?.icon ?? collectionAttribute?.icon ?? clazz.icon ?? activity.icon.Activity
</script>
@ -70,7 +69,7 @@
{@const createMessages = valueMessages.filter(({ action }) => action === 'create')}
{#each createMessages as valueMessage, index}
<NotificationObjectValue
<DocUpdateMessageObjectValue
message={valueMessage}
{objectPresenter}
{objectPanel}
@ -80,7 +79,7 @@
/>
{/each}
{#each removeMessages as valueMessage, index}
<NotificationObjectValue
<DocUpdateMessageObjectValue
message={valueMessage}
{objectPresenter}
{objectPanel}
@ -91,7 +90,7 @@
{/each}
{:else}
{#each valueMessages as valueMessage, index}
<NotificationObjectValue
<DocUpdateMessageObjectValue
message={valueMessage}
{objectPresenter}
{objectPanel}

View File

@ -13,15 +13,13 @@
// limitations under the License.
-->
<script lang="ts">
import { DocNavLink, getDocLinkTitle } from '@hcengineering/view-resources'
import { buildRemovedDoc, checkIsObjectRemoved, DocNavLink, getDocLinkTitle } from '@hcengineering/view-resources'
import { Component, Icon, IconAdd, IconDelete } from '@hcengineering/ui'
import { getClient } from '@hcengineering/presentation'
import { createQuery, getClient } from '@hcengineering/presentation'
import view, { ObjectPanel, ObjectPresenter } from '@hcengineering/view'
import { Doc } from '@hcengineering/core'
import { Class, Doc, Ref } from '@hcengineering/core'
import { DisplayDocUpdateMessage, DocUpdateMessageViewlet } from '@hcengineering/activity'
import { getActivityObject } from '../../activityMessagesUtils'
export let message: DisplayDocUpdateMessage
export let viewlet: DocUpdateMessageViewlet | undefined
export let objectPanel: ObjectPanel | undefined
@ -30,6 +28,9 @@
export let hasSeparator: boolean = false
const client = getClient()
const objectQuery = createQuery()
let object: Doc | undefined = undefined
async function getValue (object: Doc | undefined): Promise<string | undefined> {
if (object === undefined) {
@ -42,9 +43,23 @@
return await getDocLinkTitle(client, object._id, object._class, object)
}
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>) {
const isRemoved = await checkIsObjectRemoved(client, _id, _class)
if (isRemoved) {
object = await buildRemovedDoc(client, _id, _class)
} else {
objectQuery.query(_class, { _id }, (res) => {
object = res[0]
})
}
}
$: loadObject(message.objectId, message.objectClass)
</script>
{#await getActivityObject(client, message.objectId, message.objectClass) then { object }}
{#if object}
{#await getValue(object) then value}
{#if withIcon && message.action === 'create'}
<Icon icon={IconAdd} size="x-small" />
@ -71,7 +86,7 @@
<Component is={objectPresenter.presenter} props={{ value: object, accent: true, shouldShowAvatar: false }} />
{/if}
{/await}
{/await}
{/if}
<style lang="scss">
.valueLink {

View File

@ -35,7 +35,8 @@
import DocUpdateMessageContent from './DocUpdateMessageContent.svelte'
import DocUpdateMessageAttributes from './DocUpdateMessageAttributes.svelte'
import { getAttributeModel, getCollectionAttribute, getActivityObject } from '../../activityMessagesUtils'
import { getAttributeModel, getCollectionAttribute } from '../../activityMessagesUtils'
import { buildRemovedDoc, checkIsObjectRemoved } from '@hcengineering/view-resources'
export let value: DisplayDocUpdateMessage
export let showNotify: boolean = false
@ -51,6 +52,8 @@
const viewletQuery = createQuery()
const userQuery = createQuery()
const objectQuery = createQuery()
const parentObjectQuery = createQuery()
const collectionAttribute = getCollectionAttribute(hierarchy, value.attachedToClass, value.updateCollection)
const clazz = hierarchy.getClass(value.objectClass)
@ -111,26 +114,40 @@
$: person = user?.person && $personByIdStore.get(user.person)
$: getActivityObject(client, value.objectId, value.objectClass).then((result) => {
isObjectLoading = false
object = result.object
isObjectRemoved = result.isRemoved
})
$: loadObject(value.objectId, value.objectClass)
$: loadParentObject(value, parentMessage)
$: getParentObject(value, parentMessage).then((result) => {
parentObject = result?.object
})
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>) {
isObjectRemoved = await checkIsObjectRemoved(client, _id, _class)
async function getParentObject (message: DocUpdateMessage, parentMessage?: ActivityMessage) {
if (parentMessage) {
return await getActivityObject(client, parentMessage.attachedTo, parentMessage.attachedToClass)
if (isObjectRemoved) {
object = await buildRemovedDoc(client, _id, _class)
isObjectLoading = false
} else {
objectQuery.query(_class, { _id }, (res) => {
isObjectLoading = false
object = res[0]
})
}
}
if (message.objectId === message.attachedTo) {
async function loadParentObject (message: DocUpdateMessage, parentMessage?: ActivityMessage) {
if (!parentMessage && message.objectId === message.attachedTo) {
return
}
return await getActivityObject(client, message.attachedTo, message.attachedToClass)
const _id = parentMessage ? parentMessage.attachedTo : message.attachedTo
const _class = parentMessage ? parentMessage.attachedToClass : message.attachedToClass
const isRemoved = await checkIsObjectRemoved(client, _id, _class)
if (isRemoved) {
parentObject = await buildRemovedDoc(client, _id, _class)
return
}
parentObjectQuery.query(_class, { _id }, (res) => {
parentObject = res[0]
})
}
$: if (object && value.objectClass !== object._class) {
@ -172,13 +189,13 @@
{#if viewlet?.component}
<ShowMore>
<div class="customContent">
{#each [...(value?.previousMessages ?? []), value] as msg}
{#await getActivityObject(client, msg.objectId, msg.objectClass) then { object }}
{#if object}
<Component is={viewlet.component} props={{ message: value, value: object }} />
{/if}
{/await}
{#each value?.previousMessages ?? [] as msg}
<Component is={viewlet.component} props={{ message: msg, _id: msg.objectId, _class: msg.objectClass }} />
{/each}
<Component
is={viewlet.component}
props={{ message: value, _id: value.objectId, _class: value.objectClass, value: object }}
/>
</div>
</ShowMore>
{:else if value.action === 'create' || value.action === 'remove'}

View File

@ -14,8 +14,20 @@
-->
<script lang="ts">
import { Reaction } from '@hcengineering/activity'
import { Ref } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
export let value: Reaction | undefined
import activity from '../../plugin'
export let _id: Ref<Reaction>
export let value: Reaction | undefined = undefined
const query = createQuery()
$: value === undefined &&
query.query(activity.class.Reaction, { _id }, (res) => {
value = res[0]
})
</script>
<span class="labels-row gap-1">

View File

@ -14,13 +14,25 @@
-->
<script lang="ts">
import { DocUpdateMessage } from '@hcengineering/activity'
import { Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Attachment } from '@hcengineering/attachment'
import { getOrBuildObject } from '@hcengineering/view-resources'
import attachment from '../../plugin'
import AttachmentPresenter from '../AttachmentPresenter.svelte'
import RemovedAttachmentPresenter from '../RemovedAttachmentPresenter.svelte'
export let message: DocUpdateMessage
export let value: Attachment | undefined
export let _id: Ref<Attachment>
export let value: Attachment | undefined = undefined
const client = getClient()
$: value === undefined &&
getOrBuildObject<Attachment>(client, _id, attachment.class.Attachment).then((res) => {
value = res
})
</script>
{#if value}

View File

@ -14,14 +14,28 @@
-->
<script lang="ts">
import type { Backlink } from '@hcengineering/chunter'
import { MessageViewer } from '@hcengineering/presentation'
import { createQuery, MessageViewer } from '@hcengineering/presentation'
import { Ref } from '@hcengineering/core'
export let value: Backlink
import chunter from '../plugin'
export let _id: Ref<Backlink> | undefined = undefined
export let value: Backlink | undefined = undefined
const query = createQuery()
$: value === undefined &&
_id &&
query.query(chunter.class.Backlink, { _id }, (res) => {
value = res[0]
})
</script>
<div class="root">
<MessageViewer message={value?.message} />
</div>
{#if value}
<div class="root">
<MessageViewer message={value.message} />
</div>
{/if}
<style lang="scss">
.root {

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Doc, Ref, SortingOrder } from '@hcengineering/core'
import { Class, Doc, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { Component, IconClose, Spinner } from '@hcengineering/ui'
import view from '@hcengineering/view'
@ -22,9 +22,9 @@
import {
ActivityExtension,
ActivityMessagePresenter,
combineActivityMessages,
getActivityObject
combineActivityMessages
} from '@hcengineering/activity-resources'
import { buildRemovedDoc, checkIsObjectRemoved } from '@hcengineering/view-resources'
export let _id: Ref<ActivityMessage>
@ -33,6 +33,7 @@
const dispatch = createEventDispatcher()
const selectedMessageQuery = createQuery()
const messagesQuery = createQuery()
const objectQuery = createQuery()
let messages: DisplayActivityMessage[] = []
let selectedMessage: ActivityMessage | undefined = undefined
@ -52,10 +53,19 @@
}
)
$: selectedMessage &&
getActivityObject(client, selectedMessage.attachedTo, selectedMessage.attachedToClass).then((res) => {
object = res.object
})
async function loadObject (_id: Ref<Doc>, _class: Ref<Class<Doc>>) {
const isRemoved = await checkIsObjectRemoved(client, _id, _class)
if (isRemoved) {
object = await buildRemovedDoc(client, _id, _class)
} else {
objectQuery.query(_class, { _id }, (res) => {
object = res[0]
})
}
}
$: selectedMessage && loadObject(selectedMessage.attachedTo, selectedMessage.attachedToClass)
$: objectPresenter =
selectedMessage && hierarchy.classHierarchyMixin(selectedMessage.attachedToClass, view.mixin.ObjectPresenter)

View File

@ -35,22 +35,23 @@
"svelte-eslint-parser": "^0.33.1"
},
"dependencies": {
"@hcengineering/platform": "^0.6.9",
"svelte": "^4.2.5",
"@hcengineering/telegram": "^0.6.14",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/text-editor": "^0.6.0",
"@hcengineering/contact": "^0.6.20",
"@hcengineering/setting": "^0.6.11",
"@hcengineering/chunter": "^0.6.12",
"@hcengineering/login": "^0.6.8",
"@hcengineering/core": "^0.6.28",
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/notification-resources": "^0.6.0",
"@hcengineering/attachment": "^0.6.9",
"@hcengineering/attachment-resources": "^0.6.0",
"@hcengineering/chunter": "^0.6.12",
"@hcengineering/contact": "^0.6.20",
"@hcengineering/contact-resources": "^0.6.0",
"@hcengineering/core": "^0.6.28",
"@hcengineering/login": "^0.6.8",
"@hcengineering/notification-resources": "^0.6.0",
"@hcengineering/panel": "^0.6.15",
"@hcengineering/templates": "^0.6.7"
"@hcengineering/platform": "^0.6.9",
"@hcengineering/presentation": "^0.6.2",
"@hcengineering/setting": "^0.6.11",
"@hcengineering/telegram": "^0.6.14",
"@hcengineering/templates": "^0.6.7",
"@hcengineering/text-editor": "^0.6.0",
"@hcengineering/ui": "^0.6.11",
"@hcengineering/view-resources": "^0.6.0",
"svelte": "^4.2.5"
}
}

View File

@ -13,10 +13,32 @@
// limitations under the License.
-->
<script lang="ts">
import { MessageViewer } from '@hcengineering/presentation'
import { createQuery, getClient, MessageViewer } from '@hcengineering/presentation'
import { TelegramMessage } from '@hcengineering/telegram'
import { Ref } from '@hcengineering/core'
import { buildRemovedDoc, checkIsObjectRemoved } from '@hcengineering/view-resources'
export let value: TelegramMessage | undefined
import telegram from '../../plugin'
export let _id: Ref<TelegramMessage> | undefined = undefined
export let value: TelegramMessage | undefined = undefined
const query = createQuery()
const client = getClient()
$: value === undefined && _id && loadObject(_id)
async function loadObject (_id: Ref<TelegramMessage>) {
const isRemoved = await checkIsObjectRemoved(client, _id, telegram.class.Message)
if (isRemoved) {
value = await buildRemovedDoc(client, _id, telegram.class.Message)
} else {
query.query(telegram.class.Message, { _id }, (res) => {
value = res[0]
})
}
}
</script>
{#if value}

View File

@ -24,7 +24,7 @@ import Reconnect from './components/Reconnect.svelte'
import TxMessage from './components/activity/TxMessage.svelte'
import IconTelegram from './components/icons/TelegramColor.svelte'
import TxSharedCreate from './components/activity/TxSharedCreate.svelte'
import NotificationMessageCreated from './components/notification/NotificationMessageCreated.svelte'
import TelegramMessageCreated from './components/activity/TelegramMessageCreated.svelte'
import telegram from './plugin'
import { getCurrentEmployeeTG, getIntegrationOwnerTG } from './utils'
@ -38,12 +38,10 @@ export default async (): Promise<Resources> => ({
IconTelegram,
SharedMessages
},
notification: {
NotificationMessageCreated
},
activity: {
TxSharedCreate,
TxMessage
TxMessage,
TelegramMessageCreated
},
function: {
GetCurrentEmployeeTG: getCurrentEmployeeTG,

View File

@ -71,9 +71,6 @@ export default plugin(telegramId, {
IconTelegram: '' as AnyComponent,
SharedMessages: '' as AnyComponent
},
notification: {
NotificationMessageCreated: '' as AnyComponent
},
integrationType: {
Telegram: '' as Ref<IntegrationType>
},

View File

@ -37,7 +37,13 @@ import core, {
type ReverseLookups,
type Space,
type TxOperations,
type Mixin
type Mixin,
type TxCUD,
type TxCollectionCUD,
TxProcessor,
type TxCreateDoc,
type TxUpdateDoc,
type TxMixin
} from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform'
import { getResource, translate } from '@hcengineering/platform'
@ -1075,3 +1081,61 @@ export function getCategoryQueryNoLookupOptions<T extends Doc> (options: FindOpt
const { lookup, ...resultOptions } = options
return resultOptions
}
export async function buildRemovedDoc<T extends Doc> (
client: Client,
objectId: Ref<T>,
_class: Ref<Class<T>>
): Promise<T | undefined> {
const isAttached = client.getHierarchy().isDerived(_class, core.class.AttachedDoc)
const txes = await client.findAll<TxCUD<Doc>>(
isAttached ? core.class.TxCollectionCUD : core.class.TxCUD,
isAttached
? { 'tx.objectId': objectId }
: {
objectId
},
{ sort: { modifiedOn: 1 } }
)
const createTx = isAttached
? txes.map((tx) => (tx as TxCollectionCUD<Doc, AttachedDoc>).tx).find((tx) => tx._class === core.class.TxCreateDoc)
: txes.find((tx) => tx._class === core.class.TxCreateDoc)
if (createTx === undefined) return
let doc = TxProcessor.createDoc2Doc(createTx as TxCreateDoc<Doc>)
for (let tx of txes) {
tx = TxProcessor.extractTx(tx) as TxCUD<Doc>
if (tx._class === core.class.TxUpdateDoc) {
doc = TxProcessor.updateDoc2Doc(doc, tx as TxUpdateDoc<Doc>)
} else if (tx._class === core.class.TxMixin) {
const mixinTx = tx as TxMixin<Doc, Doc>
doc = TxProcessor.updateMixin4Doc(doc, mixinTx)
}
}
return doc as T
}
export async function getOrBuildObject<T extends Doc> (
client: Client,
objectId: Ref<T>,
objectClass: Ref<Class<T>>
): Promise<T | undefined> {
const object = await client.findOne<Doc>(objectClass, { _id: objectId })
if (object !== undefined) {
return object as T
}
return await buildRemovedDoc(client, objectId, objectClass)
}
export async function checkIsObjectRemoved (
client: Client,
objectId: Ref<Doc>,
objectClass: Ref<Class<Doc>>
): Promise<boolean> {
const object = await client.findOne(objectClass, { _id: objectId })
return object === undefined
}