From e11a0a87cbf74735ab1c30ef3006c28e8d38444c Mon Sep 17 00:00:00 2001 From: Andrey Sobolev <haiodo@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:17:37 +0700 Subject: [PATCH] UBERF-8540: Allow derived operations with apply (#7044) --- models/notification/src/migration.ts | 12 +++++++ packages/core/src/operations.ts | 4 +-- packages/core/src/server.ts | 2 ++ .../src/components/ChannelScrollView.svelte | 2 +- .../ReverseChannelScrollView.svelte | 2 +- .../src/components/chat/utils.ts | 2 +- .../src/inboxNotificationsClient.ts | 6 ++-- plugins/notification-resources/src/utils.ts | 4 +-- .../components/WorkbenchTabPresenter.svelte | 16 +++++---- server/core/src/mem.ts | 2 ++ server/core/src/plugin.ts | 3 +- server/elastic/src/backup.ts | 4 +++ server/middleware/src/lowLevel.ts | 35 +++++++++---------- server/mongo/src/storage.ts | 4 +++ server/postgres/src/storage.ts | 7 ++++ server/server-storage/src/blobStorage.ts | 2 ++ server/tool/src/upgrade.ts | 8 +---- 17 files changed, 71 insertions(+), 44 deletions(-) diff --git a/models/notification/src/migration.ts b/models/notification/src/migration.ts index 4c67119729..19405eaf68 100644 --- a/models/notification/src/migration.ts +++ b/models/notification/src/migration.ts @@ -399,6 +399,18 @@ export const notificationOperation: MigrateOperation = { func: async (client: MigrationClient): Promise<void> => { await client.update(DOMAIN_DOC_NOTIFY, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } }) } + }, + { + state: 'remove-update-txes-docnotify-ctx', + func: async (client) => { + await client.deleteMany(DOMAIN_TX, { + _class: core.class.TxUpdateDoc, + objectClass: notification.class.DocNotifyContext, + 'operations.lastViewedTimestamp': { + $exists: true + } + }) + } } ]) diff --git a/packages/core/src/operations.ts b/packages/core/src/operations.ts index 8d55f8ec82..c8d40c1c62 100644 --- a/packages/core/src/operations.ts +++ b/packages/core/src/operations.ts @@ -313,8 +313,8 @@ export class TxOperations implements Omit<Client, 'notify'> { return this.removeDoc(doc._class, doc.space, doc._id) } - apply (scope?: string, measure?: string): ApplyOperations { - return new ApplyOperations(this, scope, measure, this.isDerived) + apply (scope?: string, measure?: string, derived?: boolean): ApplyOperations { + return new ApplyOperations(this, scope, measure, derived ?? this.isDerived) } async diffUpdate<T extends Doc = Doc>( diff --git a/packages/core/src/server.ts b/packages/core/src/server.ts index 8ae4596a6e..5a16bfa67d 100644 --- a/packages/core/src/server.ts +++ b/packages/core/src/server.ts @@ -85,6 +85,8 @@ export interface LowLevelStorage { rawUpdate: <T extends Doc>(domain: Domain, query: DocumentQuery<T>, operations: DocumentUpdate<T>) => Promise<void> + rawDeleteMany: <T extends Doc>(domain: Domain, query: DocumentQuery<T>) => Promise<void> + // Traverse documents traverse: <T extends Doc>( domain: Domain, diff --git a/plugins/chunter-resources/src/components/ChannelScrollView.svelte b/plugins/chunter-resources/src/components/ChannelScrollView.svelte index a17e633496..d606e645e4 100644 --- a/plugins/chunter-resources/src/components/ChannelScrollView.svelte +++ b/plugins/chunter-resources/src/components/ChannelScrollView.svelte @@ -760,7 +760,7 @@ if (unViewed.length === 0) { forceRead = true - const op = client.apply(undefined, 'chunter.forceReadContext') + const op = client.apply(undefined, 'chunter.forceReadContext', true) await inboxClient.readDoc(op, object._id) await op.commit() } diff --git a/plugins/chunter-resources/src/components/ReverseChannelScrollView.svelte b/plugins/chunter-resources/src/components/ReverseChannelScrollView.svelte index 9f8353db0b..027478ef15 100644 --- a/plugins/chunter-resources/src/components/ReverseChannelScrollView.svelte +++ b/plugins/chunter-resources/src/components/ReverseChannelScrollView.svelte @@ -419,7 +419,7 @@ if (unViewed.length === 0) { forceRead = true - const op = client.apply(undefined, 'chunter.forceReadContext') + const op = client.apply(undefined, 'chunter.forceReadContext', true) await inboxClient.readDoc(op, object._id) await op.commit() } diff --git a/plugins/chunter-resources/src/components/chat/utils.ts b/plugins/chunter-resources/src/components/chat/utils.ts index 377cd196d8..203b576a06 100644 --- a/plugins/chunter-resources/src/components/chat/utils.ts +++ b/plugins/chunter-resources/src/components/chat/utils.ts @@ -400,7 +400,7 @@ export async function hideActivityChannels (contexts: DocNotifyContext[]): Promi export async function readActivityChannels (contexts: DocNotifyContext[]): Promise<void> { const client = InboxNotificationsClientImpl.getClient() const notificationsByContext = get(client.inboxNotificationsByContext) - const ops = getClient().apply(undefined, 'readActivityChannels') + const ops = getClient().apply(undefined, 'readActivityChannels', true) try { for (const context of contexts) { diff --git a/plugins/notification-resources/src/inboxNotificationsClient.ts b/plugins/notification-resources/src/inboxNotificationsClient.ts index 19369b51d6..8d9585bfc7 100644 --- a/plugins/notification-resources/src/inboxNotificationsClient.ts +++ b/plugins/notification-resources/src/inboxNotificationsClient.ts @@ -235,7 +235,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient { } async archiveAllNotifications (): Promise<void> { - const ops = getClient().apply(undefined, 'archiveAllNotifications') + const ops = getClient().apply(undefined, 'archiveAllNotifications', true) try { const inboxNotifications = await ops.findAll( @@ -260,7 +260,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient { } async readAllNotifications (): Promise<void> { - const ops = getClient().apply(undefined, 'readAllNotifications') + const ops = getClient().apply(undefined, 'readAllNotifications', true) try { const inboxNotifications = await ops.findAll( @@ -285,7 +285,7 @@ export class InboxNotificationsClientImpl implements InboxNotificationsClient { } async unreadAllNotifications (): Promise<void> { - const ops = getClient().apply(undefined, 'unreadAllNotifications') + const ops = getClient().apply(undefined, 'unreadAllNotifications', true) try { const inboxNotifications = await ops.findAll( diff --git a/plugins/notification-resources/src/utils.ts b/plugins/notification-resources/src/utils.ts index f5977d1d8d..cf07387a79 100644 --- a/plugins/notification-resources/src/utils.ts +++ b/plugins/notification-resources/src/utils.ts @@ -128,7 +128,7 @@ export async function readNotifyContext (doc: DocNotifyContext): Promise<void> { const inboxClient = InboxNotificationsClientImpl.getClient() const inboxNotifications = get(inboxClient.inboxNotificationsByContext).get(doc._id) ?? [] - const ops = getClient().apply(undefined, 'readNotifyContext') + const ops = getClient().apply(undefined, 'readNotifyContext', true) try { await inboxClient.readNotifications( ops, @@ -152,7 +152,7 @@ export async function unReadNotifyContext (doc: DocNotifyContext): Promise<void> return } - const ops = getClient().apply(undefined, 'unReadNotifyContext') + const ops = getClient().apply(undefined, 'unReadNotifyContext', true) try { await inboxClient.unreadNotifications( diff --git a/plugins/workbench-resources/src/components/WorkbenchTabPresenter.svelte b/plugins/workbench-resources/src/components/WorkbenchTabPresenter.svelte index 12dcd0f5f0..48d6df9bdf 100644 --- a/plugins/workbench-resources/src/components/WorkbenchTabPresenter.svelte +++ b/plugins/workbench-resources/src/components/WorkbenchTabPresenter.svelte @@ -13,24 +13,24 @@ // limitations under the License. --> <script lang="ts"> + import { Asset, getResource, translate } from '@hcengineering/platform' + import { ComponentExtensions, getClient, reduceCalls } from '@hcengineering/presentation' import { AnySvelteComponent, closePanel, getCurrentLocation, Icon, - ModernTab, - navigate, languageStore, - locationToUrl + locationToUrl, + ModernTab, + navigate } from '@hcengineering/ui' - import { ComponentExtensions, getClient, reduceCalls } from '@hcengineering/presentation' - import { Asset, getResource, translate } from '@hcengineering/platform' - import { WorkbenchTab } from '@hcengineering/workbench' import view from '@hcengineering/view' import { showMenu } from '@hcengineering/view-resources' + import { WorkbenchTab } from '@hcengineering/workbench' - import { closeTab, getTabDataByLocation, getTabLocation, selectTab, tabIdStore, tabsStore } from '../workbench' import workbench from '../plugin' + import { closeTab, getTabDataByLocation, getTabLocation, selectTab, tabIdStore, tabsStore } from '../workbench' export let tab: WorkbenchTab @@ -63,7 +63,9 @@ iconProps = data.iconProps if (tab.name !== name && tab.location === locationToUrl(tabLoc)) { + const op = client.apply(undefined, undefined, true) await client.diffUpdate(tab, { name }) + await op.commit() } } diff --git a/server/core/src/mem.ts b/server/core/src/mem.ts index 5f113d8e42..14c33d8ded 100644 --- a/server/core/src/mem.ts +++ b/server/core/src/mem.ts @@ -116,6 +116,8 @@ export class DummyDbAdapter implements DbAdapter { query: DocumentQuery<T>, operations: DocumentUpdate<T> ): Promise<void> {} + + async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> {} } class InMemoryAdapter extends DummyDbAdapter implements DbAdapter { diff --git a/server/core/src/plugin.ts b/server/core/src/plugin.ts index afd452091d..31836fef0f 100644 --- a/server/core/src/plugin.ts +++ b/server/core/src/plugin.ts @@ -36,8 +36,7 @@ const serverCore = plugin(serverCoreId, { SearchPresenter: '' as Ref<Mixin<SearchPresenter>> }, space: { - DocIndexState: '' as Ref<Space>, - TriggerState: '' as Ref<Space> + DocIndexState: '' as Ref<Space> }, metadata: { FrontUrl: '' as Metadata<string>, diff --git a/server/elastic/src/backup.ts b/server/elastic/src/backup.ts index 8ff9c2959e..72093cc57a 100644 --- a/server/elastic/src/backup.ts +++ b/server/elastic/src/backup.ts @@ -142,6 +142,10 @@ class ElasticDataAdapter implements DbAdapter { throw new Error('Method not implemented.') } + async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> { + throw new Error('Method not implemented') + } + async clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> { const indexExists = await this.client.indices.exists({ index: this.indexName diff --git a/server/middleware/src/lowLevel.ts b/server/middleware/src/lowLevel.ts index a049ca089d..608aa11aeb 100644 --- a/server/middleware/src/lowLevel.ts +++ b/server/middleware/src/lowLevel.ts @@ -19,10 +19,10 @@ import { FindOptions, type Doc, type Domain, + type Iterator, type MeasureContext, type Ref, - type StorageIterator, - type Iterator + type StorageIterator } from '@hcengineering/core' import { PlatformError, unknownStatus } from '@hcengineering/platform' import type { Middleware, PipelineContext } from '@hcengineering/server-core' @@ -47,36 +47,35 @@ export class LowLevelMiddleware extends BaseMiddleware implements Middleware { return adapterManager.getAdapter(domain, false).find(ctx, domain, recheck) }, - async load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> { - return await adapterManager.getAdapter(domain, false).load(ctx, domain, docs) + load (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<Doc[]> { + return adapterManager.getAdapter(domain, false).load(ctx, domain, docs) }, - async upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> { - await adapterManager.getAdapter(domain, true).upload(ctx, domain, docs) + upload (ctx: MeasureContext, domain: Domain, docs: Doc[]): Promise<void> { + return adapterManager.getAdapter(domain, true).upload(ctx, domain, docs) }, async clean (ctx: MeasureContext, domain: Domain, docs: Ref<Doc>[]): Promise<void> { await adapterManager.getAdapter(domain, true).clean(ctx, domain, docs) }, - async groupBy<T>(ctx: MeasureContext, domain: Domain, field: string): Promise<Set<T>> { - return await adapterManager.getAdapter(domain, false).groupBy(ctx, domain, field) + groupBy<T>(ctx: MeasureContext, domain: Domain, field: string): Promise<Set<T>> { + return adapterManager.getAdapter(domain, false).groupBy(ctx, domain, field) }, - async rawFindAll<T extends Doc>(domain: Domain, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<T[]> { - return await adapterManager.getAdapter(domain, false).rawFindAll(domain, query, options) + rawFindAll<T extends Doc>(domain: Domain, query: DocumentQuery<T>, options?: FindOptions<T>): Promise<T[]> { + return adapterManager.getAdapter(domain, false).rawFindAll(domain, query, options) }, - async rawUpdate<T extends Doc>( - domain: Domain, - query: DocumentQuery<T>, - operations: DocumentUpdate<T> - ): Promise<void> { - await adapterManager.getAdapter(domain, true).rawUpdate(domain, query, operations) + rawUpdate<T extends Doc>(domain: Domain, query: DocumentQuery<T>, operations: DocumentUpdate<T>): Promise<void> { + return adapterManager.getAdapter(domain, true).rawUpdate(domain, query, operations) }, - async traverse<T extends Doc>( + rawDeleteMany (domain, query) { + return adapterManager.getAdapter(domain, true).rawDeleteMany(domain, query) + }, + traverse<T extends Doc>( domain: Domain, query: DocumentQuery<T>, options?: Pick<FindOptions<T>, 'sort' | 'limit' | 'projection'> ): Promise<Iterator<T>> { - return await adapterManager.getAdapter(domain, false).traverse(domain, query, options) + return adapterManager.getAdapter(domain, false).traverse(domain, query, options) } } return undefined diff --git a/server/mongo/src/storage.ts b/server/mongo/src/storage.ts index 2d7d28aff4..26470c664c 100644 --- a/server/mongo/src/storage.ts +++ b/server/mongo/src/storage.ts @@ -262,6 +262,10 @@ abstract class MongoAdapterBase implements DbAdapter { } } + async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> { + await this.db.collection(domain).deleteMany(this.translateRawQuery(query)) + } + abstract init (): Promise<void> collection<TSchema extends Document = Document>(domain: Domain): Collection<TSchema> { diff --git a/server/postgres/src/storage.ts b/server/postgres/src/storage.ts index 8a44e4cf50..00125c4b15 100644 --- a/server/postgres/src/storage.ts +++ b/server/postgres/src/storage.ts @@ -327,6 +327,13 @@ abstract class PostgresAdapterBase implements DbAdapter { }) } + async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> { + const translatedQuery = this.buildRawQuery(domain, query) + await this.retryTxn(this.client, async (client) => { + await client.query(`DELETE FROM ${translateDomain(domain)} WHERE ${translatedQuery}`) + }) + } + async findAll<T extends Doc>( ctx: MeasureContext<SessionData>, _class: Ref<Class<T>>, diff --git a/server/server-storage/src/blobStorage.ts b/server/server-storage/src/blobStorage.ts index a90ce52abc..209e005a7e 100644 --- a/server/server-storage/src/blobStorage.ts +++ b/server/server-storage/src/blobStorage.ts @@ -70,6 +70,8 @@ class StorageBlobAdapter implements DbAdapter { operations: DocumentUpdate<T> ): Promise<void> {} + async rawDeleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> {} + async findAll<T extends Doc>( ctx: MeasureContext, _class: Ref<Class<T>>, diff --git a/server/tool/src/upgrade.ts b/server/tool/src/upgrade.ts index a6a8b9780b..0a178650c4 100644 --- a/server/tool/src/upgrade.ts +++ b/server/tool/src/upgrade.ts @@ -96,12 +96,6 @@ export class MigrateClientImpl implements MigrationClient { } async deleteMany<T extends Doc>(domain: Domain, query: DocumentQuery<T>): Promise<void> { - const ctx = new MeasureMetricsContext('deleteMany', {}) - const docs = await this.lowLevel.rawFindAll(domain, query) - await this.lowLevel.clean( - ctx, - domain, - docs.map((d) => d._id) - ) + await this.lowLevel.rawDeleteMany(domain, query) } }