TSK-915: Tracker status (#2802)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev
2023-03-22 17:08:19 +07:00
committed by GitHub
parent 1ad25782f4
commit 1d94a1c3f8
30 changed files with 266 additions and 489 deletions
@@ -15,11 +15,11 @@
<script lang="ts">
import contact, { EmployeeAccount } from '@hcengineering/contact'
import { employeeByIdStore, EmployeePresenter } from '@hcengineering/contact-resources'
import core, { ClassifierKind, Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
import core, { ClassifierKind, Doc, Mixin, Ref } from '@hcengineering/core'
import { AttributeBarEditor, createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import type { Issue, IssueStatus } from '@hcengineering/tracker'
import type { Issue } from '@hcengineering/tracker'
import { Component, Label } from '@hcengineering/ui'
import { getFiltredKeys, isCollectionAttr, ObjectBox } from '@hcengineering/view-resources'
import tracker from '../../../plugin'
@@ -32,7 +32,6 @@
import StatusEditor from '../StatusEditor.svelte'
export let issue: Issue
export let issueStatuses: WithLookup<IssueStatus>[]
export let showAllMixins: boolean = false
const query = createQuery()
@@ -116,7 +115,7 @@
<Label label={tracker.string.Status} />
</span>
<StatusEditor value={issue} statuses={issueStatuses} shouldShowLabel />
<StatusEditor value={issue} shouldShowLabel />
{#if issue.blockedBy?.length}
<span class="labelTop">
@@ -13,29 +13,28 @@
// limitations under the License.
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import core, { Account, AttachedData, Doc, generateId, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import core, { Account, AttachedData, Doc, generateId, Ref, SortingOrder } from '@hcengineering/core'
import { translate } from '@hcengineering/platform'
import presentation, { getClient, KeyedAttribute } from '@hcengineering/presentation'
import { IssueStatus, IssuePriority, Issue, Project, calcRank } from '@hcengineering/tracker'
import { addNotification, Button, Component, EditBox } from '@hcengineering/ui'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
import { calcRank, Issue, IssuePriority, IssueStatus, Project } from '@hcengineering/tracker'
import { addNotification, Button, Component, EditBox } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import tracker from '../../../plugin'
import AssigneeEditor from '../AssigneeEditor.svelte'
import StatusEditor from '../StatusEditor.svelte'
import PriorityEditor from '../PriorityEditor.svelte'
import EstimationEditor from '../timereport/EstimationEditor.svelte'
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import IssueNotification from '../IssueNotification.svelte'
import { translate } from '@hcengineering/platform'
import PriorityEditor from '../PriorityEditor.svelte'
import StatusEditor from '../StatusEditor.svelte'
import EstimationEditor from '../timereport/EstimationEditor.svelte'
export let parentIssue: Issue
export let issueStatuses: WithLookup<IssueStatus>[]
export let currentProject: Project
const dispatch = createEventDispatcher()
const client = getClient()
let newIssue: AttachedData<Issue> = getIssueDefaults()
let newIssue = { ...getIssueDefaults(), space: currentProject._id }
let thisRef: HTMLDivElement
let focusIssueTitle: () => void
let labels: TagReference[] = []
@@ -71,7 +70,7 @@
}
function resetToDefaults () {
newIssue = getIssueDefaults()
newIssue = { ...getIssueDefaults(), space: currentProject._id }
labels = []
focusIssueTitle?.()
objectId = generateId()
@@ -173,7 +172,6 @@
<div id="status-editor" class="mr-1">
<StatusEditor
value={newIssue}
statuses={issueStatuses}
kind="transparent"
size="medium"
justify="center"
@@ -14,13 +14,13 @@
-->
<script lang="ts">
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
import { Class, Data, Doc, Ref, SortingOrder, WithLookup } from '@hcengineering/core'
import { Class, Data, Doc, Ref, WithLookup } from '@hcengineering/core'
import notification from '@hcengineering/notification'
import { Panel } from '@hcengineering/panel'
import { getResource } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import setting, { settingId } from '@hcengineering/setting'
import type { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import type { Issue, Project } from '@hcengineering/tracker'
import {
Button,
EditBox,
@@ -48,13 +48,11 @@
let lastId: Ref<Doc> = _id
let lastClass: Ref<Class<Doc>> = _class
const queryClient = createQuery()
const statusesQuery = createQuery()
const dispatch = createEventDispatcher()
const client = getClient()
let issue: WithLookup<Issue> | undefined
let currentProject: Project | undefined
let issueStatuses: WithLookup<IssueStatus>[] | undefined
let title = ''
let description = ''
let innerWidth: number
@@ -93,20 +91,8 @@
{ lookup: { attachedTo: tracker.class.Issue, space: tracker.class.Project } }
)
$: currentProject &&
statusesQuery.query(
tracker.class.IssueStatus,
{ attachedTo: currentProject._id },
(statuses) => (issueStatuses = statuses),
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
$: issueId = currentProject && issue && getIssueId(currentProject, issue)
$: canSave = title.trim().length > 0
$: isDescriptionEmpty = !new DOMParser().parseFromString(description, 'text/html').documentElement.innerText?.trim()
$: parentIssue = issue?.$lookup?.attachedTo
let saved = false
@@ -190,7 +176,7 @@
{#if parentIssue}
<div class="mb-6">
{#if currentProject && issueStatuses}
{#if currentProject}
<SubIssueSelector {issue} />
{:else}
<Spinner />
@@ -221,12 +207,8 @@
<div class="mt-6">
{#key issue._id && currentProject !== undefined}
{#if currentProject !== undefined && issueStatuses !== undefined}
<SubIssues
{issue}
issueStatuses={new Map([[currentProject._id, issueStatuses]])}
projects={new Map([[currentProject?._id, currentProject]])}
/>
{#if currentProject !== undefined}
<SubIssues {issue} projects={new Map([[currentProject?._id, currentProject]])} />
{/if}
{/key}
</div>
@@ -270,8 +252,8 @@
</svelte:fragment>
<svelte:fragment slot="custom-attributes">
{#if issue && currentProject && issueStatuses}
<ControlPanel {issue} {issueStatuses} {showAllMixins} />
{#if issue && currentProject}
<ControlPanel {issue} {showAllMixins} />
{/if}
<div class="divider" />
@@ -13,8 +13,8 @@
// limitations under the License.
-->
<script lang="ts">
import { Doc, DocumentQuery, Ref, WithLookup } from '@hcengineering/core'
import { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { Doc, DocumentQuery, Ref } from '@hcengineering/core'
import { Issue, Project } from '@hcengineering/tracker'
import { Viewlet, ViewOptions } from '@hcengineering/view'
import {
ActionContext,
@@ -34,7 +34,6 @@
// Extra properties
export let projects: Map<Ref<Project>, Project> | undefined
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]>
let list: List
@@ -66,7 +65,7 @@
documents={issues}
{query}
flatHeaders={true}
props={{ projects, issueStatuses }}
props={{ projects }}
{disableHeader}
selectedObjectIds={$selectionStore ?? []}
on:row-focus={(event) => {
@@ -13,18 +13,18 @@
// limitations under the License.
-->
<script lang="ts">
import { Ref, SortingOrder, toIdMap, WithLookup } from '@hcengineering/core'
import { Ref, toIdMap } from '@hcengineering/core'
import { createQuery } from '@hcengineering/presentation'
import { Issue, IssueStatus, Project, trackerId } from '@hcengineering/tracker'
import { Issue, Project, trackerId } from '@hcengineering/tracker'
import {
Button,
Chevron,
closeTooltip,
ExpandCollapse,
getCurrentLocation,
IconScaleFull,
IconAdd,
IconArrowRight,
IconScaleFull,
Label,
navigate
} from '@hcengineering/ui'
@@ -33,8 +33,8 @@
createFilter,
filterStore,
getViewOptions,
viewOptionStore,
ViewletSettingButton
ViewletSettingButton,
viewOptionStore
} from '@hcengineering/view-resources'
import tracker from '../../../plugin'
import CreateSubIssue from './CreateSubIssue.svelte'
@@ -42,7 +42,6 @@
export let issue: Issue
export let projects: Map<Ref<Project>, Project>
export let issueStatuses: Map<Ref<Project>, WithLookup<IssueStatus>[]>
let subIssueEditorRef: HTMLDivElement
let isCollapsed = false
@@ -58,7 +57,6 @@
})
let _projects = projects
let _issueStatuses = issueStatuses
const projectsQuery = createQuery()
@@ -70,28 +68,6 @@
projectsQuery.unsubscribe()
}
const statusesQuery = createQuery()
$: if (issueStatuses === undefined) {
statusesQuery.query(
tracker.class.IssueStatus,
{},
(statuses) => {
const st = new Map<Ref<Project>, WithLookup<IssueStatus>[]>()
for (const s of statuses) {
const id = s.attachedTo as Ref<Project>
st.set(id, [...(st.get(id) ?? []), s])
}
_issueStatuses = st
},
{
lookup: { category: tracker.class.IssueStatusCategory },
sort: { rank: SortingOrder.Ascending }
}
)
} else {
statusesQuery.unsubscribe()
}
$: viewOptions = viewlet !== undefined ? getViewOptions(viewlet, $viewOptionStore) : undefined
</script>
@@ -158,39 +134,25 @@
</div>
</div>
<div class="mt-1">
{#if issueStatuses}
{#if hasSubIssues && viewOptions && viewlet}
{#if !isCollapsed}
<ExpandCollapse isExpanded={!isCollapsed}>
<div class="list" class:collapsed={isCollapsed}>
<SubIssueList
projects={_projects}
{viewlet}
{viewOptions}
issueStatuses={_issueStatuses}
query={{ attachedTo: issue._id }}
/>
</div>
</ExpandCollapse>
{#if hasSubIssues && viewOptions && viewlet}
{#if !isCollapsed}
<ExpandCollapse isExpanded={!isCollapsed}>
<div class="list" class:collapsed={isCollapsed}>
<SubIssueList projects={_projects} {viewlet} {viewOptions} query={{ attachedTo: issue._id }} />
</div>
</ExpandCollapse>
{/if}
{/if}
<ExpandCollapse isExpanded={!isCollapsed}>
{#if isCreating}
{@const project = projects.get(issue.space)}
{#if project !== undefined}
<div class="pt-4" bind:this={subIssueEditorRef}>
<CreateSubIssue parentIssue={issue} currentProject={project} on:close={() => (isCreating = false)} />
</div>
{/if}
{/if}
<ExpandCollapse isExpanded={!isCollapsed}>
{#if isCreating}
{@const project = projects.get(issue.space)}
{@const statuses = issueStatuses.get(issue.space)}
{#if project !== undefined && statuses !== undefined}
<div class="pt-4" bind:this={subIssueEditorRef}>
<CreateSubIssue
parentIssue={issue}
issueStatuses={statuses}
currentProject={project}
on:close={() => (isCreating = false)}
/>
</div>
{/if}
{/if}
</ExpandCollapse>
{/if}
</ExpandCollapse>
</div>
<style lang="scss">
@@ -29,7 +29,7 @@
} from '@hcengineering/ui'
import { getIssueId } from '../../../issues'
import tracker from '../../../plugin'
import { subIssueListProvider } from '../../../utils'
import { statusByIdStore, statusStore, subIssueListProvider } from '../../../utils'
export let value: WithLookup<Issue>
export let currentProject: Project | undefined = undefined
@@ -44,7 +44,7 @@
$: project = currentProject
let subIssues: Issue[] = []
let countComplate: number = 0
let countComplete: number = 0
const projectQuery = createQuery()
$: if (currentProject === undefined) {
@@ -59,7 +59,6 @@
projectQuery.unsubscribe()
}
const query = createQuery()
const statusesQuery = createQuery()
$: update(value)
@@ -75,25 +74,18 @@
} else {
query.unsubscribe()
}
if (value.subIssues > 0 || value.$lookup?.subIssues !== undefined) {
statusesQuery.query(tracker.class.IssueStatus, {}, (res) => (statuses = res), {
lookup: { category: tracker.class.IssueStatusCategory }
})
} else {
statusesQuery.unsubscribe()
}
}
let statuses: WithLookup<IssueStatus>[] = []
$: if (statuses && subIssues) {
const doneStatuses = statuses.filter((s) => s.category === tracker.issueStatusCategory.Completed).map((p) => p._id)
countComplate = subIssues.filter((si) => doneStatuses.includes(si.status)).length
$: if (subIssues) {
const doneStatuses = $statusStore
.filter((s) => s.category === tracker.issueStatusCategory.Completed)
.map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
}
$: hasSubIssues = (subIssues?.length ?? 0) > 0
function getIssueStatusIcon (issue: Issue, statuses: WithLookup<IssueStatus>[] | undefined) {
const status = statuses?.find((s) => issue.status === s._id)
function getIssueStatusIcon (issue: Issue, statuses: Map<Ref<WithLookup<IssueStatus>>, WithLookup<IssueStatus>>) {
const status = statuses.get(issue.status)
const category = status?.$lookup?.category
const color = status?.color ?? category?.color
@@ -119,7 +111,12 @@
value: subIssues.map((iss) => {
const text = project ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
return { id: iss._id, text, isSelected: iss._id === value._id, ...getIssueStatusIcon(iss, statuses) }
return {
id: iss._id,
text,
isSelected: iss._id === value._id,
...getIssueStatusIcon(iss, $statusByIdStore)
}
}),
width: 'large'
},
@@ -156,9 +153,9 @@
{#if subIssues}
<div class="flex-row-center content-color text-sm pointer-events-none">
<div class="mr-1">
<ProgressCircle bind:value={countComplate} bind:max={subIssues.length} size={'inline'} primary />
<ProgressCircle bind:value={countComplete} bind:max={subIssues.length} size={'inline'} primary />
</div>
{countComplate}/{subIssues.length}
{countComplete}/{subIssues.length}
</div>
{/if}
</svelte:fragment>