From 26d8099f1d284821335f1e99a3b11abeb9158a70 Mon Sep 17 00:00:00 2001
From: Andrey Sobolev <haiodo@users.noreply.github.com>
Date: Thu, 10 Aug 2023 22:12:48 +0700
Subject: [PATCH] UBER-720: Rework list view to multiple requests (#3578)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
---
 models/tracker/src/index.ts                   |  8 +---
 .../src/components/PersonRefPresenter.svelte  |  1 +
 .../src/components/issues/Issues.svelte       | 17 ++++++-
 .../src/components/myissues/MyIssues.svelte   | 19 +++++++-
 .../src/components/ViewletSelector.svelte     |  4 +-
 .../src/components/filter/ValueFilter.svelte  |  8 +++-
 .../src/components/list/List.svelte           | 28 +++++++++++-
 .../src/components/list/ListCategories.svelte | 21 ++++++++-
 .../src/components/list/ListCategory.svelte   | 45 ++++++++++++++++---
 .../src/components/list/ListHeader.svelte     |  7 +--
 10 files changed, 131 insertions(+), 27 deletions(-)

diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts
index 0c951dfd51..5d88c0c832 100644
--- a/models/tracker/src/index.ts
+++ b/models/tracker/src/index.ts
@@ -541,12 +541,7 @@ export function createModel (builder: Builder): void {
           displayProps: { key: 'assignee', fixed: 'right' },
           props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
         }
-      ],
-      options: {
-        lookup: {
-          space: tracker.class.Project
-        }
-      }
+      ]
     },
     tracker.viewlet.IssueList
   )
@@ -1052,7 +1047,6 @@ export function createModel (builder: Builder): void {
             icon: tracker.icon.Issues,
             component: tracker.component.Issues,
             componentProps: {
-              baseQuery: { '$lookup.space.archived': false },
               space: undefined,
               title: tracker.string.AllIssues,
               config: [
diff --git a/plugins/contact-resources/src/components/PersonRefPresenter.svelte b/plugins/contact-resources/src/components/PersonRefPresenter.svelte
index 82a43daad6..5f05b77d05 100644
--- a/plugins/contact-resources/src/components/PersonRefPresenter.svelte
+++ b/plugins/contact-resources/src/components/PersonRefPresenter.svelte
@@ -59,4 +59,5 @@
   {disabled}
   {inline}
   {accent}
+  on:accent-color
 />
diff --git a/plugins/tracker-resources/src/components/issues/Issues.svelte b/plugins/tracker-resources/src/components/issues/Issues.svelte
index 471d05fe80..2f900fc11a 100644
--- a/plugins/tracker-resources/src/components/issues/Issues.svelte
+++ b/plugins/tracker-resources/src/components/issues/Issues.svelte
@@ -60,13 +60,28 @@
     }
   )
 
+  const archivedProjectQuery = createQuery()
+  let archived: Ref<Project>[] = []
+
+  archivedProjectQuery.query(
+    tracker.class.Project,
+    { archived: true },
+    (res) => {
+      archived = res.map((it) => it._id)
+    },
+    { projection: { _id: 1 } }
+  )
+
   $: queries = { all, active, backlog }
   $: mode = $resolvedLocationStore.query?.mode ?? undefined
   $: if (mode === undefined || queries[mode] === undefined) {
     ;[[mode]] = config
   }
   $: if (mode !== undefined) {
-    query = { ...queries[mode], '$lookup.space.archived': false }
+    query = { ...queries[mode] }
+    if (query?.space === undefined) {
+      query = { ...query, space: { $nin: archived } }
+    }
     modeSelectorProps = {
       config,
       mode,
diff --git a/plugins/tracker-resources/src/components/myissues/MyIssues.svelte b/plugins/tracker-resources/src/components/myissues/MyIssues.svelte
index 229e01cf04..fedfcf88f6 100644
--- a/plugins/tracker-resources/src/components/myissues/MyIssues.svelte
+++ b/plugins/tracker-resources/src/components/myissues/MyIssues.svelte
@@ -18,7 +18,7 @@
   import { Doc, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
   import type { IntlString } from '@hcengineering/platform'
   import { createQuery } from '@hcengineering/presentation'
-  import type { Issue } from '@hcengineering/tracker'
+  import type { Issue, Project } from '@hcengineering/tracker'
   import { resolvedLocationStore } from '@hcengineering/ui'
 
   import { IModeSelector } from '@hcengineering/ui'
@@ -50,13 +50,28 @@
     { sort: { _id: 1 } }
   )
 
+  const archivedProjectQuery = createQuery()
+  let archived: Ref<Project>[] = []
+
+  archivedProjectQuery.query(
+    tracker.class.Project,
+    { archived: true },
+    (res) => {
+      archived = res.map((it) => it._id)
+    },
+    { projection: { _id: 1 } }
+  )
+
   $: queries = { assigned, created, subscribed }
   $: mode = $resolvedLocationStore.query?.mode ?? undefined
   $: if (mode === undefined || queries[mode] === undefined) {
     ;[[mode]] = config
   }
   $: if (mode !== undefined) {
-    query = { ...queries[mode], '$lookup.space.archived': false }
+    query = { ...queries[mode] }
+    if (query?.space === undefined) {
+      query = { ...query, space: { $nin: archived } }
+    }
     modeSelectorProps = {
       config,
       mode,
diff --git a/plugins/view-resources/src/components/ViewletSelector.svelte b/plugins/view-resources/src/components/ViewletSelector.svelte
index c286ae117e..a3ad056383 100644
--- a/plugins/view-resources/src/components/ViewletSelector.svelte
+++ b/plugins/view-resources/src/components/ViewletSelector.svelte
@@ -46,7 +46,7 @@
     activeViewlet: Record<string, Ref<Viewlet> | null>,
     key: string
   ) {
-    if (viewlets.length === 0) return
+    if (viewlets == null || viewlets.length === 0) return
     const newViewlet = viewlets.find((viewlet) => viewlet?._id === activeViewlet[key]) ?? viewlets[0]
     if (viewlet?._id !== newViewlet?._id) {
       viewlet = newViewlet
@@ -55,7 +55,7 @@
     }
   }
 
-  $: viewslist = viewlets.map((views) => {
+  $: viewslist = viewlets?.map((views) => {
     return {
       id: views._id,
       icon: views.$lookup?.descriptor?.icon,
diff --git a/plugins/view-resources/src/components/filter/ValueFilter.svelte b/plugins/view-resources/src/components/filter/ValueFilter.svelte
index d20b81e3cf..d481941b12 100644
--- a/plugins/view-resources/src/components/filter/ValueFilter.svelte
+++ b/plugins/view-resources/src/components/filter/ValueFilter.svelte
@@ -73,11 +73,17 @@
       prefix = attr.attributeOf + '.'
     }
     const isDerivedFromSpace = hierarchy.isDerived(_class, core.class.Space)
+
+    const archived =
+      space === undefined || isDerivedFromSpace
+        ? []
+        : (await client.findAll(core.class.Space, { archived: true }, { projection: { _id: 1 } })).map((it) => it._id)
+
     objectsPromise = client.findAll(
       _class,
       {
         ...resultQuery,
-        ...(space ? { space } : isDerivedFromSpace ? { archived: false } : { '$lookup.space.archived': false })
+        ...(space ? { space } : isDerivedFromSpace ? { archived: false } : { space: { $nin: archived } })
       },
       {
         sort: { [filter.key.key]: SortingOrder.Ascending },
diff --git a/plugins/view-resources/src/components/list/List.svelte b/plugins/view-resources/src/components/list/List.svelte
index 9968c39834..7f8df629aa 100644
--- a/plugins/view-resources/src/components/list/List.svelte
+++ b/plugins/view-resources/src/components/list/List.svelte
@@ -48,6 +48,7 @@
   $: orderBy = viewOptions.orderBy
 
   const docsQuery = createQuery()
+
   $: lookup = buildConfigLookup(client.getHierarchy(), _class, config, options?.lookup)
   $: resultOptions = { ...options, lookup, ...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {}) }
 
@@ -59,17 +60,38 @@
   $: if (documents === undefined) {
     docsQuery.query(
       _class,
-      resultQuery,
+      noLookup(resultQuery),
       (res) => {
         docs = res
       },
-      resultOptions
+      {
+        ...resultOptions,
+        projection: { ...resultOptions.projection, _id: 1, _class: 1, ...getProjection(viewOptions.groupBy) }
+      }
     )
   } else {
     docsQuery.unsubscribe()
     docs = documents
   }
 
+  function getProjection (fields: string[]): Record<string, number> {
+    const res: Record<string, number> = {}
+    for (const f of fields) {
+      res[f] = 1
+    }
+    return res
+  }
+
+  function noLookup (query: DocumentQuery<Doc>): DocumentQuery<Doc> {
+    const newQuery: DocumentQuery<Doc> = {}
+    for (const [k, v] of Object.entries(query)) {
+      if (!k.startsWith('$lookup.')) {
+        newQuery[k] = v
+      }
+    }
+    return newQuery
+  }
+
   $: dispatch('content', docs)
 
   const dispatch = createEventDispatcher()
@@ -153,6 +175,8 @@
       select(-2, evt.detail)
     }}
     on:collapsed
+    {resultQuery}
+    {resultOptions}
   />
 </div>
 
diff --git a/plugins/view-resources/src/components/list/ListCategories.svelte b/plugins/view-resources/src/components/list/ListCategories.svelte
index 3fce049331..31e6284e74 100644
--- a/plugins/view-resources/src/components/list/ListCategories.svelte
+++ b/plugins/view-resources/src/components/list/ListCategories.svelte
@@ -13,7 +13,17 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { CategoryType, Class, Doc, DocumentQuery, generateId, Lookup, Ref, Space } from '@hcengineering/core'
+  import {
+    CategoryType,
+    Class,
+    Doc,
+    DocumentQuery,
+    FindOptions,
+    generateId,
+    Lookup,
+    Ref,
+    Space
+  } from '@hcengineering/core'
   import { getResource, IntlString } from '@hcengineering/platform'
   import { getClient } from '@hcengineering/presentation'
   import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
@@ -68,6 +78,9 @@
   export let groupPersistKey: string
   export let compactMode: boolean = false
 
+  export let resultQuery: DocumentQuery<Doc>
+  export let resultOptions: FindOptions<Doc>
+
   $: groupByKey = viewOptions.groupBy[level] ?? noCategory
   let categories: CategoryType[] = []
   $: updateCategories(_class, docs, groupByKey, viewOptions, viewOptionsConfig)
@@ -348,13 +361,15 @@
     oneCat={viewOptions.groupBy.length === 1}
     lastCat={i === categories.length - 1}
     {category}
-    {items}
+    itemProj={items}
     {newObjectProps}
     {createItemDialog}
     {createItemDialogProps}
     {createItemLabel}
     {viewOptionsConfig}
     {compactMode}
+    {resultQuery}
+    {resultOptions}
     on:check
     on:uncheckAll
     on:row-focus
@@ -411,6 +426,8 @@
         {initIndex}
         {viewOptionsConfig}
         {listDiv}
+        {resultQuery}
+        {resultOptions}
         on:dragItem
         on:check
         on:uncheckAll
diff --git a/plugins/view-resources/src/components/list/ListCategory.svelte b/plugins/view-resources/src/components/list/ListCategory.svelte
index 3f82f9861c..ecf9f15132 100644
--- a/plugins/view-resources/src/components/list/ListCategory.svelte
+++ b/plugins/view-resources/src/components/list/ListCategory.svelte
@@ -13,9 +13,20 @@
 // limitations under the License.
 -->
 <script lang="ts">
-  import { AggregateValue, Class, Doc, DocumentUpdate, Lookup, PrimitiveType, Ref, Space } from '@hcengineering/core'
+  import {
+    AggregateValue,
+    Class,
+    Doc,
+    DocumentQuery,
+    DocumentUpdate,
+    FindOptions,
+    Lookup,
+    PrimitiveType,
+    Ref,
+    Space
+  } from '@hcengineering/core'
   import { IntlString } from '@hcengineering/platform'
-  import { getClient } from '@hcengineering/presentation'
+  import { createQuery, getClient } from '@hcengineering/presentation'
   import { DocWithRank, calcRank } from '@hcengineering/task'
   import {
     AnyComponent,
@@ -42,7 +53,7 @@
   export let groupByKey: string
   export let space: Ref<Space> | undefined
   export let baseMenuClass: Ref<Class<Doc>> | undefined
-  export let items: Doc[]
+  export let itemProj: Doc[]
   export let createItemDialog: AnyComponent | AnySvelteComponent | undefined
   export let createItemDialogProps: Record<string, any> | undefined
   export let createItemLabel: IntlString | undefined
@@ -68,15 +79,34 @@
   export let index: number
   export let groupPersistKey: string
   export let compactMode: boolean = false
+  export let resultQuery: DocumentQuery<Doc>
+  export let resultOptions: FindOptions<Doc>
 
   $: lastLevel = level + 1 >= viewOptions.groupBy.length
 
+  let items: Doc[] = []
+
+  const docsQuery = createQuery()
+
   const autoFoldLimit = 20
   const defaultLimit = 20
   const singleCategoryLimit = 50
   $: initialLimit = !lastLevel ? undefined : singleCat ? singleCategoryLimit : defaultLimit
   $: limit = initialLimit
 
+  $: if (lastLevel) {
+    docsQuery.query(
+      _class,
+      { ...resultQuery, _id: { $in: itemProj.map((it) => it._id) } },
+      (res) => {
+        items = res
+      },
+      { ...resultOptions, limit: limit ?? 200 }
+    )
+  } else {
+    docsQuery.unsubscribe()
+  }
+
   $: categoryCollapseKey = `list_collapsing_${location.pathname}_${groupPersistKey}`
   $: storedCollapseState = localStorage.getItem(categoryCollapseKey)
 
@@ -92,7 +122,7 @@
 
   function initCollapsed (singleCat: boolean, lastLevel: boolean): void {
     if (localStorage.getItem(categoryCollapseKey) === null) {
-      collapsed = !disableHeader && !singleCat && items.length > (lastLevel ? autoFoldLimit : singleCategoryLimit)
+      collapsed = !disableHeader && !singleCat && itemProj.length > (lastLevel ? autoFoldLimit : singleCategoryLimit)
     }
   }
 
@@ -388,8 +418,9 @@
       {category}
       {space}
       {level}
-      limited={limited.length}
-      {items}
+      limited={lastLevel ? limited.length : itemProj.length}
+      itemsProj={itemProj}
+      items={limited}
       {headerComponent}
       {createItemDialog}
       {createItemDialogProps}
@@ -423,7 +454,7 @@
     {#if !lastLevel}
       <slot
         name="category"
-        docs={items}
+        docs={itemProj}
         {_class}
         {space}
         {lookup}
diff --git a/plugins/view-resources/src/components/list/ListHeader.svelte b/plugins/view-resources/src/components/list/ListHeader.svelte
index b6556a694b..c4cfba63d0 100644
--- a/plugins/view-resources/src/components/list/ListHeader.svelte
+++ b/plugins/view-resources/src/components/list/ListHeader.svelte
@@ -45,6 +45,7 @@
   export let space: Ref<Space> | undefined
   export let limited: number
   export let items: Doc[]
+  export let itemsProj: Doc[]
   export let flat = false
   export let collapsed = false
   export let lastCat = false
@@ -139,11 +140,11 @@
           </span>
         </span>
       {/if}
-      {#if limited < items.length}
+      {#if limited < itemsProj.length}
         <div class="antiSection-header__counter flex-row-center mx-2">
           <span class="caption-color">{limited}</span>
           <span class="text-xs mx-0-5">/</span>
-          {items.length}
+          {itemsProj.length}
         </div>
         <ActionIcon
           size={'small'}
@@ -154,7 +155,7 @@
           }}
         />
       {:else}
-        <span class="antiSection-header__counter ml-2">{items.length}</span>
+        <span class="antiSection-header__counter ml-2">{itemsProj.length}</span>
       {/if}
       <div class="flex-row-center flex-reverse flex-grow mr-2 gap-2 reverse">
         {#each extraHeaders ?? [] as extra}