From a596f468ea9ef9f1159bc895a89c1538db3ec67d Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Thu, 13 Jun 2024 17:43:30 +0700
Subject: [PATCH] UBERF-7247: Fix queryFind for mixins on server (#5803)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 packages/query/src/__tests__/minmodel.ts   | 75 +++++++++++++++++++---
 packages/query/src/__tests__/query.test.ts | 27 ++++++++
 server/core/src/server/storage.ts          | 12 ++--
 3 files changed, 100 insertions(+), 14 deletions(-)

diff --git a/packages/query/src/__tests__/minmodel.ts b/packages/query/src/__tests__/minmodel.ts
index 8e7322b60e..b03547ebd0 100644
--- a/packages/query/src/__tests__/minmodel.ts
+++ b/packages/query/src/__tests__/minmodel.ts
@@ -13,7 +13,20 @@
 // limitations under the License.
 //
 
-import type { Account, Arr, Class, Data, Doc, Domain, Mixin, Obj, Ref, TxCreateDoc, TxCUD } from '@hcengineering/core'
+import type {
+  Account,
+  Arr,
+  Class,
+  Data,
+  Doc,
+  Domain,
+  Mixin,
+  Obj,
+  Ref,
+  Space,
+  TxCreateDoc,
+  TxCUD
+} from '@hcengineering/core'
 import core, { AccountRole, AttachedDoc, ClassifierKind, DOMAIN_MODEL, DOMAIN_TX, TxFactory } from '@hcengineering/core'
 import type { IntlString, Plugin } from '@hcengineering/platform'
 import { plugin } from '@hcengineering/platform'
@@ -54,16 +67,26 @@ export interface AttachedComment extends AttachedDoc {
   message: string
 }
 
+interface TestProject extends Space {
+  prjName: string
+}
+
+interface TestProjectMixin extends TestProject {
+  someField?: string
+}
+
 /**
  * @public
  */
 export const test = plugin('test' as Plugin, {
   mixin: {
-    TestMixin: '' as Ref<Mixin<TestMixin>>
+    TestMixin: '' as Ref<Mixin<TestMixin>>,
+    TestProjectMixin: '' as Ref<Mixin<TestProjectMixin>>
   },
   class: {
     TestComment: '' as Ref<Class<AttachedComment>>,
-    ParticipantsHolder: '' as Ref<Class<ParticipantsHolder>>
+    ParticipantsHolder: '' as Ref<Class<ParticipantsHolder>>,
+    TestProject: '' as Ref<Class<TestProject>>
   }
 })
 
@@ -88,13 +111,6 @@ export function genMinModel (): TxCUD<Doc>[] {
   txes.push(
     createClass(core.class.Doc, { label: 'Doc' as IntlString, extends: core.class.Obj, kind: ClassifierKind.CLASS })
   )
-  txes.push(
-    createClass(core.class.AttachedDoc, {
-      label: 'AttachedDoc' as IntlString,
-      extends: core.class.Doc,
-      kind: ClassifierKind.MIXIN
-    })
-  )
   txes.push(
     createClass(core.class.Class, {
       label: 'Class' as IntlString,
@@ -103,6 +119,21 @@ export function genMinModel (): TxCUD<Doc>[] {
       domain: DOMAIN_MODEL
     })
   )
+  txes.push(
+    createClass(core.class.Mixin, {
+      label: 'Mixin' as IntlString,
+      extends: core.class.Class,
+      kind: ClassifierKind.CLASS,
+      domain: DOMAIN_MODEL
+    })
+  )
+  txes.push(
+    createClass(core.class.AttachedDoc, {
+      label: 'AttachedDoc' as IntlString,
+      extends: core.class.Doc,
+      kind: ClassifierKind.MIXIN
+    })
+  )
   txes.push(
     createClass(core.class.Space, {
       label: 'Space' as IntlString,
@@ -164,6 +195,13 @@ export function genMinModel (): TxCUD<Doc>[] {
       kind: ClassifierKind.CLASS
     })
   )
+  txes.push(
+    createClass(core.class.TxMixin, {
+      label: 'TxMixin' as IntlString,
+      extends: core.class.TxCUD,
+      kind: ClassifierKind.CLASS
+    })
+  )
 
   txes.push(
     createClass(test.mixin.TestMixin, {
@@ -173,6 +211,23 @@ export function genMinModel (): TxCUD<Doc>[] {
     })
   )
 
+  txes.push(
+    createClass(test.class.TestProject, {
+      label: 'TestProject' as IntlString,
+      extends: core.class.Space,
+      kind: ClassifierKind.CLASS,
+      domain: DOMAIN_TEST
+    })
+  )
+
+  txes.push(
+    createClass(test.mixin.TestProjectMixin, {
+      label: 'TestProjectMixin' as IntlString,
+      extends: test.class.TestProject,
+      kind: ClassifierKind.MIXIN
+    })
+  )
+
   txes.push(
     createClass(test.class.TestComment, {
       label: 'TestComment' as IntlString,
diff --git a/packages/query/src/__tests__/query.test.ts b/packages/query/src/__tests__/query.test.ts
index 7d6bf134c3..9a1a288f10 100644
--- a/packages/query/src/__tests__/query.test.ts
+++ b/packages/query/src/__tests__/query.test.ts
@@ -942,4 +942,31 @@ describe('query', () => {
     const result = await resolveP
     expect(result.length).toEqual(2)
   })
+
+  it('check query mixin projection', async () => {
+    const { liveQuery, factory } = await getClient()
+
+    let projects = await liveQuery.queryFind(test.mixin.TestProjectMixin, {}, { projection: { _id: 1 } })
+    expect(projects.length).toEqual(0)
+    const project = await factory.createDoc(test.class.TestProject, core.space.Space, {
+      archived: false,
+      description: '',
+      members: [],
+      private: false,
+      prjName: 'test project',
+      name: 'qwe'
+    })
+
+    projects = await liveQuery.queryFind(test.mixin.TestProjectMixin, {}, { projection: { _id: 1 } })
+    expect(projects.length).toEqual(0)
+    await factory.createMixin(project, test.class.TestProject, core.space.Space, test.mixin.TestProjectMixin, {
+      someField: 'qwe'
+    })
+    // We need to process all events before we could do query again
+    await new Promise<void>((resolve) => {
+      setTimeout(resolve, 100)
+    })
+    projects = await liveQuery.queryFind(test.mixin.TestProjectMixin, {}, { projection: { _id: 1 } })
+    expect(projects.length).toEqual(1)
+  })
 })
diff --git a/server/core/src/server/storage.ts b/server/core/src/server/storage.ts
index d4e32898f7..df9e42c633 100644
--- a/server/core/src/server/storage.ts
+++ b/server/core/src/server/storage.ts
@@ -146,7 +146,13 @@ export class TServerStorage implements ServerStorage {
       findOne: async (_class, query, options) => {
         return (
           await metrics.with('query', {}, async (ctx) => {
-            return await this.findAll(ctx, _class, query, { ...options, limit: 1 })
+            const results = await this.findAll(ctx, _class, query, { ...options, limit: 1 })
+            return toFindResult(
+              results.map((v) => {
+                return this.hierarchy.updateLookupMixin(_class, v, options)
+              }),
+              results.total
+            )
           })
         )[0]
       },
@@ -232,9 +238,7 @@ export class TServerStorage implements ServerStorage {
         const r = await ctx.with('adapter-tx', { domain: lastDomain }, async (ctx) => await adapter.tx(ctx, ...part))
 
         // Update server live queries.
-        for (const t of part) {
-          await this.liveQuery.tx(t)
-        }
+        await this.liveQuery.tx(...part)
         if (Array.isArray(r)) {
           result.push(...r)
         } else {