Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UBERF-7934 Update activity from collaborator #6372

Merged
merged 3 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion models/activity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class TDocUpdateMessage extends TActivityMessage implements DocUpdateMess

@Prop(TypeRef(core.class.TxCUD), core.string.Object)
// @Index(IndexKind.Indexed)
txId!: Ref<TxCUD<Doc>>
txId?: Ref<TxCUD<Doc>>

@Prop(TypeString(), core.string.Object)
// @Index(IndexKind.Indexed)
Expand Down
2 changes: 1 addition & 1 deletion plugins/activity/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface DocUpdateMessage extends ActivityMessage {
objectId: Ref<Doc>
objectClass: Ref<Class<Doc>>

txId: Ref<TxCUD<Doc>>
txId?: Ref<TxCUD<Doc>>

action: DocUpdateAction
updateCollection?: string
Expand Down
44 changes: 8 additions & 36 deletions server-plugins/activity-resources/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ import {
AttachedDoc,
type Attribute,
Class,
CollaborativeDoc,
collaborativeDocFromLastVersion,
Collection,
Doc,
Hierarchy,
Markup,
MeasureContext,
Mixin,
Ref,
Expand All @@ -18,18 +15,15 @@ import {
TxCUD,
TxMixin,
TxProcessor,
TxUpdateDoc,
WorkspaceId
TxUpdateDoc
} from '@hcengineering/core'
import core from '@hcengineering/core/src/component'
import { ActivityMessageControl, DocAttributeUpdates, DocUpdateAction } from '@hcengineering/activity'
import { ActivityControl, DocObjectCache, getAllObjectTransactions } from '@hcengineering/server-activity'
import { getDocCollaborators } from '@hcengineering/server-notification-resources'
import notification from '@hcengineering/notification'
import { StorageAdapter, TriggerControl } from '@hcengineering/server-core'
import { TriggerControl } from '@hcengineering/server-core'
import { translate } from '@hcengineering/platform'
import { loadCollaborativeDoc } from '@hcengineering/collaboration'
import { EmptyMarkup, yDocToMarkup } from '@hcengineering/text'

function getAvailableAttributesKeys (tx: TxCUD<Doc>, hierarchy: Hierarchy): string[] {
if (hierarchy.isDerived(tx._class, core.class.TxUpdateDoc)) {
Expand Down Expand Up @@ -301,11 +295,12 @@ export async function getTxAttributesUpdates (
continue
}

if (
hierarchy.isDerived(attrClass, core.class.TypeMarkup) ||
hierarchy.isDerived(attrClass, core.class.TypeCollaborativeDoc) ||
mixin === notification.mixin.Collaborators
) {
if (attrClass === core.class.TypeCollaborativeDoc) {
// collaborative documents activity is handled by collaborator
continue
}

if (hierarchy.isDerived(attrClass, core.class.TypeMarkup) || mixin === notification.mixin.Collaborators) {
if (docDiff === undefined) {
docDiff = await getDocDiff(control, updateObject._class, updateObject._id, originTx._id, mixin, objectCache)
}
Expand All @@ -331,16 +326,6 @@ export async function getTxAttributesUpdates (
}
}

// we don't want to show collaborative documents in activity
// instead we show their content as Markup
// TODO this should be generalized via activity extension
const attrType = mixin !== undefined ? hierarchy.findAttribute(mixin, key) : clazz
if (attrType?.type?._class === core.class.TypeCollaborativeDoc) {
attrClass = isMixin ? attrClass : core.class.TypeMarkup
attrValue = await getMarkup(ctx, control.storageAdapter, control.workspace, attrValue, key)
prevValue = await getMarkup(ctx, control.storageAdapter, control.workspace, prevValue, key)
}

let setAttr = []

if (Array.isArray(attrValue)) {
Expand Down Expand Up @@ -424,16 +409,3 @@ export function getCollectionAttribute (

return undefined
}

async function getMarkup (
ctx: MeasureContext,
storage: StorageAdapter,
workspace: WorkspaceId,
value: CollaborativeDoc,
field: string
): Promise<Markup> {
if (value === undefined) return EmptyMarkup
value = collaborativeDocFromLastVersion(value)
const ydoc = await loadCollaborativeDoc(storage, workspace, value, ctx)
return ydoc !== undefined ? yDocToMarkup(ydoc, field) : EmptyMarkup
}
1 change: 1 addition & 0 deletions server/collaborator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@types/jest": "^29.5.5"
},
"dependencies": {
"@hcengineering/activity": "^0.6.0",
"@hcengineering/analytics": "^0.6.0",
"@hcengineering/core": "^0.6.32",
"@hcengineering/account": "^0.6.0",
Expand Down
42 changes: 33 additions & 9 deletions server/collaborator/src/extensions/storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MeasureContext } from '@hcengineering/core'
import {
Document,
Extension,
afterLoadDocumentPayload,
afterUnloadDocumentPayload,
onChangePayload,
onConnectPayload,
Expand All @@ -28,15 +29,18 @@ import {
import { Doc as YDoc } from 'yjs'
import { Context, withContext } from '../context'
import { CollabStorageAdapter } from '../storage/adapter'
import { TransformerFactory } from '../types'

export interface StorageConfiguration {
ctx: MeasureContext
adapter: CollabStorageAdapter
transformerFactory: TransformerFactory
}

export class StorageExtension implements Extension {
private readonly configuration: StorageConfiguration
private readonly collaborators = new Map<string, Set<string>>()
private readonly markups = new Map<string, Record<string, string>>()

constructor (configuration: StorageConfiguration) {
this.configuration = configuration
Expand All @@ -52,7 +56,15 @@ export class StorageExtension implements Extension {
const { connectionId } = context

this.configuration.ctx.info('load document', { documentName, connectionId })
return await this.loadDocument(documentName as DocumentId, context)
return await this.loadDocument(documentName, context)
}

async afterLoadDocument ({ context, documentName, document }: withContext<afterLoadDocumentPayload>): Promise<any> {
const { workspaceId } = context

// remember the markup for the document
const transformer = this.configuration.transformerFactory(workspaceId)
this.markups.set(documentName, transformer.fromYdoc(document))
}

async onStoreDocument ({ context, documentName, document }: withContext<onStoreDocumentPayload>): Promise<void> {
Expand All @@ -68,7 +80,7 @@ export class StorageExtension implements Extension {
}

this.collaborators.delete(documentName)
await this.storeDocument(documentName as DocumentId, document, context)
await this.storeDocument(documentName, document, context)
}

async onConnect ({ context, documentName, instance }: withContext<onConnectPayload>): Promise<any> {
Expand All @@ -91,36 +103,48 @@ export class StorageExtension implements Extension {
}

this.collaborators.delete(documentName)
await this.storeDocument(documentName as DocumentId, document, context)
await this.storeDocument(documentName, document, context)
}

async afterUnloadDocument ({ documentName }: afterUnloadDocumentPayload): Promise<any> {
this.configuration.ctx.info('unload document', { documentName })
this.collaborators.delete(documentName)
this.markups.delete(documentName)
}

async loadDocument (documentId: DocumentId, context: Context): Promise<YDoc | undefined> {
private async loadDocument (documentName: string, context: Context): Promise<YDoc | undefined> {
const { ctx, adapter } = this.configuration

try {
return await ctx.with('load-document', {}, async (ctx) => {
return await adapter.loadDocument(ctx, documentId, context)
return await adapter.loadDocument(ctx, documentName as DocumentId, context)
})
} catch (err) {
ctx.error('failed to load document', { documentId, error: err })
ctx.error('failed to load document', { documentName, error: err })
throw new Error('Failed to load document')
}
}

async storeDocument (documentId: DocumentId, document: Document, context: Context): Promise<void> {
private async storeDocument (documentName: string, document: Document, context: Context): Promise<void> {
const { ctx, adapter } = this.configuration
const { workspaceId } = context

try {
const transformer = this.configuration.transformerFactory(workspaceId)

const prevMarkup = this.markups.get(documentName) ?? {}
const currMarkup = transformer.fromYdoc(document)

await ctx.with('save-document', {}, async (ctx) => {
await adapter.saveDocument(ctx, documentId, document, context)
await adapter.saveDocument(ctx, documentName as DocumentId, document, context, {
prev: prevMarkup,
curr: currMarkup
})
})

this.markups.set(documentName, currMarkup)
} catch (err) {
ctx.error('failed to save document', { documentId, error: err })
ctx.error('failed to save document', { documentName, error: err })
throw new Error('Failed to save document')
}
}
Expand Down
3 changes: 2 additions & 1 deletion server/collaborator/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ export async function start (ctx: MeasureContext, config: Config, storageAdapter
}),
new StorageExtension({
ctx: extensionsCtx.newChild('storage', {}),
adapter: new PlatformStorageAdapter(storageAdapter)
adapter: new PlatformStorageAdapter(storageAdapter),
transformerFactory
})
]
})
Expand Down
11 changes: 10 additions & 1 deletion server/collaborator/src/storage/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,14 @@ import { Context } from '../context'

export interface CollabStorageAdapter {
loadDocument: (ctx: MeasureContext, documentId: DocumentId, context: Context) => Promise<YDoc | undefined>
saveDocument: (ctx: MeasureContext, documentId: DocumentId, document: YDoc, context: Context) => Promise<void>
saveDocument: (
ctx: MeasureContext,
documentId: DocumentId,
document: YDoc,
context: Context,
markup: {
prev: Record<string, string>
curr: Record<string, string>
}
) => Promise<void>
}
70 changes: 55 additions & 15 deletions server/collaborator/src/storage/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.
//

import activity, { DocUpdateMessage } from '@hcengineering/activity'
import {
YDocVersion,
loadCollaborativeDoc,
Expand All @@ -26,13 +27,15 @@ import {
parsePlatformDocumentId
} from '@hcengineering/collaborator-client'
import core, {
AttachedData,
CollaborativeDoc,
MeasureContext,
TxOperations,
collaborativeDocWithLastVersion
} from '@hcengineering/core'
import { StorageAdapter } from '@hcengineering/server-core'
import { Doc as YDoc } from 'yjs'

import { Context } from '../context'

import { CollabStorageAdapter } from './adapter'
Expand Down Expand Up @@ -78,7 +81,16 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
return undefined
}

async saveDocument (ctx: MeasureContext, documentId: DocumentId, document: YDoc, context: Context): Promise<void> {
async saveDocument (
ctx: MeasureContext,
documentId: DocumentId,
document: YDoc,
context: Context,
markup: {
prev: Record<string, string>
curr: Record<string, string>
}
): Promise<void> {
const { clientFactory } = context

const client = await ctx.with('connect', {}, async () => {
Expand Down Expand Up @@ -108,7 +120,7 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
if (platformDocumentId !== undefined) {
ctx.info('save document content to platform', { documentId, platformDocumentId })
await ctx.with('save-to-platform', {}, async (ctx) => {
await this.saveDocumentToPlatform(ctx, client, documentId, platformDocumentId, snapshot)
await this.saveDocumentToPlatform(ctx, client, documentId, platformDocumentId, snapshot, markup)
})
}
} finally {
Expand Down Expand Up @@ -177,7 +189,11 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
client: Omit<TxOperations, 'close'>,
documentName: string,
platformDocumentId: PlatformDocumentId,
snapshot: YDocVersion | undefined
snapshot: YDocVersion | undefined,
markup: {
prev: Record<string, string>
curr: Record<string, string>
}
): Promise<void> {
const { objectClass, objectId, objectAttr } = parsePlatformDocumentId(platformDocumentId)

Expand All @@ -197,19 +213,43 @@ export class PlatformStorageAdapter implements CollabStorageAdapter {
}

const hierarchy = client.getHierarchy()
if (hierarchy.isDerived(attribute.type._class, core.class.TypeCollaborativeDoc)) {
const collaborativeDoc = (current as any)[objectAttr] as CollaborativeDoc
const newCollaborativeDoc =
snapshot !== undefined
? collaborativeDocWithLastVersion(collaborativeDoc, snapshot.versionId)
: collaborativeDoc

await ctx.with('update', {}, async () => {
await client.diffUpdate(current, { [objectAttr]: newCollaborativeDoc })
})
} else {
ctx.error('unsupported attribute type', { documentName, objectClass, objectAttr })
if (!hierarchy.isDerived(attribute.type._class, core.class.TypeCollaborativeDoc)) {
ctx.warn('unsupported attribute type', { documentName, objectClass, objectAttr })
return
}

const collaborativeDoc = (current as any)[objectAttr] as CollaborativeDoc
const newCollaborativeDoc =
snapshot !== undefined ? collaborativeDocWithLastVersion(collaborativeDoc, snapshot.versionId) : collaborativeDoc

await ctx.with('update', {}, async () => {
await client.diffUpdate(current, { [objectAttr]: newCollaborativeDoc })
})

await ctx.with('activity', {}, async () => {
const data: AttachedData<DocUpdateMessage> = {
objectId,
objectClass,
action: 'update',
attributeUpdates: {
attrKey: objectAttr,
attrClass: core.class.TypeMarkup,
prevValue: markup.prev[objectAttr],
set: [markup.curr[objectAttr]],
added: [],
removed: [],
isMixin: hierarchy.isMixin(objectClass)
}
}
await client.addCollection(
activity.class.DocUpdateMessage,
current.space,
current._id,
current._class,
'docUpdateMessages',
data
)
})
}
}

Expand Down
15 changes: 14 additions & 1 deletion server/collaborator/src/transformers/markup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,20 @@ export class MarkupTransformer implements Transformer {

fromYdoc (document: Doc, fieldName?: string | string[] | undefined): any {
const json = this.transformer.fromYdoc(document, fieldName)
return jsonToMarkup(json)
if (typeof fieldName === 'string') {
return jsonToMarkup(json)
}

if (fieldName === undefined || fieldName.length === 0) {
fieldName = Array.from(document.share.keys())
}

const data: Record<string, string> = {}
fieldName?.forEach((field) => {
data[field] = jsonToMarkup(json[field])
})

return data
}

toYdoc (document: any, fieldName: string): Doc {
Expand Down