mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-23 10:05:01 +02:00
UBER-963: Related issues (#3773)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
@@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import core, { SortingOrder, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import core, { IdMap, SortingOrder, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Issue, IssueStatus } from '@hcengineering/tracker'
|
||||
import {
|
||||
Icon,
|
||||
@@ -25,7 +25,6 @@
|
||||
closeTooltip,
|
||||
getPlatformColorDef,
|
||||
navigate,
|
||||
showPopup,
|
||||
themeStore,
|
||||
tooltip
|
||||
} from '@hcengineering/ui'
|
||||
@@ -33,6 +32,7 @@
|
||||
import { getIssueId, issueLinkFragmentProvider } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
||||
import { listIssueStatusOrder } from '../../../utils'
|
||||
|
||||
export let issue: WithLookup<Issue>
|
||||
|
||||
@@ -56,49 +56,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
function showSubIssues () {
|
||||
if (subIssues) {
|
||||
closeTooltip()
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: subIssues.map((iss) => {
|
||||
const project = iss.$lookup?.space
|
||||
const status = iss.$lookup?.status as WithLookup<IssueStatus>
|
||||
const icon = status.$lookup?.category?.icon
|
||||
const color = status.color ?? status.$lookup?.category?.color
|
||||
|
||||
return {
|
||||
id: iss._id,
|
||||
icon,
|
||||
isSelected: iss._id === issue._id,
|
||||
...(project !== undefined ? { text: `${getIssueId(project, iss)} ${iss.title}` } : undefined),
|
||||
...(color !== undefined ? { iconColor: getPlatformColorDef(color, $themeStore.dark).icon } : undefined)
|
||||
}
|
||||
}),
|
||||
width: 'large'
|
||||
},
|
||||
{
|
||||
getBoundingClientRect: () => {
|
||||
const rect = subIssuesElement.getBoundingClientRect()
|
||||
const offsetX = 5
|
||||
const offsetY = -1
|
||||
|
||||
return DOMRect.fromRect({ width: 1, height: 1, x: rect.right + offsetX, y: rect.top + offsetY })
|
||||
}
|
||||
},
|
||||
(selectedIssue) => {
|
||||
if (selectedIssue !== undefined) {
|
||||
const issue = subIssues?.find((p) => p._id === selectedIssue)
|
||||
if (issue) {
|
||||
openIssue(issue)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
$: areSubIssuesLoading = !subIssues
|
||||
$: parentIssue = issue.$lookup?.attachedTo ? (issue.$lookup?.attachedTo as Issue) : null
|
||||
$: if (parentIssue && parentIssue.subIssues > 0) {
|
||||
@@ -119,6 +76,52 @@
|
||||
}
|
||||
|
||||
$: parentStatus = parentIssue ? $statusStore.get(parentIssue.status) : undefined
|
||||
|
||||
let categories: IdMap<StatusCategory> = new Map()
|
||||
|
||||
getClient()
|
||||
.findAll(core.class.StatusCategory, {})
|
||||
.then((res) => {
|
||||
categories = toIdMap(res)
|
||||
})
|
||||
|
||||
let sortedSubIssues: WithLookup<Issue>[] = []
|
||||
|
||||
$: {
|
||||
if (subIssues !== undefined) {
|
||||
subIssues.sort(
|
||||
(a, b) =>
|
||||
listIssueStatusOrder.indexOf($statusStore.get(a.status)?.category ?? tracker.issueStatusCategory.Backlog) -
|
||||
listIssueStatusOrder.indexOf($statusStore.get(b.status)?.category ?? tracker.issueStatusCategory.Backlog)
|
||||
)
|
||||
sortedSubIssues = subIssues ?? []
|
||||
}
|
||||
}
|
||||
|
||||
$: subIssueValue = sortedSubIssues.map((iss) => {
|
||||
const project = iss.$lookup?.space
|
||||
const status = iss.$lookup?.status as WithLookup<IssueStatus>
|
||||
const icon = status.$lookup?.category?.icon
|
||||
const color = status.color ?? status.$lookup?.category?.color
|
||||
|
||||
const c = $statusStore.get(iss.status)?.category
|
||||
const category = c !== undefined ? categories.get(c) : undefined
|
||||
|
||||
return {
|
||||
id: iss._id,
|
||||
icon,
|
||||
isSelected: iss._id === issue._id,
|
||||
...(project !== undefined ? { text: `${getIssueId(project, iss)} ${iss.title}` } : undefined),
|
||||
...(color !== undefined ? { iconColor: getPlatformColorDef(color, $themeStore.dark).icon } : undefined),
|
||||
category:
|
||||
category !== undefined
|
||||
? {
|
||||
label: category.label,
|
||||
icon: category.icon
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if parentIssue}
|
||||
@@ -152,8 +155,15 @@
|
||||
<div
|
||||
bind:this={subIssuesElement}
|
||||
class="flex-center sub-issues cursor-pointer"
|
||||
use:tooltip={{ label: tracker.string.OpenSubIssues, direction: 'bottom' }}
|
||||
on:click|preventDefault={showSubIssues}
|
||||
use:tooltip={{
|
||||
component: SelectPopup,
|
||||
props: {
|
||||
value: subIssueValue,
|
||||
onSelect: openIssue,
|
||||
showShadow: false,
|
||||
width: 'large'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span class="overflow-label">{subIssues?.length}</span>
|
||||
<div class="ml-2">
|
||||
|
||||
@@ -13,23 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, SortingOrder, Status, WithLookup } from '@hcengineering/core'
|
||||
import { createQuery } from '@hcengineering/presentation'
|
||||
import core, { IdMap, Ref, SortingOrder, Status, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
|
||||
import { createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { Issue, Project } from '@hcengineering/tracker'
|
||||
import {
|
||||
Button,
|
||||
ButtonKind,
|
||||
ButtonSize,
|
||||
ProgressCircle,
|
||||
SelectPopup,
|
||||
closeTooltip,
|
||||
showPanel,
|
||||
showPopup
|
||||
} from '@hcengineering/ui'
|
||||
import { Button, ButtonKind, ButtonSize, ProgressCircle, SelectPopup, showPanel } from '@hcengineering/ui'
|
||||
import { statusStore } from '@hcengineering/view-resources'
|
||||
import { getIssueId } from '../../../issues'
|
||||
import tracker from '../../../plugin'
|
||||
import { subIssueListProvider } from '../../../utils'
|
||||
import { listIssueStatusOrder, subIssueListProvider } from '../../../utils'
|
||||
import IssueStatusIcon from '../IssueStatusIcon.svelte'
|
||||
|
||||
export let value: WithLookup<Issue>
|
||||
@@ -46,6 +37,7 @@
|
||||
$: project = currentProject
|
||||
|
||||
let subIssues: Issue[] = []
|
||||
let _subIssues: Issue[] = []
|
||||
let countComplete: number = 0
|
||||
|
||||
const projectQuery = createQuery()
|
||||
@@ -64,11 +56,18 @@
|
||||
|
||||
$: update(value)
|
||||
|
||||
let categories: IdMap<StatusCategory> = new Map()
|
||||
|
||||
getClient()
|
||||
.findAll(core.class.StatusCategory, {})
|
||||
.then((res) => {
|
||||
categories = toIdMap(res)
|
||||
})
|
||||
|
||||
function update (value: WithLookup<Issue>): void {
|
||||
if (value.$lookup?.subIssues !== undefined) {
|
||||
query.unsubscribe()
|
||||
subIssues = value.$lookup.subIssues as Issue[]
|
||||
subIssues.sort((a, b) => (a.rank ?? '').localeCompare(b.rank ?? ''))
|
||||
} else if (value.subIssues > 0) {
|
||||
query.query(tracker.class.Issue, { attachedTo: value._id }, (res) => (subIssues = res), {
|
||||
sort: { rank: SortingOrder.Ascending }
|
||||
@@ -101,43 +100,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
function showSubIssues () {
|
||||
if (subIssues) {
|
||||
closeTooltip()
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{
|
||||
value: subIssues.map((iss) => {
|
||||
const text = project ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
|
||||
return {
|
||||
id: iss._id,
|
||||
text,
|
||||
isSelected: iss._id === value._id,
|
||||
icon: IssueStatusIcon,
|
||||
iconProps: {
|
||||
value: $statusStore.get(iss.status),
|
||||
size: 'small',
|
||||
fill: undefined
|
||||
}
|
||||
}
|
||||
}),
|
||||
width: 'large'
|
||||
},
|
||||
{
|
||||
getBoundingClientRect: () => {
|
||||
const rect = btn.getBoundingClientRect()
|
||||
const offsetX = 0
|
||||
const offsetY = 0
|
||||
|
||||
return DOMRect.fromRect({ width: 1, height: 1, x: rect.left + offsetX, y: rect.bottom + offsetY })
|
||||
}
|
||||
},
|
||||
(selectedIssue) => {
|
||||
selectedIssue !== undefined && openIssue(selectedIssue)
|
||||
}
|
||||
)
|
||||
}
|
||||
$: {
|
||||
subIssues.sort(
|
||||
(a, b) =>
|
||||
listIssueStatusOrder.indexOf($statusStore.get(a.status)?.category ?? tracker.issueStatusCategory.Backlog) -
|
||||
listIssueStatusOrder.indexOf($statusStore.get(b.status)?.category ?? tracker.issueStatusCategory.Backlog)
|
||||
)
|
||||
_subIssues = subIssues
|
||||
}
|
||||
|
||||
$: subIssuesValue = _subIssues.map((iss) => {
|
||||
const text = project ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
|
||||
const c = $statusStore.get(iss.status)?.category
|
||||
const category = c !== undefined ? categories.get(c) : undefined
|
||||
return {
|
||||
id: iss._id,
|
||||
text,
|
||||
isSelected: iss._id === value._id,
|
||||
icon: IssueStatusIcon,
|
||||
iconProps: {
|
||||
value: $statusStore.get(iss.status),
|
||||
size: 'small',
|
||||
fill: undefined
|
||||
},
|
||||
category:
|
||||
category !== undefined
|
||||
? {
|
||||
label: category.label,
|
||||
icon: category.icon
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
{#if hasSubIssues}
|
||||
@@ -147,9 +141,14 @@
|
||||
{kind}
|
||||
{size}
|
||||
{justify}
|
||||
on:click={(ev) => {
|
||||
ev.stopPropagation()
|
||||
if (subIssues) showSubIssues()
|
||||
showTooltip={{
|
||||
component: SelectPopup,
|
||||
props: {
|
||||
value: subIssuesValue,
|
||||
onSelect: openIssue,
|
||||
showShadow: false,
|
||||
width: 'large'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svelte:fragment slot="content">
|
||||
|
||||
Reference in New Issue
Block a user