TSK-1342: Reduce number of transfer data and improve Kanban initial render speed (#3078)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-04-26 12:01:27 +07:00 committed by GitHub
parent 40d13681d6
commit 42e320f425
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 89 additions and 63 deletions

View File

@ -619,7 +619,6 @@ export function createModel (builder: Builder): void {
space: recruit.class.Vacancy
}
],
assignee: contact.class.Employee,
_id: {
related: [tracker.class.Issue, 'relations._id']
}

View File

@ -595,7 +595,7 @@ export function createModel (builder: Builder): void {
props: { listProps: { key: 'modified', fixed: 'left' } }
},
{
key: '$lookup.assignee',
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
}
@ -675,7 +675,7 @@ export function createModel (builder: Builder): void {
props: { listProps: { fixed: 'right', optional: true } }
},
{
key: '$lookup.assignee',
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
}
@ -726,7 +726,7 @@ export function createModel (builder: Builder): void {
props: { listProps: { fixed: 'right' } }
},
{
key: '$lookup.assignee',
key: 'assignee',
presenter: tracker.component.AssigneePresenter,
props: { defaultClass: contact.class.Employee, shouldShowLabel: false }
}
@ -1783,7 +1783,7 @@ export function createModel (builder: Builder): void {
{ key: '', presenter: tracker.component.SprintDatePresenter, props: { field: 'startDate' } },
{ key: '', presenter: tracker.component.SprintDatePresenter, props: { field: 'targetDate' } },
{
key: '$lookup.lead',
key: 'lead',
presenter: tracker.component.SprintLeadPresenter,
props: {
_class: tracker.class.Sprint,

View File

@ -325,6 +325,7 @@
<KanbanRow
bind:this={stateRows[si]}
on:obj-focus
index={si}
{groupByDocs}
{stateObjects}
{isDragging}

View File

@ -17,7 +17,8 @@
import ui, { Button, IconMoreH, mouseAttractor } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import { slide } from 'svelte/transition'
import { CardDragEvent, Item } from '../types'
import { CardDragEvent, DocWithRank, Item } from '../types'
import Spinner from '@hcengineering/ui/src/components/Spinner.svelte'
export let stateObjects: Item[]
export let isDragging: boolean
@ -27,6 +28,7 @@
export let selection: number | undefined = undefined
export let checkedSet: Set<Ref<Doc>>
export let state: CategoryType
export let index: number
export let cardDragOver: (evt: CardDragEvent, object: Item) => void
export let cardDrop: (evt: CardDragEvent, object: Item) => void
@ -53,7 +55,20 @@
let limit = 50
$: limitedObjects = stateObjects.slice(0, limit)
let limitedObjects: DocWithRank[] = []
let loading = false
function nop (op: () => void, timeout: number) {
op()
}
$: {
loading = true
;(limitedObjects.length > 0 ? nop : setTimeout)(() => {
limitedObjects = stateObjects.slice(0, limit)
loading = false
}, index * 2)
}
</script>
{#each limitedObjects as object, i (object._id)}
@ -88,19 +103,23 @@
{/each}
{#if stateObjects.length > limitedObjects.length}
<div class="step-tb75">
<div class="card-container h-18 flex-row-center flex-between p-4">
<span class="p-1">
{limitedObjects.length}/{stateObjects.length}
</span>
<Button
size={'small'}
icon={IconMoreH}
label={ui.string.ShowMore}
on:click={() => {
limit = limit + 20
}}
/>
</div>
{#if loading}
<Spinner />
{:else}
<div class="card-container h-18 flex-row-center flex-between p-4">
<span class="p-1">
{limitedObjects.length}/{stateObjects.length}
</span>
<Button
size={'small'}
icon={IconMoreH}
label={ui.string.ShowMore}
on:click={() => {
limit = limit + 20
}}
/>
</div>
{/if}
</div>
{/if}

View File

@ -392,6 +392,9 @@
}
delayCall(() => {
if (divBox === undefined) {
return
}
const tempEls = divBox.querySelectorAll('.categoryHeader')
observer = new IntersectionObserver(checkIntersection, { root: null, rootMargin: '0px', threshold: 0.1 })
tempEls.forEach((el) => observer.observe(el))

View File

@ -101,8 +101,12 @@
bind:this={imageElement}
on:load={(data) => {
if (imageElement !== undefined) {
accentColor = imageToColor(imageElement)
dispatch('accent-color', accentColor)
try {
accentColor = imageToColor(imageElement)
dispatch('accent-color', accentColor)
} catch (err) {
// Ignore
}
}
}}
/>
@ -114,8 +118,12 @@
bind:this={imageElement}
on:load={(data) => {
if (imageElement !== undefined) {
accentColor = imageToColor(imageElement)
dispatch('accent-color', accentColor)
try {
accentColor = imageToColor(imageElement)
dispatch('accent-color', accentColor)
} catch (err) {
// ignore
}
}
}}
/>

View File

@ -1,13 +1,13 @@
<script lang="ts">
import { Employee } from '@hcengineering/contact'
import { WithLookup } from '@hcengineering/core'
import { Ref, WithLookup } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { IconSize } from '@hcengineering/ui'
import { PersonLabelTooltip } from '..'
import { PersonLabelTooltip, employeeByIdStore } from '..'
import PersonPresenter from '../components/PersonPresenter.svelte'
import contact from '../plugin'
export let value: WithLookup<Employee> | null | undefined
export let value: Ref<Employee> | WithLookup<Employee> | null | undefined
export let tooltipLabels: PersonLabelTooltip | undefined = undefined
export let shouldShowAvatar: boolean = true
export let shouldShowName: boolean = true
@ -21,11 +21,13 @@
export let accent: boolean = false
export let defaultName: IntlString | undefined = undefined
export let element: HTMLElement | undefined = undefined
$: employeeValue = typeof value === 'string' ? $employeeByIdStore.get(value) : value
</script>
<PersonPresenter
bind:element
{value}
value={employeeValue}
{tooltipLabels}
onEdit={onEmployeeEdit}
{shouldShowAvatar}
@ -37,6 +39,6 @@
{colorInherit}
{accent}
{defaultName}
statusLabel={value?.active === false && shouldShowName ? contact.string.Inactive : undefined}
statusLabel={employeeValue?.active === false && shouldShowName ? contact.string.Inactive : undefined}
on:accent-color
/>

View File

@ -68,7 +68,7 @@
<div class="tool mr-1 flex-row-center">
{#if !dragged}
<div class="mr-2">
<Component is={notification.component.NotificationPresenter} props={{ value: object }} />
<Component showLoading={false} is={notification.component.NotificationPresenter} props={{ value: object }} />
</div>
{/if}
</div>
@ -76,6 +76,7 @@
<div class="tool mr-1 flex-row-center">
<div class="step-lr75">
<Component
showLoading={false}
is={contact.component.ChannelsPresenter}
props={{ value: channels, object: object.$lookup?.attachedTo, length: 'tiny' }}
/>
@ -89,7 +90,7 @@
<div class="mr-2">
<ApplicationPresenter value={object} />
</div>
<Component is={tracker.component.RelatedIssueSelector} props={{ object }} />
<Component showLoading={false} is={tracker.component.RelatedIssueSelector} props={{ object }} />
</div>
<DueDatePresenter
value={object.dueDate}
@ -115,7 +116,7 @@
{/if}
</div>
<AssigneePresenter
value={object.$lookup?.assignee}
value={object.assignee}
issueId={object._id}
defaultClass={contact.class.Employee}
currentSpace={object.space}

View File

@ -17,14 +17,14 @@
import { Class, Doc, Ref, Space } from '@hcengineering/core'
import { Task } from '@hcengineering/task'
import { getClient } from '@hcengineering/presentation'
import { UsersPopup } from '@hcengineering/contact-resources'
import { UsersPopup, employeeByIdStore } from '@hcengineering/contact-resources'
import { AttributeModel } from '@hcengineering/view'
import { eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { getObjectPresenter } from '@hcengineering/view-resources'
import { IntlString } from '@hcengineering/platform'
import { IntlString, getEmbeddedLabel } from '@hcengineering/platform'
import task from '../plugin'
export let value: Employee | null | undefined
export let value: Ref<Employee> | Employee | null | undefined
export let issueId: Ref<Task>
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
export let currentSpace: Ref<Space> | undefined = undefined
@ -33,13 +33,15 @@
export let defaultName: IntlString | undefined = undefined
export let placeholderLabel: IntlString | undefined = undefined
$: employeeValue = typeof value === 'string' ? $employeeByIdStore.get(value) : value
const client = getClient()
let presenter: AttributeModel | undefined
$: if (value || defaultClass) {
if (value) {
getObjectPresenter(client, value._class, { key: '' }).then((p) => {
$: if (employeeValue || defaultClass) {
if (employeeValue) {
getObjectPresenter(client, employeeValue._class, { key: '' }).then((p) => {
presenter = p
})
} else if (defaultClass) {
@ -84,12 +86,12 @@
UsersPopup,
{
_class: contact.class.Employee,
selected: value?._id,
selected: employeeValue?._id,
docQuery: {
active: true
},
allowDeselect: true,
placeholder: task.string.AssignThisTask
placeholder: placeholderLabel ?? presenter?.label ?? task.string.AssignThisTask
},
eventToHTMLElement(event),
handleAssigneeChanged
@ -108,7 +110,7 @@
shouldShowName={shouldShowLabel}
onEmployeeEdit={handleAssigneeEditorOpened}
tooltipLabels={{
personLabel: value ? getName(value) : undefined,
personLabel: employeeValue ? getEmbeddedLabel(getName(employeeValue)) : undefined,
placeholderLabel: placeholderLabel ?? presenter.label
}}
/>

View File

@ -14,7 +14,6 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@hcengineering/contact'
import {
CategoryType,
Class,
@ -133,7 +132,6 @@
...options,
lookup: {
...options?.lookup,
assignee: contact.class.Employee,
space: task.class.SpaceWithStates,
state: task.class.State,
doneState: task.class.DoneState

View File

@ -13,10 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@hcengineering/contact'
import { Class, Doc, FindOptions, getObjectValue, Ref, Timestamp } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Issue, Component } from '@hcengineering/tracker'
import { Component, Issue } from '@hcengineering/tracker'
import { CheckBox, Spinner, Timeline, TimelineRow } from '@hcengineering/ui'
import { AttributeModel, BuildModelKey } from '@hcengineering/view'
import { buildModel, LoadingProps } from '@hcengineering/view-resources'
@ -37,7 +36,6 @@
const baseOptions: FindOptions<Issue> = {
lookup: {
assignee: contact.class.Employee,
status: tracker.class.IssueStatus
}
}

View File

@ -13,10 +13,9 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@hcengineering/contact'
import { Class, Doc, FindOptions, getObjectValue, Ref } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import { Issue, Component } from '@hcengineering/tracker'
import { Component, Issue } from '@hcengineering/tracker'
import { CheckBox, Spinner, tooltip } from '@hcengineering/ui'
import { BuildModelKey } from '@hcengineering/view'
import { buildModel, LoadingProps } from '@hcengineering/view-resources'
@ -37,7 +36,6 @@
const baseOptions: FindOptions<Issue> = {
lookup: {
assignee: contact.class.Employee,
status: tracker.class.IssueStatus
}
}

View File

@ -14,17 +14,17 @@
-->
<script lang="ts">
import contact, { Employee } from '@hcengineering/contact'
import { UsersPopup } from '@hcengineering/contact-resources'
import { Class, Doc, Ref } from '@hcengineering/core'
import { IntlString } from '@hcengineering/platform'
import { getClient } from '@hcengineering/presentation'
import { UsersPopup } from '@hcengineering/contact-resources'
import { Issue, IssueTemplate } from '@hcengineering/tracker'
import { eventToHTMLElement, showPopup } from '@hcengineering/ui'
import { AttributeModel } from '@hcengineering/view'
import { getObjectPresenter } from '@hcengineering/view-resources'
import tracker from '../../plugin'
export let value: Employee | null | undefined
export let value: Employee | Ref<Employee> | null | undefined
export let object: Issue | IssueTemplate
export let defaultClass: Ref<Class<Doc>> | undefined = undefined
export let isEditable: boolean = true
@ -37,9 +37,11 @@
$: if (value || defaultClass) {
if (value) {
getObjectPresenter(client, value._class, { key: '' }).then((p) => {
presenter = p
})
getObjectPresenter(client, typeof value === 'string' ? contact.class.Employee : value._class, { key: '' }).then(
(p) => {
presenter = p
}
)
} else if (defaultClass) {
getObjectPresenter(client, defaultClass, { key: '' }).then((p) => {
presenter = p
@ -68,7 +70,7 @@
UsersPopup,
{
_class: contact.class.Employee,
selected: value?._id,
selected: typeof value === 'string' ? value : value?._id,
docQuery: {
active: true
},

View File

@ -130,7 +130,6 @@
}
const lookup: Lookup<Issue> = {
assignee: contact.class.Employee,
space: tracker.class.Project,
status: tracker.class.IssueStatus,
component: tracker.class.Component,

View File

@ -13,12 +13,11 @@
// limitations under the License.
-->
<script lang="ts">
import contact from '@hcengineering/contact'
import { Doc, DocumentQuery, Ref, SortingOrder } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, Project } from '@hcengineering/tracker'
import { Label, Spinner } from '@hcengineering/ui'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import { ViewOptions, Viewlet } from '@hcengineering/view'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import AddIssueDuo from '../../icons/AddIssueDuo.svelte'
@ -41,10 +40,7 @@
let projects: Map<Ref<Project>, Project> | undefined
$: subIssuesQuery.query(tracker.class.Issue, query, async (result) => (subIssues = result), {
sort: { rank: SortingOrder.Ascending },
lookup: {
assignee: contact.class.Employee
}
sort: { rank: SortingOrder.Ascending }
})
const projectsQuery = createQuery()

View File

@ -98,7 +98,7 @@ export const defaultPriorities = [
export const issuesGroupBySorting: Record<IssuesGrouping, SortingQuery<Issue>> = {
[IssuesGrouping.Status]: { '$lookup.status.rank': SortingOrder.Ascending },
[IssuesGrouping.Assignee]: { '$lookup.assignee.name': SortingOrder.Ascending },
[IssuesGrouping.Assignee]: { assignee: SortingOrder.Ascending },
[IssuesGrouping.Priority]: { priority: SortingOrder.Ascending },
[IssuesGrouping.Component]: { '$lookup.component.label': SortingOrder.Ascending },
[IssuesGrouping.Sprint]: { '$lookup.sprint.label': SortingOrder.Ascending },