mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-24 02:25:00 +02:00
Tracker: move "IssueStatus" enum into model (#1449)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
@@ -1,8 +1,22 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { IssueStatus, Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import Issues from './Issues.svelte'
|
||||
import { Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import tracker from '../../plugin'
|
||||
import Issues from './Issues.svelte'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
|
||||
@@ -12,6 +26,6 @@
|
||||
<Issues
|
||||
{currentSpace}
|
||||
{completedIssuesPeriod}
|
||||
includedGroups={{ status: [IssueStatus.InProgress, IssueStatus.Todo] }}
|
||||
includedGroups={{ status: [tracker.issueStatusCategory.Unstarted, tracker.issueStatusCategory.Started] }}
|
||||
title={tracker.string.ActiveIssues}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { IssueStatus, Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import Issues from './Issues.svelte'
|
||||
import { Team, IssuesDateModificationPeriod } from '@anticrm/tracker'
|
||||
import tracker from '../../plugin'
|
||||
import Issues from './Issues.svelte'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
|
||||
@@ -13,5 +27,5 @@
|
||||
title={tracker.string.BacklogIssues}
|
||||
{currentSpace}
|
||||
{completedIssuesPeriod}
|
||||
includedGroups={{ status: [IssueStatus.Backlog] }}
|
||||
includedGroups={{ status: [tracker.issueStatusCategory.Backlog] }}
|
||||
/>
|
||||
|
||||
@@ -1,65 +1,59 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { Class, Doc, FindOptions, Ref, WithLookup } from '@anticrm/core'
|
||||
import { Kanban } from '@anticrm/kanban'
|
||||
import { Class, Doc, FindOptions, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||
import { Kanban, TypeState } from '@anticrm/kanban'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { Button, Component, eventToHTMLElement, Icon, IconAdd, IconMoreH, showPopup, Tooltip } from '@anticrm/ui'
|
||||
import view from '@anticrm/view'
|
||||
import { Issue, Team } from '@anticrm/tracker'
|
||||
import { Button, eventToHTMLElement, Icon, IconAdd, showPopup, Tooltip } from '@anticrm/ui'
|
||||
import { focusStore, ListSelectionProvider, selectionStore } from '@anticrm/view-resources'
|
||||
import ActionContext from '@anticrm/view-resources/src/components/ActionContext.svelte'
|
||||
import Menu from '@anticrm/view-resources/src/components/Menu.svelte'
|
||||
import { onMount } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
import CreateIssue from '../CreateIssue.svelte'
|
||||
import AssigneePresenter from './AssigneePresenter.svelte';
|
||||
import AssigneePresenter from './AssigneePresenter.svelte'
|
||||
import IssuePresenter from './IssuePresenter.svelte'
|
||||
import PriorityPresenter from './PriorityPresenter.svelte';
|
||||
import PriorityPresenter from './PriorityPresenter.svelte'
|
||||
|
||||
export let currentSpace: Ref<Team>
|
||||
export let baseMenuClass: Ref<Class<Doc>> | undefined = undefined
|
||||
|
||||
const states = [
|
||||
{
|
||||
_id: IssueStatus.Backlog,
|
||||
title: 'Backlog',
|
||||
color: 0,
|
||||
icon: tracker.icon.StatusBacklog
|
||||
},
|
||||
{
|
||||
_id: IssueStatus.InProgress,
|
||||
title: 'In progress',
|
||||
color: 1,
|
||||
icon: tracker.icon.StatusInProgress
|
||||
},
|
||||
{
|
||||
_id: IssueStatus.Todo,
|
||||
title: 'To do',
|
||||
color: 2,
|
||||
icon: tracker.icon.StatusTodo
|
||||
},
|
||||
{
|
||||
_id: IssueStatus.Done,
|
||||
title: 'Done',
|
||||
color: 3,
|
||||
icon: tracker.icon.StatusDone
|
||||
},
|
||||
{
|
||||
_id: IssueStatus.Canceled,
|
||||
title: 'Canceled',
|
||||
color: 4,
|
||||
icon: tracker.icon.StatusCanceled
|
||||
}
|
||||
]
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const spaceQuery = createQuery()
|
||||
const statusesQuery = createQuery()
|
||||
|
||||
let currentTeam: Team | undefined
|
||||
|
||||
$: spaceQuery.query(tracker.class.Team, { _id: currentSpace }, (res) => {
|
||||
currentTeam = res.shift()
|
||||
})
|
||||
|
||||
let states: TypeState[] | undefined
|
||||
$: statusesQuery.query(tracker.class.IssueStatus, { attachedTo: currentSpace }, (issueStatuses) => {
|
||||
states = issueStatuses.map((status) => ({
|
||||
_id: status._id,
|
||||
title: status.name,
|
||||
color: status.color ?? status.$lookup?.category?.color ?? 0
|
||||
}))
|
||||
}, {
|
||||
lookup: { category: tracker.class.IssueStatusCategory },
|
||||
sort: { rank: SortingOrder.Ascending }
|
||||
})
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
/* eslint-disable no-unused-vars */
|
||||
let issue: Issue
|
||||
@@ -107,7 +101,7 @@ import PriorityPresenter from './PriorityPresenter.svelte';
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if currentTeam}
|
||||
{#if currentTeam && states}
|
||||
<ActionContext
|
||||
context={{
|
||||
mode: 'browser'
|
||||
@@ -141,7 +135,7 @@ import PriorityPresenter from './PriorityPresenter.svelte';
|
||||
on:contextmenu={(evt) => showMenu(evt.detail.evt, evt.detail.objects)}
|
||||
>
|
||||
<svelte:fragment slot="header" let:state let:count>
|
||||
<div class="header flex-col">
|
||||
<div class="header flex-col">
|
||||
<div class="flex-between label font-medium w-full h-full mb-4">
|
||||
<div class="flex-row-center gap-2">
|
||||
<Icon icon={state.icon} size={'small'} />
|
||||
@@ -188,10 +182,6 @@ import PriorityPresenter from './PriorityPresenter.svelte';
|
||||
min-height: 6rem;
|
||||
user-select: none;
|
||||
|
||||
.filter {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--theme-caption-color);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
|
||||
@@ -1,7 +1,21 @@
|
||||
<!--
|
||||
// Copyright © 2022 Hardcore Engineering Inc.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import { DocumentQuery, FindOptions, Ref } from '@anticrm/core'
|
||||
import { Issue, Team } from '@anticrm/tracker'
|
||||
import { DocumentQuery, FindOptions, Ref, WithLookup } from '@anticrm/core'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { Component, Button, eventToHTMLElement, IconAdd, Scroller, showPopup, Tooltip } from '@anticrm/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import tracker from '../../plugin'
|
||||
@@ -12,13 +26,15 @@
|
||||
export let query: DocumentQuery<Issue>
|
||||
export let groupBy: { key: IssuesGroupByKeys | undefined; group: Issue[IssuesGroupByKeys] | undefined }
|
||||
export let orderBy: IssuesOrderByKeys
|
||||
export let statuses: WithLookup<IssueStatus>[]
|
||||
export let currentSpace: Ref<Team> | undefined = undefined
|
||||
export let currentTeam: Team
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const options: FindOptions<Issue> = {
|
||||
lookup: {
|
||||
assignee: contact.class.Employee
|
||||
assignee: contact.class.Employee,
|
||||
status: tracker.class.IssueStatus
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +62,8 @@
|
||||
isEditable: false,
|
||||
shouldShowLabel: true,
|
||||
value: grouping,
|
||||
defaultName: groupBy.key === 'assignee' ? tracker.string.NoAssignee : undefined
|
||||
defaultName: groupBy.key === 'assignee' ? tracker.string.NoAssignee : undefined,
|
||||
statuses: groupBy.key === 'status' ? statuses : undefined
|
||||
}}
|
||||
/>
|
||||
<span class="eLabelCounter ml-2">{issuesAmount}</span>
|
||||
@@ -64,7 +81,7 @@
|
||||
itemsConfig={[
|
||||
{ key: '', presenter: tracker.component.PriorityPresenter, props: { currentSpace } },
|
||||
{ key: '', presenter: tracker.component.IssuePresenter, props: { currentTeam } },
|
||||
{ key: '', presenter: tracker.component.StatusPresenter, props: { currentSpace } },
|
||||
{ key: '', presenter: tracker.component.StatusPresenter, props: { currentSpace, statuses } },
|
||||
{ key: '', presenter: tracker.component.TitlePresenter, props: { shouldUseMargin: true } },
|
||||
{ key: '', presenter: tracker.component.DueDatePresenter, props: { currentSpace } },
|
||||
{ key: 'modifiedOn', presenter: tracker.component.ModificationDatePresenter },
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref, Timestamp } from '@anticrm/core'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { Ref, Timestamp, WithLookup } from '@anticrm/core'
|
||||
import { Issue, Team } from '@anticrm/tracker'
|
||||
import { DatePresenter, Tooltip, getDaysDifference } from '@anticrm/ui'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import DueDatePopup from './DueDatePopup.svelte'
|
||||
import tracker from '../../plugin'
|
||||
|
||||
export let value: Issue
|
||||
export let value: WithLookup<Issue>
|
||||
export let currentSpace: Ref<Team> | undefined = undefined
|
||||
|
||||
const WARNING_DAYS = 7
|
||||
@@ -64,7 +64,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: shouldRenderPresenter = dueDateMs && value.status !== IssueStatus.Done && value.status !== IssueStatus.Canceled
|
||||
$: shouldRenderPresenter =
|
||||
dueDateMs &&
|
||||
value.$lookup?.status?.category !== tracker.issueStatusCategory.Completed &&
|
||||
value.$lookup?.status?.category !== tracker.issueStatusCategory.Canceled
|
||||
</script>
|
||||
|
||||
{#if shouldRenderPresenter}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import contact from '@anticrm/contact'
|
||||
import type { DocumentQuery, Ref } from '@anticrm/core'
|
||||
import { DocumentQuery, Ref, SortingOrder, WithLookup } from '@anticrm/core'
|
||||
import { createQuery } from '@anticrm/presentation'
|
||||
import {
|
||||
Issue,
|
||||
@@ -22,7 +22,8 @@
|
||||
IssuesGrouping,
|
||||
IssuesOrdering,
|
||||
IssuesDateModificationPeriod,
|
||||
IssueStatus
|
||||
IssueStatus,
|
||||
IssueStatusCategory
|
||||
} from '@anticrm/tracker'
|
||||
import { Button, Label, ScrollBox, IconOptions, showPopup, eventToHTMLElement } from '@anticrm/ui'
|
||||
import CategoryPresenter from './CategoryPresenter.svelte'
|
||||
@@ -50,9 +51,11 @@
|
||||
const ENTRIES_LIMIT = 200
|
||||
const spaceQuery = createQuery()
|
||||
const issuesQuery = createQuery()
|
||||
const statusesQuery = createQuery()
|
||||
const issuesMap: { [status: string]: number } = {}
|
||||
let currentTeam: Team | undefined
|
||||
let issues: Issue[] = []
|
||||
let statusesById: ReadonlyMap<Ref<IssueStatus>, WithLookup<IssueStatus>> = new Map()
|
||||
|
||||
$: totalIssues = getTotalIssues(issuesMap)
|
||||
|
||||
@@ -71,25 +74,41 @@
|
||||
|
||||
$: groupByKey = issuesGroupKeyMap[groupingKey]
|
||||
$: categories = getCategories(groupByKey, issues, !!shouldShowEmptyGroups)
|
||||
$: displayedCategories = (categories as any[]).filter((x: ReturnType<typeof getCategories>) => {
|
||||
return (
|
||||
groupByKey === undefined || includedGroups[groupByKey] === undefined || includedGroups[groupByKey]?.includes(x)
|
||||
)
|
||||
})
|
||||
$: includedIssuesQuery = getIncludedIssuesQuery(includedGroups)
|
||||
$: filteredIssuesQuery = getModifiedOnIssuesFilterQuery(issues, completedIssuesPeriod)
|
||||
$: displayedCategories = (categories as any[]).filter((x) => {
|
||||
if (groupByKey === undefined || includedGroups[groupByKey] === undefined) {
|
||||
return true
|
||||
}
|
||||
|
||||
const getIncludedIssuesQuery = (groups: Partial<Record<IssuesGroupByKeys, Array<any>>>) => {
|
||||
if (groupByKey === 'status') {
|
||||
const category = statusesById.get(x as Ref<IssueStatus>)?.category
|
||||
|
||||
return !!(category && includedGroups.status?.includes(category))
|
||||
}
|
||||
|
||||
return includedGroups[groupByKey]?.includes(x)
|
||||
})
|
||||
$: includedIssuesQuery = getIncludedIssuesQuery(includedGroups, statuses)
|
||||
$: filteredIssuesQuery = getModifiedOnIssuesFilterQuery(issues, completedIssuesPeriod)
|
||||
$: statuses = [...statusesById.values()]
|
||||
|
||||
const getIncludedIssuesQuery = (
|
||||
groups: Partial<Record<IssuesGroupByKeys, Array<any>>>,
|
||||
issueStatuses: IssueStatus[]
|
||||
) => {
|
||||
const resultMap: { [p: string]: { $in: any[] } } = {}
|
||||
|
||||
for (const [key, value] of Object.entries(groups)) {
|
||||
resultMap[key] = { $in: value }
|
||||
const includedCategories = key === 'status' ? filterIssueStatuses(issueStatuses, value) : value
|
||||
resultMap[key] = { $in: includedCategories }
|
||||
}
|
||||
|
||||
return resultMap
|
||||
}
|
||||
|
||||
const getModifiedOnIssuesFilterQuery = (currentIssues: Issue[], period: IssuesDateModificationPeriod | null) => {
|
||||
const getModifiedOnIssuesFilterQuery = (
|
||||
currentIssues: WithLookup<Issue>[],
|
||||
period: IssuesDateModificationPeriod | null
|
||||
) => {
|
||||
const filter: { _id: { $in: Array<Ref<Issue>> } } = { _id: { $in: [] } }
|
||||
|
||||
if (!period || period === IssuesDateModificationPeriod.All) {
|
||||
@@ -97,7 +116,10 @@
|
||||
}
|
||||
|
||||
for (const issue of currentIssues) {
|
||||
if (issue.status === IssueStatus.Done && issue.modifiedOn < getIssuesModificationDatePeriodTime(period)) {
|
||||
if (
|
||||
issue.$lookup?.status?.category === tracker.issueStatusCategory.Completed &&
|
||||
issue.modifiedOn < getIssuesModificationDatePeriodTime(period)
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -116,6 +138,18 @@
|
||||
{ limit: ENTRIES_LIMIT, lookup: { assignee: contact.class.Employee } }
|
||||
)
|
||||
|
||||
$: statusesQuery.query(
|
||||
tracker.class.IssueStatus,
|
||||
{ attachedTo: currentSpace },
|
||||
(issueStatuses) => {
|
||||
statusesById = new Map(issueStatuses.map((status) => [status._id, status]))
|
||||
},
|
||||
{
|
||||
lookup: { category: tracker.class.IssueStatusCategory },
|
||||
sort: { rank: SortingOrder.Ascending }
|
||||
}
|
||||
)
|
||||
|
||||
const getCategories = (key: IssuesGroupByKeys | undefined, elements: Issue[], shouldShowAll: boolean) => {
|
||||
if (!key) {
|
||||
return [undefined] // No grouping
|
||||
@@ -132,6 +166,15 @@
|
||||
return shouldShowAll ? defaultIssueCategories[key] ?? existingCategories : existingCategories
|
||||
}
|
||||
|
||||
function filterIssueStatuses (
|
||||
issueStatuses: IssueStatus[],
|
||||
issueStatusCategories: Ref<IssueStatusCategory>[]
|
||||
): Ref<IssueStatus>[] {
|
||||
const statusCategories = new Set(issueStatusCategories)
|
||||
|
||||
return issueStatuses.filter((status) => statusCategories.has(status.category)).map((s) => s._id)
|
||||
}
|
||||
|
||||
const getTotalIssues = (map: { [status: string]: number }) => {
|
||||
let total = 0
|
||||
|
||||
@@ -197,6 +240,7 @@
|
||||
groupBy={{ key: groupByKey, group: category }}
|
||||
orderBy={issuesOrderKeyMap[orderingKey]}
|
||||
query={resultQuery}
|
||||
{statuses}
|
||||
{currentSpace}
|
||||
{currentTeam}
|
||||
on:content={(event) => {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
// limitations under the License.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { Ref } from '@anticrm/core'
|
||||
import { Ref, WithLookup } from '@anticrm/core'
|
||||
import { Issue, IssueStatus, Team } from '@anticrm/tracker'
|
||||
import { getClient } from '@anticrm/presentation'
|
||||
import { Tooltip } from '@anticrm/ui'
|
||||
@@ -21,13 +21,14 @@
|
||||
import StatusSelector from '../StatusSelector.svelte'
|
||||
|
||||
export let value: Issue
|
||||
export let statuses: WithLookup<IssueStatus>[]
|
||||
export let currentSpace: Ref<Team> | undefined = undefined
|
||||
export let isEditable: boolean = true
|
||||
export let shouldShowLabel: boolean = false
|
||||
|
||||
const client = getClient()
|
||||
|
||||
const handleStatusChanged = async (newStatus: IssueStatus | undefined) => {
|
||||
const handleStatusChanged = async (newStatus: Ref<IssueStatus> | undefined) => {
|
||||
if (!isEditable || newStatus === undefined) {
|
||||
return
|
||||
}
|
||||
@@ -49,7 +50,8 @@
|
||||
kind={'icon'}
|
||||
{isEditable}
|
||||
{shouldShowLabel}
|
||||
status={value.status}
|
||||
{statuses}
|
||||
selectedStatusId={value.status}
|
||||
onStatusChange={handleStatusChanged}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -58,7 +60,8 @@
|
||||
kind={'icon'}
|
||||
{isEditable}
|
||||
{shouldShowLabel}
|
||||
status={value.status}
|
||||
{statuses}
|
||||
selectedStatusId={value.status}
|
||||
onStatusChange={handleStatusChanged}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user