mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 17:05:16 +03:00
UBER-1182: Fix task type categories (#4222)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
5029c18f02
commit
5062febad9
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
}
|
||||
})
|
||||
|
@ -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}
|
||||
|
@ -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])
|
||||
|
@ -109,6 +109,14 @@ export interface TaskTypeDescriptor extends Doc {
|
||||
allowCreate: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface TaskStatusFactory {
|
||||
category: Ref<StatusCategory>
|
||||
statuses: (string | [string, number])[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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>
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
/>
|
||||
|
@ -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 }
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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 ?? []
|
||||
|
@ -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 {
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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 (
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
@ -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> {
|
||||
|
Loading…
Reference in New Issue
Block a user