UBER-720: Rework list view to multiple requests (#3578)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-08-10 22:12:48 +07:00 committed by GitHub
parent 7a9e8f0321
commit 26d8099f1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 131 additions and 27 deletions

View File

@ -541,12 +541,7 @@ export function createModel (builder: Builder): void {
displayProps: { key: 'assignee', fixed: 'right' }, displayProps: { key: 'assignee', fixed: 'right' },
props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' } props: { kind: 'list', shouldShowName: false, avatarSize: 'x-small' }
} }
], ]
options: {
lookup: {
space: tracker.class.Project
}
}
}, },
tracker.viewlet.IssueList tracker.viewlet.IssueList
) )
@ -1052,7 +1047,6 @@ export function createModel (builder: Builder): void {
icon: tracker.icon.Issues, icon: tracker.icon.Issues,
component: tracker.component.Issues, component: tracker.component.Issues,
componentProps: { componentProps: {
baseQuery: { '$lookup.space.archived': false },
space: undefined, space: undefined,
title: tracker.string.AllIssues, title: tracker.string.AllIssues,
config: [ config: [

View File

@ -59,4 +59,5 @@
{disabled} {disabled}
{inline} {inline}
{accent} {accent}
on:accent-color
/> />

View File

@ -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 } $: queries = { all, active, backlog }
$: mode = $resolvedLocationStore.query?.mode ?? undefined $: mode = $resolvedLocationStore.query?.mode ?? undefined
$: if (mode === undefined || queries[mode] === undefined) { $: if (mode === undefined || queries[mode] === undefined) {
;[[mode]] = config ;[[mode]] = config
} }
$: if (mode !== undefined) { $: if (mode !== undefined) {
query = { ...queries[mode], '$lookup.space.archived': false } query = { ...queries[mode] }
if (query?.space === undefined) {
query = { ...query, space: { $nin: archived } }
}
modeSelectorProps = { modeSelectorProps = {
config, config,
mode, mode,

View File

@ -18,7 +18,7 @@
import { Doc, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core' import { Doc, DocumentQuery, getCurrentAccount, Ref } from '@hcengineering/core'
import type { IntlString } from '@hcengineering/platform' import type { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation' import { createQuery } from '@hcengineering/presentation'
import type { Issue } from '@hcengineering/tracker' import type { Issue, Project } from '@hcengineering/tracker'
import { resolvedLocationStore } from '@hcengineering/ui' import { resolvedLocationStore } from '@hcengineering/ui'
import { IModeSelector } from '@hcengineering/ui' import { IModeSelector } from '@hcengineering/ui'
@ -50,13 +50,28 @@
{ sort: { _id: 1 } } { 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 } $: queries = { assigned, created, subscribed }
$: mode = $resolvedLocationStore.query?.mode ?? undefined $: mode = $resolvedLocationStore.query?.mode ?? undefined
$: if (mode === undefined || queries[mode] === undefined) { $: if (mode === undefined || queries[mode] === undefined) {
;[[mode]] = config ;[[mode]] = config
} }
$: if (mode !== undefined) { $: if (mode !== undefined) {
query = { ...queries[mode], '$lookup.space.archived': false } query = { ...queries[mode] }
if (query?.space === undefined) {
query = { ...query, space: { $nin: archived } }
}
modeSelectorProps = { modeSelectorProps = {
config, config,
mode, mode,

View File

@ -46,7 +46,7 @@
activeViewlet: Record<string, Ref<Viewlet> | null>, activeViewlet: Record<string, Ref<Viewlet> | null>,
key: string 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] const newViewlet = viewlets.find((viewlet) => viewlet?._id === activeViewlet[key]) ?? viewlets[0]
if (viewlet?._id !== newViewlet?._id) { if (viewlet?._id !== newViewlet?._id) {
viewlet = newViewlet viewlet = newViewlet
@ -55,7 +55,7 @@
} }
} }
$: viewslist = viewlets.map((views) => { $: viewslist = viewlets?.map((views) => {
return { return {
id: views._id, id: views._id,
icon: views.$lookup?.descriptor?.icon, icon: views.$lookup?.descriptor?.icon,

View File

@ -73,11 +73,17 @@
prefix = attr.attributeOf + '.' prefix = attr.attributeOf + '.'
} }
const isDerivedFromSpace = hierarchy.isDerived(_class, core.class.Space) 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( objectsPromise = client.findAll(
_class, _class,
{ {
...resultQuery, ...resultQuery,
...(space ? { space } : isDerivedFromSpace ? { archived: false } : { '$lookup.space.archived': false }) ...(space ? { space } : isDerivedFromSpace ? { archived: false } : { space: { $nin: archived } })
}, },
{ {
sort: { [filter.key.key]: SortingOrder.Ascending }, sort: { [filter.key.key]: SortingOrder.Ascending },

View File

@ -48,6 +48,7 @@
$: orderBy = viewOptions.orderBy $: orderBy = viewOptions.orderBy
const docsQuery = createQuery() const docsQuery = createQuery()
$: lookup = buildConfigLookup(client.getHierarchy(), _class, config, options?.lookup) $: lookup = buildConfigLookup(client.getHierarchy(), _class, config, options?.lookup)
$: resultOptions = { ...options, lookup, ...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {}) } $: resultOptions = { ...options, lookup, ...(orderBy !== undefined ? { sort: { [orderBy[0]]: orderBy[1] } } : {}) }
@ -59,17 +60,38 @@
$: if (documents === undefined) { $: if (documents === undefined) {
docsQuery.query( docsQuery.query(
_class, _class,
resultQuery, noLookup(resultQuery),
(res) => { (res) => {
docs = res docs = res
}, },
resultOptions {
...resultOptions,
projection: { ...resultOptions.projection, _id: 1, _class: 1, ...getProjection(viewOptions.groupBy) }
}
) )
} else { } else {
docsQuery.unsubscribe() docsQuery.unsubscribe()
docs = documents 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) $: dispatch('content', docs)
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
@ -153,6 +175,8 @@
select(-2, evt.detail) select(-2, evt.detail)
}} }}
on:collapsed on:collapsed
{resultQuery}
{resultOptions}
/> />
</div> </div>

View File

@ -13,7 +13,17 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <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 { getResource, IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation' import { getClient } from '@hcengineering/presentation'
import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui' import { AnyComponent, AnySvelteComponent } from '@hcengineering/ui'
@ -68,6 +78,9 @@
export let groupPersistKey: string export let groupPersistKey: string
export let compactMode: boolean = false export let compactMode: boolean = false
export let resultQuery: DocumentQuery<Doc>
export let resultOptions: FindOptions<Doc>
$: groupByKey = viewOptions.groupBy[level] ?? noCategory $: groupByKey = viewOptions.groupBy[level] ?? noCategory
let categories: CategoryType[] = [] let categories: CategoryType[] = []
$: updateCategories(_class, docs, groupByKey, viewOptions, viewOptionsConfig) $: updateCategories(_class, docs, groupByKey, viewOptions, viewOptionsConfig)
@ -348,13 +361,15 @@
oneCat={viewOptions.groupBy.length === 1} oneCat={viewOptions.groupBy.length === 1}
lastCat={i === categories.length - 1} lastCat={i === categories.length - 1}
{category} {category}
{items} itemProj={items}
{newObjectProps} {newObjectProps}
{createItemDialog} {createItemDialog}
{createItemDialogProps} {createItemDialogProps}
{createItemLabel} {createItemLabel}
{viewOptionsConfig} {viewOptionsConfig}
{compactMode} {compactMode}
{resultQuery}
{resultOptions}
on:check on:check
on:uncheckAll on:uncheckAll
on:row-focus on:row-focus
@ -411,6 +426,8 @@
{initIndex} {initIndex}
{viewOptionsConfig} {viewOptionsConfig}
{listDiv} {listDiv}
{resultQuery}
{resultOptions}
on:dragItem on:dragItem
on:check on:check
on:uncheckAll on:uncheckAll

View File

@ -13,9 +13,20 @@
// limitations under the License. // limitations under the License.
--> -->
<script lang="ts"> <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 { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation' import { createQuery, getClient } from '@hcengineering/presentation'
import { DocWithRank, calcRank } from '@hcengineering/task' import { DocWithRank, calcRank } from '@hcengineering/task'
import { import {
AnyComponent, AnyComponent,
@ -42,7 +53,7 @@
export let groupByKey: string export let groupByKey: string
export let space: Ref<Space> | undefined export let space: Ref<Space> | undefined
export let baseMenuClass: Ref<Class<Doc>> | undefined export let baseMenuClass: Ref<Class<Doc>> | undefined
export let items: Doc[] export let itemProj: Doc[]
export let createItemDialog: AnyComponent | AnySvelteComponent | undefined export let createItemDialog: AnyComponent | AnySvelteComponent | undefined
export let createItemDialogProps: Record<string, any> | undefined export let createItemDialogProps: Record<string, any> | undefined
export let createItemLabel: IntlString | undefined export let createItemLabel: IntlString | undefined
@ -68,15 +79,34 @@
export let index: number export let index: number
export let groupPersistKey: string export let groupPersistKey: string
export let compactMode: boolean = false export let compactMode: boolean = false
export let resultQuery: DocumentQuery<Doc>
export let resultOptions: FindOptions<Doc>
$: lastLevel = level + 1 >= viewOptions.groupBy.length $: lastLevel = level + 1 >= viewOptions.groupBy.length
let items: Doc[] = []
const docsQuery = createQuery()
const autoFoldLimit = 20 const autoFoldLimit = 20
const defaultLimit = 20 const defaultLimit = 20
const singleCategoryLimit = 50 const singleCategoryLimit = 50
$: initialLimit = !lastLevel ? undefined : singleCat ? singleCategoryLimit : defaultLimit $: initialLimit = !lastLevel ? undefined : singleCat ? singleCategoryLimit : defaultLimit
$: limit = initialLimit $: 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}` $: categoryCollapseKey = `list_collapsing_${location.pathname}_${groupPersistKey}`
$: storedCollapseState = localStorage.getItem(categoryCollapseKey) $: storedCollapseState = localStorage.getItem(categoryCollapseKey)
@ -92,7 +122,7 @@
function initCollapsed (singleCat: boolean, lastLevel: boolean): void { function initCollapsed (singleCat: boolean, lastLevel: boolean): void {
if (localStorage.getItem(categoryCollapseKey) === null) { 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} {category}
{space} {space}
{level} {level}
limited={limited.length} limited={lastLevel ? limited.length : itemProj.length}
{items} itemsProj={itemProj}
items={limited}
{headerComponent} {headerComponent}
{createItemDialog} {createItemDialog}
{createItemDialogProps} {createItemDialogProps}
@ -423,7 +454,7 @@
{#if !lastLevel} {#if !lastLevel}
<slot <slot
name="category" name="category"
docs={items} docs={itemProj}
{_class} {_class}
{space} {space}
{lookup} {lookup}

View File

@ -45,6 +45,7 @@
export let space: Ref<Space> | undefined export let space: Ref<Space> | undefined
export let limited: number export let limited: number
export let items: Doc[] export let items: Doc[]
export let itemsProj: Doc[]
export let flat = false export let flat = false
export let collapsed = false export let collapsed = false
export let lastCat = false export let lastCat = false
@ -139,11 +140,11 @@
</span> </span>
</span> </span>
{/if} {/if}
{#if limited < items.length} {#if limited < itemsProj.length}
<div class="antiSection-header__counter flex-row-center mx-2"> <div class="antiSection-header__counter flex-row-center mx-2">
<span class="caption-color">{limited}</span> <span class="caption-color">{limited}</span>
<span class="text-xs mx-0-5">/</span> <span class="text-xs mx-0-5">/</span>
{items.length} {itemsProj.length}
</div> </div>
<ActionIcon <ActionIcon
size={'small'} size={'small'}
@ -154,7 +155,7 @@
}} }}
/> />
{:else} {:else}
<span class="antiSection-header__counter ml-2">{items.length}</span> <span class="antiSection-header__counter ml-2">{itemsProj.length}</span>
{/if} {/if}
<div class="flex-row-center flex-reverse flex-grow mr-2 gap-2 reverse"> <div class="flex-row-center flex-reverse flex-grow mr-2 gap-2 reverse">
{#each extraHeaders ?? [] as extra} {#each extraHeaders ?? [] as extra}