Tracker: move "IssueStatus" enum into model (#1449)

Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@xored.com>
This commit is contained in:
Sergei Ogorelkov
2022-04-23 23:59:57 +07:00
committed by GitHub
parent 51687d1a2b
commit d663e383e6
25 changed files with 5840 additions and 3127 deletions
@@ -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}