UBER-1182: Fix task type categories (#4222)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-12-20 13:29:31 +07:00 committed by GitHub
parent 5029c18f02
commit 5062febad9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 382 additions and 478 deletions

View File

@ -64,6 +64,7 @@ import { getEmbeddedLabel, type Asset, type IntlString } from '@hcengineering/pl
import setting from '@hcengineering/setting'
import tags from '@hcengineering/tags'
import {
type TaskStatusFactory,
calculateStatuses,
findStatusAttr,
type KanbanCard,
@ -489,7 +490,7 @@ export function createModel (builder: Builder): void {
ofAttribute: task.attribute.State,
label: task.string.StateBacklog,
icon: task.icon.TaskState,
color: PaletteColorIndexes.Coin,
color: PaletteColorIndexes.Cloud,
defaultStatusName: 'Backlog',
order: 0
},
@ -503,7 +504,7 @@ export function createModel (builder: Builder): void {
ofAttribute: task.attribute.State,
label: task.string.StateActive,
icon: task.icon.TaskState,
color: PaletteColorIndexes.Blueberry,
color: PaletteColorIndexes.Porpoise,
defaultStatusName: 'New state',
order: 0
},
@ -517,7 +518,7 @@ export function createModel (builder: Builder): void {
ofAttribute: task.attribute.State,
label: task.string.DoneStatesWon,
icon: task.icon.TaskState,
color: PaletteColorIndexes.Houseplant,
color: PaletteColorIndexes.Grass,
defaultStatusName: 'Won',
order: 0
},
@ -531,7 +532,7 @@ export function createModel (builder: Builder): void {
ofAttribute: task.attribute.State,
label: task.string.DoneStatesLost,
icon: task.icon.TaskState,
color: PaletteColorIndexes.Firework,
color: PaletteColorIndexes.Coin,
defaultStatusName: 'Lost',
order: 0
},
@ -602,7 +603,10 @@ export function createModel (builder: Builder): void {
/**
* @public
*/
export type FixTaskData = Omit<Data<TaskType>, 'space' | 'statuses' | 'parent'> & { _id?: TaskType['_id'] }
export type FixTaskData = Omit<Data<TaskType>, 'space' | 'statuses' | 'statusCategories' | 'parent'> & {
_id?: TaskType['_id']
statusCategories: TaskType['statusCategories'] | TaskStatusFactory[]
}
export interface FixTaskResult {
taskTypes: TaskType[]
projectTypes: ProjectType[]
@ -694,35 +698,74 @@ export async function fixTaskTypes (
const statusAttr = findStatusAttr(client.hierarchy, data.ofClass)
// Ensure we have at leas't one item in every category.
for (const c of data.statusCategories) {
const cat = await client.model.findOne(core.class.StatusCategory, { _id: c })
const st = statuses.find((it) => it.category === c)
if (st === undefined) {
// We need to add new status into missing category
const statusId: Ref<Status> = generateId()
await client.create<Status>(DOMAIN_STATUS, {
_id: statusId,
_class: data.statusClass,
category: c,
modifiedBy: core.account.ConfigUser,
modifiedOn: Date.now(),
name: cat?.defaultStatusName ?? 'New state',
space: task.space.Statuses,
ofAttribute: statusAttr._id
})
dStatuses.push(statusId)
const category = typeof c === 'string' ? c : c.category
const cat = await client.model.findOne(core.class.StatusCategory, { _id: category })
await client.update(
DOMAIN_SPACE,
{
_id: t._id
},
{ $push: { statuses: { _id: statusId } } }
)
t.statuses.push({ _id: statusId, taskType: taskTypeId })
const st = statuses.find((it) => it.category === category)
const newStatuses: Ref<Status>[] = []
if (st === undefined) {
if (typeof c === 'string') {
// We need to add new status into missing category
const statusId: Ref<Status> = generateId()
await client.create<Status>(DOMAIN_STATUS, {
_id: statusId,
_class: data.statusClass,
category,
modifiedBy: core.account.ConfigUser,
modifiedOn: Date.now(),
name: cat?.defaultStatusName ?? 'New state',
space: task.space.Statuses,
ofAttribute: statusAttr._id
})
newStatuses.push(statusId)
dStatuses.push(statusId)
await client.update(
DOMAIN_SPACE,
{
_id: t._id
},
{ $push: { statuses: newStatuses.map((it) => ({ _id: it })) } }
)
t.statuses.push(...newStatuses.map((it) => ({ _id: it, taskType: taskTypeId })))
} else {
for (const sts of c.statuses) {
const stsName = Array.isArray(sts) ? sts[0] : sts
const color = Array.isArray(sts) ? sts[1] : undefined
const st = statuses.find((it) => it.name.toLowerCase() === stsName.toLowerCase())
if (st === undefined) {
// We need to add new status into missing category
const statusId: Ref<Status> = generateId()
await client.create<Status>(DOMAIN_STATUS, {
_id: statusId,
_class: data.statusClass,
category,
modifiedBy: core.account.ConfigUser,
modifiedOn: Date.now(),
name: stsName,
color,
space: task.space.Statuses,
ofAttribute: statusAttr._id
})
newStatuses.push(statusId)
dStatuses.push(statusId)
}
await client.update(
DOMAIN_SPACE,
{
_id: t._id
},
{ $push: { statuses: newStatuses.map((it) => ({ _id: it })) } }
)
t.statuses.push(...newStatuses.map((it) => ({ _id: it, taskType: taskTypeId })))
}
}
}
}
const taskType: TaskType = {
...data,
statusCategories: data.statusCategories.map((it) => (typeof it === 'string' ? it : it.category)),
parent: t._id,
_id: taskTypeId,
_class: task.class.TaskType,

View File

@ -14,21 +14,20 @@
//
import activity from '@hcengineering/activity'
import chunter from '@hcengineering/chunter'
import { type Builder } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
import task from '@hcengineering/model-task'
import view from '@hcengineering/model-view'
import workbench from '@hcengineering/model-workbench'
import notification from '@hcengineering/notification'
import setting from '@hcengineering/setting'
import { trackerId } from '@hcengineering/tracker'
import { generateClassNotificationTypes } from '@hcengineering/model-notification'
import presentation from '@hcengineering/model-presentation'
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
import chunter from '@hcengineering/chunter'
import tracker from './plugin'
import { createActions as defineActions } from './actions'
import tracker from './plugin'
import { definePresenters } from './presenters'
import {
TComponent,
@ -130,78 +129,6 @@ function defineNotifications (builder: Builder): void {
)
}
function defineStatusCategories (builder: Builder): void {
builder.createDoc(
core.class.StatusCategory,
core.space.Model,
{
ofAttribute: tracker.attribute.IssueStatus,
label: tracker.string.CategoryBacklog,
icon: tracker.icon.CategoryBacklog,
color: PaletteColorIndexes.Cloud,
defaultStatusName: 'Backlog',
order: 0
},
tracker.issueStatusCategory.Backlog
)
builder.createDoc(
core.class.StatusCategory,
core.space.Model,
{
ofAttribute: tracker.attribute.IssueStatus,
label: tracker.string.CategoryUnstarted,
icon: tracker.icon.CategoryUnstarted,
color: PaletteColorIndexes.Porpoise,
defaultStatusName: 'Todo',
order: 1
},
tracker.issueStatusCategory.Unstarted
)
builder.createDoc(
core.class.StatusCategory,
core.space.Model,
{
ofAttribute: tracker.attribute.IssueStatus,
label: tracker.string.CategoryStarted,
icon: tracker.icon.CategoryStarted,
color: PaletteColorIndexes.Cerulean,
defaultStatusName: 'In Progress',
order: 2
},
tracker.issueStatusCategory.Started
)
builder.createDoc(
core.class.StatusCategory,
core.space.Model,
{
ofAttribute: tracker.attribute.IssueStatus,
label: tracker.string.CategoryCompleted,
icon: tracker.icon.CategoryCompleted,
color: PaletteColorIndexes.Grass,
defaultStatusName: 'Done',
order: 3
},
tracker.issueStatusCategory.Completed
)
builder.createDoc(
core.class.StatusCategory,
core.space.Model,
{
ofAttribute: tracker.attribute.IssueStatus,
label: tracker.string.CategoryCanceled,
icon: tracker.icon.CategoryCanceled,
color: PaletteColorIndexes.Coin,
defaultStatusName: 'Canceled',
order: 4
},
tracker.issueStatusCategory.Canceled
)
}
/**
* Define filters
*/
@ -483,8 +410,6 @@ export function createModel (builder: Builder): void {
defineViewlets(builder)
defineStatusCategories(builder)
const issuesId = 'issues'
const componentsId = 'components'
const milestonesId = 'milestones'

View File

@ -13,7 +13,15 @@
// limitations under the License.
//
import core, { SortingOrder, TxOperations, generateId, type Data, type Ref, type Status } from '@hcengineering/core'
import core, {
DOMAIN_STATUS,
SortingOrder,
TxOperations,
generateId,
type Data,
type Ref,
type Status
} from '@hcengineering/core'
import {
createOrUpdate,
tryMigrate,
@ -22,10 +30,15 @@ import {
type MigrationUpgradeClient
} from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { createProjectType, fixTaskTypes } from '@hcengineering/model-task'
import { DOMAIN_TASK, createProjectType, fixTaskTypes } from '@hcengineering/model-task'
import tags from '@hcengineering/tags'
import task, { type TaskType } from '@hcengineering/task'
import { TimeReportDayType } from '@hcengineering/tracker'
import task, { type ProjectType, type TaskType } from '@hcengineering/task'
import {
TimeReportDayType,
baseIssueTaskStatuses,
classicIssueTaskStatuses,
createStatesData
} from '@hcengineering/tracker'
import tracker from './plugin'
async function createDefaultProject (tx: TxOperations): Promise<void> {
@ -38,20 +51,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
})
if ((await tx.findOne(task.class.ProjectType, { _id: tracker.ids.ClassingProjectType })) === undefined) {
const categories = await tx.findAll(
core.class.StatusCategory,
{ ofAttribute: tracker.attribute.IssueStatus },
{ sort: { order: SortingOrder.Ascending } }
)
const states: Omit<Data<Status>, 'rank'>[] = []
for (const category of categories) {
states.push({
ofAttribute: tracker.attribute.IssueStatus,
name: category.defaultStatusName,
category: category._id
})
}
const states: Omit<Data<Status>, 'rank'>[] = createStatesData(classicIssueTaskStatuses)
await createProjectType(
tx,
{
@ -68,7 +68,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
factory: states,
ofClass: tracker.class.Issue,
targetClass: tracker.class.Issue,
statusCategories: categories.map((it) => it._id),
statusCategories: classicIssueTaskStatuses.map((it) => it.category),
statusClass: core.class.Status,
kind: 'both',
allowedAsChildOf: [tracker.taskTypes.Issue]
@ -80,26 +80,8 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
if ((await tx.findOne(task.class.ProjectType, { _id: tracker.ids.BaseProjectType })) === undefined) {
const issueId: Ref<TaskType> = generateId()
const baseCategories = [
task.statusCategory.UnStarted,
task.statusCategory.Active,
task.statusCategory.Won,
task.statusCategory.Lost
]
const categories = await tx.findAll(
core.class.StatusCategory,
{ _id: { $in: baseCategories } },
{ sort: { order: SortingOrder.Ascending } }
)
const states: Omit<Data<Status>, 'rank'>[] = []
for (const category of categories) {
states.push({
ofAttribute: tracker.attribute.IssueStatus,
name: category.defaultStatusName,
category: category._id
})
}
const states: Omit<Data<Status>, 'rank'>[] = createStatesData(baseIssueTaskStatuses)
await createProjectType(
tx,
{
@ -116,7 +98,7 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
factory: states,
ofClass: tracker.class.Issue,
targetClass: tracker.class.Issue,
statusCategories: baseCategories,
statusCategories: baseIssueTaskStatuses.map((it) => it.category),
statusClass: core.class.Status,
kind: 'both',
allowedAsChildOf: [issueId]
@ -183,13 +165,7 @@ async function fixTrackerTaskTypes (client: MigrationClient): Promise<void> {
descriptor: tracker.descriptors.Issue,
ofClass: tracker.class.Issue,
targetClass: tracker.class.Issue,
statusCategories: [
tracker.issueStatusCategory.Backlog,
tracker.issueStatusCategory.Unstarted,
tracker.issueStatusCategory.Started,
tracker.issueStatusCategory.Completed,
tracker.issueStatusCategory.Canceled
],
statusCategories: classicIssueTaskStatuses,
statusClass: tracker.class.IssueStatus,
kind: 'task',
allowedAsChildOf: [typeId]
@ -214,6 +190,112 @@ export const trackerOperation: MigrateOperation = {
)
}
},
{
state: 'migrate-category-types',
func: async (client) => {
//
await client.update<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Backlog },
{ $set: { category: task.statusCategory.UnStarted } }
)
await client.update<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Unstarted },
{ $set: { category: task.statusCategory.Active } }
)
await client.update<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Started },
{ $set: { category: task.statusCategory.Active } }
)
await client.update<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Completed },
{ $set: { category: task.statusCategory.Won } }
)
await client.update<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: tracker.issueStatusCategory.Canceled },
{ $set: { category: task.statusCategory.Lost } }
)
// We need to update Project and TaskTypes.
const projectTypes = await client.find<ProjectType>(DOMAIN_SPACE, { _class: task.class.ProjectType })
// We need to update Project and TaskTypes.
const taskTypes = await client.find<TaskType>(DOMAIN_TASK, { _class: task.class.TaskType })
const ptUpdate = new Map<Ref<ProjectType>, ProjectType>()
const ttUpdate = new Map<Ref<TaskType>, TaskType>()
for (const tt of taskTypes) {
if (tt.statusCategories.includes(tracker.issueStatusCategory.Backlog)) {
// We need to replace category
tt.statusCategories = [
task.statusCategory.UnStarted,
task.statusCategory.Active,
task.statusCategory.Won,
task.statusCategory.Lost
]
ttUpdate.set(tt._id, tt)
}
}
// We need to fix duplicate statuses per category.
const toRemove: Ref<Status>[] = []
for (const c of [
task.statusCategory.UnStarted,
task.statusCategory.Active,
task.statusCategory.Won,
task.statusCategory.Lost
]) {
const allStatuses = await client.find<Status>(
DOMAIN_STATUS,
{ _class: tracker.class.IssueStatus, category: c },
{ projection: { name: 1, _id: 1 } }
)
let idx = -1
for (const s of allStatuses) {
idx++
const sName = s.name.trim().toLowerCase()
const prev = allStatuses.findIndex((it) => it.name.trim().toLowerCase() === sName)
if (prev !== idx) {
const prevStatus = allStatuses[prev]
// We have a duplicate tasks
await client.update<Status>(DOMAIN_TASK, { status: s._id }, { $set: { status: prevStatus._id } })
for (const tt of taskTypes) {
const pos = tt.statuses.indexOf(s._id)
if (pos !== -1) {
tt.statuses[pos] = prevStatus._id
ttUpdate.set(tt._id, tt)
}
}
for (const pt of projectTypes) {
const pos = pt.statuses.findIndex((q) => q._id === s._id)
if (pos !== -1) {
pt.statuses[pos]._id = prevStatus._id
ptUpdate.set(pt._id, pt)
}
}
toRemove.push(s._id)
}
}
}
for (const v of ptUpdate.values()) {
await client.update(DOMAIN_SPACE, { _id: v._id }, { $set: { statuses: v.statuses } })
}
for (const v of ttUpdate.values()) {
await client.update(DOMAIN_TASK, { _id: v._id }, { $set: { statuses: v.statuses } })
}
}
},
{
state: 'fixTaskTypes',
func: fixTrackerTaskTypes

View File

@ -15,7 +15,7 @@
//
import { type DocUpdateMessageViewlet, type TxViewlet } from '@hcengineering/activity'
import { type ChatMessageViewlet } from '@hcengineering/chunter'
import { type Doc, type Ref } from '@hcengineering/core'
import { type StatusCategory, type Doc, type Ref } from '@hcengineering/core'
import { type ObjectSearchCategory, type ObjectSearchFactory } from '@hcengineering/model-presentation'
import { type NotificationGroup, type NotificationType } from '@hcengineering/notification'
import { mergeIds, type IntlString, type Resource } from '@hcengineering/platform'
@ -109,5 +109,14 @@ export default mergeIds(trackerId, tracker, {
DeleteProject: '' as Ref<Action<Doc, Record<string, any>>>,
DeleteProjectClean: '' as Ref<Action<Doc, Record<string, any>>>,
DeleteIssue: '' as Ref<Action<Doc, Record<string, any>>>
},
// For migration only
issueStatusCategory: {
Backlog: '' as Ref<StatusCategory>,
Unstarted: '' as Ref<StatusCategory>,
Started: '' as Ref<StatusCategory>,
Completed: '' as Ref<StatusCategory>,
Canceled: '' as Ref<StatusCategory>
}
})

View File

@ -82,13 +82,10 @@
dispatch('accent-color', color)
}
$: color =
projectState?.color !== undefined
? getPlatformColorDef(
projectState.color ?? category?.color ?? getColorNumberByText(value?.name ?? ''),
$themeStore.dark
)
: undefined
$: color = getPlatformColorDef(
projectState?.color ?? category?.color ?? getColorNumberByText(value?.name ?? ''),
$themeStore.dark
)
$: dispatchAccentColor(color)
onMount(() => {
@ -127,6 +124,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
id="{value.category}:{value.name}"
class="flex-presenter"
class:inline-presenter={inline}
class:flex-no-shrink={!shouldShowName || shrink === 0}

View File

@ -16,7 +16,7 @@
import core, { Attribute, IdMap, Ref, Status, StatusCategory, toIdMap } from '@hcengineering/core'
import { Asset } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import { ProjectStatus, ProjectType, TaskType, findStatusAttr, isTaskCategory } from '@hcengineering/task'
import { ProjectStatus, ProjectType, TaskType, findStatusAttr } from '@hcengineering/task'
import {
CircleButton,
ColorDefinition,
@ -231,7 +231,7 @@
<div
class="color"
on:click={(ev) => {
if (state.category !== undefined && isTaskCategory(state.category)) {
if (state.category !== undefined) {
selectIcon(elements[i + prevIndex], state)
} else {
onColor(state, color, elements[i + prevIndex])

View File

@ -109,6 +109,14 @@ export interface TaskTypeDescriptor extends Doc {
allowCreate: boolean
}
/**
* @public
*/
export interface TaskStatusFactory {
category: Ref<StatusCategory>
statuses: (string | [string, number])[]
}
/**
* @public
*/

View File

@ -23,7 +23,6 @@ import core, {
IdMap,
Ref,
Status,
StatusCategory,
TxOperations,
generateId,
type AnyAttribute,
@ -138,18 +137,6 @@ export async function createState<T extends Status> (
return res
}
/**
* @public
*/
export function isTaskCategory (category: Ref<StatusCategory>): boolean {
return (
category === task.statusCategory.Active ||
category === task.statusCategory.Active ||
category === task.statusCategory.Won ||
category === task.statusCategory.Lost
)
}
/**
* @public
*/

View File

@ -1,94 +0,0 @@
<script lang="ts">
import { StatusCategory } from '@hcengineering/core'
import { ColorDefinition, IconSize, getPlatformColorDef, themeStore } from '@hcengineering/ui'
import { createEventDispatcher, onMount } from 'svelte'
import tracker from '../../plugin'
import task from '@hcengineering/task'
export let size: IconSize
export let fill: number = -1
export let category: StatusCategory
export let statusIcon: {
index: number | undefined
count: number | undefined
} = { index: 0, count: 0 }
let element: SVGSVGElement
const dispatch = createEventDispatcher()
const dispatchAccentColor = (color?: ColorDefinition) => dispatch('accent-color', color)
$: color = getPlatformColorDef(fill, $themeStore.dark)
$: dispatchAccentColor(color)
onMount(() => {
dispatchAccentColor(color)
})
</script>
<svg
bind:this={element}
class="svg-{size}"
fill={color?.icon ?? 'currentColor'}
id={category._id}
style:transform={category._id === tracker.issueStatusCategory.Started ? 'rotate(-90deg)' : ''}
style:flex-shrink={0}
viewBox="0 0 16 16"
xmlns="http://www.w3.org/2000/svg"
>
{#if category._id === tracker.issueStatusCategory.Backlog || category._id === task.statusCategory.UnStarted}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M6.97975 1.07389C3.92356 1.52014 1.50831 3.94118 1.0708 7.0002H2.08287C2.50337 4.49345 4.47822 2.51374 6.9825 2.08599L6.97975 1.07389ZM8.98249 2.08014L8.97974 1.06812C12.055 1.49885 14.4896 3.92773 14.9291 7.0002H13.917C13.4945 4.48183 11.5033 2.4954 8.98249 2.08014ZM9.01467 13.9146C11.5201 13.4879 13.4962 11.5077 13.917 9.00019H14.929C14.4913 12.06 12.0748 14.4815 9.01742 14.9267L9.01467 13.9146ZM2.0829 9.0002C2.50529 11.5176 4.49522 13.5034 7.01468 13.9196L7.01743 14.9317C3.94351 14.4999 1.51022 12.0717 1.07083 9.0002H2.0829Z"
/>
{:else if category._id === tracker.issueStatusCategory.Unstarted}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z"
/>
{:else if category._id === tracker.issueStatusCategory.Started}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8 14C11.3137 14 14 11.3137 14 8C14 4.68629 11.3137 2 8 2C4.68629 2 2 4.68629 2 8C2 11.3137 4.68629 14 8 14ZM8 15C11.866 15 15 11.866 15 8C15 4.13401 11.866 1 8 1C4.13401 1 1 4.13401 1 8C1 11.866 4.13401 15 8 15Z"
/>
{#if statusIcon.count && statusIcon.index}
<path
d="M 4.5 4.5 L 9 4.5 A 4.5 4.5 0 {statusIcon.index > (statusIcon.count - 1) / 2 ? 1 : 0} 1 {Math.cos(
((2 * Math.PI) / statusIcon.count) * statusIcon.index - 0.01
) *
4.5 +
4.5} {Math.sin(((2 * Math.PI) / statusIcon.count) * statusIcon.index - 0.01) * 4.5 + 4.5} Z"
transform="translate(3.5,3.5)"
/>
{:else}
<path
d="M 4.5 4.5 L 9 4.5 A 4.5 4.5 0 1 1 {Math.cos(Math.PI - 0.01) * 4.5 + 4.5} {Math.sin(Math.PI - 0.01) * 4.5 +
4.5} Z"
transform="translate(3.5,3.5)"
/>
{/if}
{:else if category._id === tracker.issueStatusCategory.Completed}
<path
d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C15,4.1,11.9,1,8,1z M8,14c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S11.3,14,8,14z"
/>
<path
d="M10.6,6.1L7.5,9.2L5.9,7.6c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8l2,2c0.1,0.1,0.3,0.2,0.4,0.2s0.3-0.1,0.4-0.2l3.5-3.5c0.2-0.2,0.2-0.6,0-0.8S10.8,5.8,10.6,6.1z"
/>
{:else if category._id === tracker.issueStatusCategory.Canceled}
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M8,1C4.1,1,1,4.1,1,8c0,3.9,3.1,7,7,7c3.9,0,7-3.1,7-7C15,4.1,11.9,1,8,1z M8,14c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S11.3,14,8,14z"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M10.5,5.5c-0.2-0.2-0.6-0.2-0.8,0L8,7.2L6.3,5.5c-0.2-0.2-0.6-0.2-0.8,0s-0.2,0.6,0,0.8L7.2,8L5.5,9.7c-0.2,0.2-0.2,0.6,0,0.8s0.6,0.2,0.8,0L8,8.8l1.7,1.7c0.2,0.2,0.6,0.2,0.8,0c0.2-0.2,0.2-0.6,0-0.8L8.8,8l1.7-1.7C10.8,6.1,10.8,5.7,10.5,5.5z"
/>
{/if}
</svg>

View File

@ -13,18 +13,19 @@
// limitations under the License.
-->
<script lang="ts">
import { getClient } from '@hcengineering/presentation'
import tracker, { Issue } from '@hcengineering/tracker'
import { DueDatePresenter } from '@hcengineering/ui'
import { WithLookup } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import task from '@hcengineering/task'
import { Issue } from '@hcengineering/tracker'
import { DueDatePresenter } from '@hcengineering/ui'
export let value: WithLookup<Issue>
export let width: string | undefined = undefined
const client = getClient()
$: shouldIgnoreOverdue =
value.$lookup?.status?.category === tracker.issueStatusCategory.Completed ||
value.$lookup?.status?.category === tracker.issueStatusCategory.Canceled
value.$lookup?.status?.category === task.statusCategory.Won ||
value.$lookup?.status?.category === task.statusCategory.Lost
const handleDueDateChanged = async (newDueDate: number | undefined | null) => {
if (newDueDate === undefined || value.dueDate === newDueDate) {

View File

@ -14,9 +14,9 @@
-->
<script lang="ts">
import { WithLookup } from '@hcengineering/core'
import { Issue } from '@hcengineering/tracker'
import { getClient } from '@hcengineering/presentation'
import tracker from '../../plugin'
import task from '@hcengineering/task'
import { Issue } from '@hcengineering/tracker'
import { ButtonKind, ButtonSize, DueDatePresenter } from '@hcengineering/ui'
export let value: WithLookup<Issue>
@ -43,8 +43,8 @@
$: shouldRenderPresenter = dueDateMs != null
$: ignoreOverDue =
value.$lookup?.status?.category === tracker.issueStatusCategory.Completed ||
value.$lookup?.status?.category === tracker.issueStatusCategory.Canceled
value.$lookup?.status?.category === task.statusCategory.Won ||
value.$lookup?.status?.category === task.statusCategory.Lost
</script>
<DueDatePresenter

View File

@ -13,24 +13,12 @@
// limitations under the License.
-->
<script lang="ts">
import core, { Ref, StatusCategory, WithLookup } from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import {
ProjectType,
TaskType,
getProjectTypeStates,
getStates,
getTaskTypeStates,
isTaskCategory
} from '@hcengineering/task'
import { taskTypeStore, typeStore } from '@hcengineering/task-resources'
import StatePresenter from '@hcengineering/task-resources/src/components/state/StatePresenter.svelte'
import { Ref, WithLookup } from '@hcengineering/core'
import { ProjectType, TaskType } from '@hcengineering/task'
import { StatePresenter } from '@hcengineering/task-resources'
import { IssueStatus, Project } from '@hcengineering/tracker'
import { IconSize } from '@hcengineering/ui'
import { statusStore } from '@hcengineering/view-resources'
import tracker from '../../plugin'
import { activeProjects } from '../../utils'
import StatusIcon from '../icons/StatusIcon.svelte'
export let value: WithLookup<IssueStatus> | undefined
export let size: IconSize
@ -38,91 +26,15 @@
export let projectType: Ref<ProjectType> | undefined = undefined
export let taskType: Ref<TaskType> | undefined = undefined
const dynamicFillCategories = [tracker.issueStatusCategory.Started]
const client = getClient()
let category: StatusCategory | undefined
let statuses: IssueStatus[] = []
const statusIcon: {
index: number | undefined
count: number | undefined
} = { index: undefined, count: undefined }
$: _space = space !== undefined ? ($activeProjects.get(space) as Project) : undefined
$: if (value?.category === tracker.issueStatusCategory.Started) {
if (taskType !== undefined) {
statuses = getTaskTypeStates(taskType, $taskTypeStore, $statusStore.byId).filter(
(p) => p.category === tracker.issueStatusCategory.Started
)
} else if (projectType !== undefined) {
statuses = getProjectTypeStates(projectType, $typeStore, $statusStore.byId).filter(
(p) => p.category === tracker.issueStatusCategory.Started
)
} else {
statuses = getStates(_space, $typeStore, $statusStore.byId).filter(
(p) => p.category === tracker.issueStatusCategory.Started
)
}
}
async function updateCategory (
_space: Project | undefined,
status: WithLookup<IssueStatus>,
statuses: IssueStatus[]
): Promise<void> {
if (status.$lookup?.category !== undefined) {
category = status.$lookup.category
}
if (category === undefined || category._id !== value?.category) {
if (value) {
category = await client.findOne(core.class.StatusCategory, { _id: value.category })
}
}
if (value?.category !== undefined && dynamicFillCategories.includes(value?.category)) {
const index = statuses.findIndex((p) => p._id === value?._id)
if (index !== -1) {
statusIcon.index = index + 1
statusIcon.count = statuses.length + 1
} else {
statusIcon.index = undefined
statusIcon.count = undefined
}
}
}
function getViewState (type: ProjectType | undefined, state: IssueStatus): IssueStatus {
if (type === undefined) return state
const targetColor = type.statuses.find((p) => p._id === state._id)?.color
if (targetColor === undefined) return state
return {
...state,
color: targetColor
}
}
$: type = _space ? $typeStore.get(_space.type) : projectType !== undefined ? $typeStore.get(projectType) : undefined
$: viewState = value && getViewState(type, value)
$: if (value !== undefined) {
void updateCategory(_space, value, statuses)
}
$: icon = category?.icon
$: color = viewState?.color !== undefined ? viewState?.color : category !== undefined ? category.color : -1
</script>
{#if category !== undefined && isTaskCategory(category._id)}
<StatePresenter
{space}
projectType={_space?.type ?? projectType}
{taskType}
{value}
{size}
shouldShowName={false}
on:accent-color
/>
{:else if icon !== undefined && color !== undefined && category !== undefined}
<StatusIcon on:accent-color {category} {size} fill={color} {statusIcon} />
{/if}
<StatePresenter
{space}
projectType={_space?.type ?? projectType}
{taskType}
{value}
{size}
shouldShowName={false}
on:accent-color
/>

View File

@ -25,6 +25,8 @@
import tracker from '../../plugin'
import IssuesView from './IssuesView.svelte'
import task from '@hcengineering/task'
export let currentSpace: Ref<Project> | undefined = undefined
export let baseQuery: DocumentQuery<Issue> = {}
export let title: IntlString
@ -57,13 +59,9 @@
let activeStatuses: Ref<IssueStatus>[] = []
$: activeStatusQuery.query(
tracker.class.IssueStatus,
{ category: { $in: [tracker.issueStatusCategory.Unstarted, tracker.issueStatusCategory.Started] } },
(result) => {
activeStatuses = result.map(({ _id }) => _id)
}
)
$: activeStatusQuery.query(tracker.class.IssueStatus, { category: task.statusCategory.Active }, (result) => {
activeStatuses = result.map(({ _id }) => _id)
})
let active: DocumentQuery<Issue>
$: active = { status: { $in: activeStatuses }, ...spaceQuery }
@ -72,13 +70,9 @@
let backlogStatuses: Ref<IssueStatus>[] = []
let backlog: DocumentQuery<Issue> = {}
$: backlogStatusQuery.query(
tracker.class.IssueStatus,
{ category: tracker.issueStatusCategory.Backlog },
(result) => {
backlogStatuses = result.map(({ _id }) => _id)
}
)
$: backlogStatusQuery.query(tracker.class.IssueStatus, { category: task.statusCategory.UnStarted }, (result) => {
backlogStatuses = result.map(({ _id }) => _id)
})
$: backlog = { status: { $in: backlogStatuses }, ...spaceQuery }
$: queries = { all, active, backlog }

View File

@ -54,7 +54,7 @@
<div class="flex-presenter flex-gap-1-5">
{#each statuses as value, i (value._id)}
{#if value && i < 5}
{#if value != null && i < 5}
<div>
<IssueStatusIcon {space} {value} size={'small'} />
</div>

View File

@ -29,15 +29,13 @@
$: statusValue = value ? $statusStore.byId.get(value) : undefined
</script>
{#if value}
<StatusPresenter
{space}
value={statusValue}
{size}
{kind}
{colorInherit}
{accent}
on:accent-color
taskType={$selectedTaskTypeStore}
/>
{/if}
<StatusPresenter
{space}
value={statusValue}
{size}
{kind}
{colorInherit}
{accent}
on:accent-color
taskType={$selectedTaskTypeStore}
/>

View File

@ -15,6 +15,7 @@
<script lang="ts">
import core, { IdMap, Ref, SortingOrder, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import task from '@hcengineering/task'
import { Issue, IssueStatus } from '@hcengineering/tracker'
import {
Icon,
@ -101,8 +102,8 @@
const aStatus = $statusStore.byId.get(a.status)
const bStatus = $statusStore.byId.get(b.status)
const res =
listIssueStatusOrder.indexOf(aStatus?.category ?? tracker.issueStatusCategory.Backlog) -
listIssueStatusOrder.indexOf(bStatus?.category ?? tracker.issueStatusCategory.Backlog)
listIssueStatusOrder.indexOf(aStatus?.category ?? task.statusCategory.UnStarted) -
listIssueStatusOrder.indexOf(bStatus?.category ?? task.statusCategory.UnStarted)
return res
})
sortedSubIssues = subIssues ?? []

View File

@ -15,7 +15,7 @@
<script lang="ts">
import core, { IdMap, Ref, SortingOrder, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import { getStates } from '@hcengineering/task'
import task, { getStates } from '@hcengineering/task'
import { Issue, Project } from '@hcengineering/tracker'
import { Button, ButtonKind, ButtonSize, ProgressCircle, SelectPopup, showPanel } from '@hcengineering/ui'
import { statusStore } from '@hcengineering/view-resources'
@ -31,7 +31,7 @@
export let kind: ButtonKind = 'link-bordered'
export let size: ButtonSize = 'small'
export let justify: 'left' | 'center' = 'left'
export let width: string | undefined = 'min-contet'
export let width: string | undefined = 'min-content'
export let compactMode: boolean = false
let btn: HTMLElement
@ -62,7 +62,7 @@
let categories: IdMap<StatusCategory> = new Map()
getClient()
void getClient()
.findAll(core.class.StatusCategory, {})
.then((res) => {
categories = toIdMap(res)
@ -89,18 +89,15 @@
}
}
$: if (subIssues) {
$: if (subIssues != null) {
const doneStatuses = getStates(project, $typeStore, $statusStore.byId)
.filter(
(s) =>
s?.category === tracker.issueStatusCategory.Completed || s?.category === tracker.issueStatusCategory.Canceled
)
.filter((s) => s?.category === task.statusCategory.Won || s?.category === task.statusCategory.Lost)
.map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
}
$: hasSubIssues = (subIssues?.length ?? 0) > 0
function openIssue (target: Ref<Issue>) {
function openIssue (target: Ref<Issue>): void {
if (target !== value._id) {
showPanel(tracker.component.EditIssue, target, value._class, 'content')
}
@ -111,15 +108,15 @@
const aStatus = $statusStore.byId.get(a.status)
const bStatus = $statusStore.byId.get(b.status)
const res =
listIssueStatusOrder.indexOf(aStatus?.category ?? tracker.issueStatusCategory.Backlog) -
listIssueStatusOrder.indexOf(bStatus?.category ?? tracker.issueStatusCategory.Backlog)
listIssueStatusOrder.indexOf(aStatus?.category ?? task.statusCategory.UnStarted) -
listIssueStatusOrder.indexOf(bStatus?.category ?? task.statusCategory.UnStarted)
return res
})
_subIssues = subIssues
}
$: subIssuesValue = _subIssues.map((iss) => {
const text = project ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
const text = project != null ? `${getIssueId(project, iss)} ${iss.title}` : iss.title
const c = $statusStore.byId.get(iss.status)?.category
const category = c !== undefined ? categories.get(c) : undefined
return {

View File

@ -15,6 +15,7 @@
<script lang="ts">
import core, { Doc, IdMap, Ref, SortingOrder, StatusCategory, WithLookup, toIdMap } from '@hcengineering/core'
import { createQuery, getClient } from '@hcengineering/presentation'
import task from '@hcengineering/task'
import { Issue, Project } from '@hcengineering/tracker'
import { Button, ButtonKind, ButtonSize, ProgressCircle, SelectPopup, showPanel } from '@hcengineering/ui'
import { statusStore } from '@hcengineering/view-resources'
@ -40,7 +41,7 @@
$: _object = object ?? value
$: _object && update(_object)
$: _object != null && update(_object)
function update (value: WithLookup<Doc & { related: number }>): void {
if (value.$lookup?.related !== undefined) {
@ -62,7 +63,7 @@
let categories: IdMap<StatusCategory> = new Map()
getClient()
void getClient()
.findAll(core.class.StatusCategory, {})
.then((res) => {
categories = toIdMap(res)
@ -73,25 +74,22 @@
const aStatus = $statusStore.byId.get(a.status)
const bStatus = $statusStore.byId.get(b.status)
return (
listIssueStatusOrder.indexOf(aStatus?.category ?? tracker.issueStatusCategory.Backlog) -
listIssueStatusOrder.indexOf(bStatus?.category ?? tracker.issueStatusCategory.Backlog)
listIssueStatusOrder.indexOf(aStatus?.category ?? task.statusCategory.UnStarted) -
listIssueStatusOrder.indexOf(bStatus?.category ?? task.statusCategory.UnStarted)
)
})
subIssues = _subIssues
}
$: if (subIssues) {
$: if (subIssues != null) {
const doneStatuses = $statusStore.array
.filter(
(s) =>
s.category === tracker.issueStatusCategory.Completed || s.category === tracker.issueStatusCategory.Canceled
)
.filter((s) => s.category === task.statusCategory.Won || s.category === task.statusCategory.Lost)
.map((p) => p._id)
countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length
}
$: hasSubIssues = (subIssues?.length ?? 0) > 0
function openIssue (target: Ref<Issue>) {
function openIssue (target: Ref<Issue>): void {
subIssueListProvider(subIssues, target)
showPanel(tracker.component.EditIssue, target, tracker.class.Issue, 'content')
}

View File

@ -13,6 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import task from '@hcengineering/task'
import { Issue } from '@hcengineering/tracker'
import { floorFractionDigits, Label } from '@hcengineering/ui'
import { FixedColumn, statusStore } from '@hcengineering/view-resources'
@ -28,7 +29,7 @@
$: noParents = docs?.filter((it) => !ids.has(it.attachedTo))
$: rootNoBacklogIssues = noParents?.filter(
(it) => $statusStore.byId.get(it.status)?.category !== tracker.issueStatusCategory.Backlog
(it) => $statusStore.byId.get(it.status)?.category !== task.statusCategory.UnStarted
)
$: totalEstimation = floorFractionDigits(
@ -42,14 +43,14 @@
const cReported = it.childInfo.map((ct) => ct.reportedTime).reduce((a, b) => a + b, 0)
if (cEstimation !== 0) {
retEst = cEstimation
if (cat === tracker.issueStatusCategory.Completed || cat === tracker.issueStatusCategory.Canceled) {
if (cat === task.statusCategory.Won || cat === task.statusCategory.Lost) {
if (cReported < cEstimation) {
retEst = cReported
}
}
}
} else {
if (cat === tracker.issueStatusCategory.Completed || cat === tracker.issueStatusCategory.Canceled) {
if (cat === task.statusCategory.Won || cat === task.statusCategory.Lost) {
if (it.reportedTime < it.estimation) {
return it.reportedTime
}

View File

@ -18,9 +18,10 @@
import type { IntlString } from '@hcengineering/platform'
import { createQuery } from '@hcengineering/presentation'
import type { Issue, IssueStatus, Project } from '@hcengineering/tracker'
import { resolvedLocationStore, IModeSelector } from '@hcengineering/ui'
import { IModeSelector, resolvedLocationStore } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
import task from '@hcengineering/task'
import tracker from '../../plugin'
import IssuesView from '../issues/IssuesView.svelte'
@ -39,13 +40,9 @@
let activeStatuses: Ref<IssueStatus>[] = []
$: activeStatusQuery.query(
tracker.class.IssueStatus,
{ category: { $in: [tracker.issueStatusCategory.Unstarted, tracker.issueStatusCategory.Started] } },
(result) => {
activeStatuses = result.map(({ _id }) => _id)
}
)
$: activeStatusQuery.query(tracker.class.IssueStatus, { category: task.statusCategory.Active }, (result) => {
activeStatuses = result.map(({ _id }) => _id)
})
let active: DocumentQuery<Issue>
$: active = { status: { $in: activeStatuses }, ...assigned }
@ -54,13 +51,9 @@
let backlogStatuses: Ref<IssueStatus>[] = []
let backlog: DocumentQuery<Issue> = {}
$: backlogStatusQuery.query(
tracker.class.IssueStatus,
{ category: tracker.issueStatusCategory.Backlog },
(result) => {
backlogStatuses = result.map(({ _id }) => _id)
}
)
$: backlogStatusQuery.query(tracker.class.IssueStatus, { category: task.statusCategory.UnStarted }, (result) => {
backlogStatuses = result.map(({ _id }) => _id)
})
$: backlog = { status: { $in: backlogStatuses }, ...assigned }
const subscribedQuery = createQuery()

View File

@ -280,22 +280,20 @@ export const milestoneTitleMap: Record<MilestoneViewMode, IntlString> = Object.f
* @public
*/
export const listIssueStatusOrder = [
tracker.issueStatusCategory.Started,
tracker.issueStatusCategory.Unstarted,
tracker.issueStatusCategory.Backlog,
tracker.issueStatusCategory.Completed,
tracker.issueStatusCategory.Canceled
task.statusCategory.Active,
task.statusCategory.UnStarted,
task.statusCategory.Won,
task.statusCategory.Lost
] as const
/**
* @public
*/
export const listIssueKanbanStatusOrder = [
tracker.issueStatusCategory.Backlog,
tracker.issueStatusCategory.Unstarted,
tracker.issueStatusCategory.Started,
tracker.issueStatusCategory.Completed,
tracker.issueStatusCategory.Canceled
task.statusCategory.UnStarted,
task.statusCategory.Active,
task.statusCategory.Won,
task.statusCategory.Lost
] as const
export async function issueStatusSort (

View File

@ -19,6 +19,7 @@ import {
Attribute,
Class,
CollectionSize,
Data,
Doc,
DocManager,
IdMap,
@ -27,15 +28,22 @@ import {
RelatedDocument,
Space,
Status,
StatusCategory,
Timestamp,
Type,
WithLookup
} from '@hcengineering/core'
import { Asset, IntlString, Plugin, Resource, plugin } from '@hcengineering/platform'
import { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
import { ProjectTypeDescriptor, Task, Project as TaskProject, TaskType, TaskTypeDescriptor } from '@hcengineering/task'
import task, {
ProjectTypeDescriptor,
Task,
Project as TaskProject,
TaskStatusFactory,
TaskType,
TaskTypeDescriptor
} from '@hcengineering/task'
import { AnyComponent, ComponentExtensionId, Location, ResolvedLocation } from '@hcengineering/ui'
import { PaletteColorIndexes } from '@hcengineering/ui/src/colors'
import { Action, ActionCategory, IconProps } from '@hcengineering/view'
/**
@ -351,12 +359,44 @@ export class ComponentManager extends DocManager {
}
}
/**
* @public
*/
export const classicIssueTaskStatuses: TaskStatusFactory[] = [
{ category: task.statusCategory.UnStarted, statuses: [['Backlog', PaletteColorIndexes.Cloud]] },
{
category: task.statusCategory.Active,
statuses: [
['Todo', PaletteColorIndexes.Porpoise],
['In progress', PaletteColorIndexes.Cerulean]
]
},
{ category: task.statusCategory.Won, statuses: [['Done', PaletteColorIndexes.Grass]] },
{ category: task.statusCategory.Lost, statuses: [['Canceled', PaletteColorIndexes.Coin]] }
]
/**
* @public
*/
export const baseIssueTaskStatuses: TaskStatusFactory[] = [
{ category: task.statusCategory.UnStarted, statuses: [['Backlog', PaletteColorIndexes.Cloud]] },
{
category: task.statusCategory.Active,
statuses: [
['Coding', PaletteColorIndexes.Porpoise],
['Under review', PaletteColorIndexes.Cerulean]
]
},
{ category: task.statusCategory.Won, statuses: [['Done', PaletteColorIndexes.Grass]] },
{ category: task.statusCategory.Lost, statuses: [['Canceled', PaletteColorIndexes.Coin]] }
]
/**
* @public
*/
export const trackerId = 'tracker' as Plugin
export default plugin(trackerId, {
const pluginState = plugin(trackerId, {
class: {
Project: '' as Ref<Class<Project>>,
Issue: '' as Ref<Class<Issue>>,
@ -391,13 +431,6 @@ export default plugin(trackerId, {
attribute: {
IssueStatus: '' as Ref<Attribute<Status>>
},
issueStatusCategory: {
Backlog: '' as Ref<StatusCategory>,
Unstarted: '' as Ref<StatusCategory>,
Started: '' as Ref<StatusCategory>,
Completed: '' as Ref<StatusCategory>,
Canceled: '' as Ref<StatusCategory>
},
icon: {
TrackerApplication: '' as Asset,
Component: '' as Asset,
@ -506,3 +539,23 @@ export default plugin(trackerId, {
SubIssue: '' as Ref<TaskType>
}
})
export default pluginState
/**
* @public
*/
export function createStatesData (data: TaskStatusFactory[]): Omit<Data<Status>, 'rank'>[] {
const states: Omit<Data<Status>, 'rank'>[] = []
for (const category of data) {
for (const sName of category.statuses) {
states.push({
ofAttribute: pluginState.attribute.IssueStatus,
name: Array.isArray(sName) ? sName[0] : sName,
color: Array.isArray(sName) ? sName[1] : undefined,
category: category.category
})
}
}
return states
}

View File

@ -16,7 +16,7 @@
* path segment in the "$schema" field for all your Rush config files. This will ensure
* correct error-underlining and tab-completion for editors such as VS Code.
*/
"rushVersion": "5.109.1",
"rushVersion": "5.112.2",
/**
* The next field selects which package manager should be installed and determines its version.

View File

@ -155,11 +155,11 @@ export class IssuesPage extends CommonTrackerPage {
await expect(this.page.locator('div.row span', { hasText: issueName })).toHaveCount(0)
}
async checkAllIssuesInStatus (statusId: string | undefined): Promise<void> {
async checkAllIssuesInStatus (statusId?: string, statusName?: string): Promise<void> {
if (statusId === undefined) throw new Error(`Unknown status id ${statusId}`)
for await (const locator of iterateLocator(this.issuesList)) {
await expect(locator.locator('div[class*="square"] > svg')).toHaveAttribute('id', statusId)
await expect(locator.locator('div[class*="square"] > div')).toHaveAttribute('id', `${statusId}:${statusName}`)
}
}

View File

@ -220,7 +220,7 @@ test.describe('Tracker filters tests', () => {
await issuesPage.inputSearch.press('Escape')
await issuesPage.checkFilter('Status', 'is')
await issuesPage.checkAllIssuesInStatus(DEFAULT_STATUSES_ID.get(status))
await issuesPage.checkAllIssuesInStatus(DEFAULT_STATUSES_ID.get(status), status)
await issuesPage.buttonClearFilers.click()
})
}

View File

@ -24,11 +24,11 @@ export const DEFAULT_STATUSES = ['Backlog', 'Todo', 'In Progress', 'Done', 'Canc
export const DEFAULT_USER = 'Appleseed John'
export const DEFAULT_STATUSES_ID = new Map([
['Backlog', 'tracker:issueStatusCategory:Backlog'],
['Todo', 'tracker:issueStatusCategory:Unstarted'],
['In Progress', 'tracker:issueStatusCategory:Started'],
['Done', 'tracker:issueStatusCategory:Completed'],
['Canceled', 'tracker:issueStatusCategory:Canceled']
['Backlog', 'task:statusCategory:UnStarted'],
['Todo', 'task:statusCategory:Active'],
['In Progress', 'task:statusCategory:Active'],
['Done', 'task:statusCategory:Won'],
['Canceled', 'task:statusCategory:Lost']
])
export async function navigate (page: Page): Promise<void> {