Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracker: move "IssueStatus" enum into model #1449

Merged
merged 18 commits into from
Apr 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8,203 changes: 5,257 additions & 2,946 deletions common/config/rush/pnpm-lock.yaml

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions models/task/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -468,10 +468,6 @@ export function createModel (builder: Builder): void {
task.viewlet.Kanban
)

builder.mixin(task.class.DoneState, core.class.Class, view.mixin.AttributePresenter, {
haiodo marked this conversation as resolved.
Show resolved Hide resolved
presenter: task.component.DoneStatePresenter
})

builder.mixin(task.class.TodoItem, core.class.Class, view.mixin.AttributeEditor, {
editor: task.component.Todos
})
Expand Down
114 changes: 107 additions & 7 deletions models/tracker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import { Domain, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
import { Domain, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp } from '@anticrm/core'
import {
Builder,
Collection,
Expand All @@ -32,9 +32,9 @@ import {
} from '@anticrm/model'
import attachment from '@anticrm/model-attachment'
import chunter from '@anticrm/model-chunter'
import core, { DOMAIN_SPACE, TDoc, TSpace } from '@anticrm/model-core'
import { IntlString } from '@anticrm/platform'
import { Document, Issue, IssuePriority, IssueStatus, Team } from '@anticrm/tracker'
import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace } from '@anticrm/model-core'
import { Asset, IntlString } from '@anticrm/platform'
import { Document, Issue, IssuePriority, IssueStatus, IssueStatusCategory, Team } from '@anticrm/tracker'
import tracker from './plugin'

import workbench from '@anticrm/model-workbench'
Expand All @@ -44,6 +44,35 @@ export { default } from './plugin'

export const DOMAIN_TRACKER = 'tracker' as Domain

/**
* @public
*/
@Model(tracker.class.IssueStatus, core.class.AttachedDoc, DOMAIN_TRACKER)
export class TIssueStatus extends TAttachedDoc implements IssueStatus {
name!: string
description?: string
color?: number

@Prop(TypeRef(tracker.class.IssueStatusCategory), tracker.string.StatusCategory)
category!: Ref<IssueStatusCategory>

@Prop(TypeString(), tracker.string.Rank)
@Hidden()
rank!: string
}

/**
* @public
*/
@Model(tracker.class.IssueStatusCategory, core.class.Doc, DOMAIN_MODEL)
export class TIssueStatusCategory extends TDoc implements IssueStatusCategory {
label!: IntlString
icon!: Asset
color!: number
defaultStatusName!: string
order!: number
}

/**
* @public
*/
Expand All @@ -61,6 +90,12 @@ export class TTeam extends TSpace implements Team {
@Prop(TypeNumber(), tracker.string.Number)
@Hidden()
sequence!: number

@Prop(Collection(tracker.class.IssueStatus), tracker.string.IssueStatuses)
issueStatuses!: number

@Prop(TypeRef(tracker.class.IssueStatus), tracker.string.DefaultIssueStatus)
defaultIssueStatus!: Ref<IssueStatus>
}

/**
Expand All @@ -77,8 +112,8 @@ export class TIssue extends TDoc implements Issue {
@Index(IndexKind.FullText)
description!: Markup

@Prop(TypeNumber(), tracker.string.Status)
status!: IssueStatus
@Prop(TypeRef(tracker.class.IssueStatus), tracker.string.Status)
status!: Ref<IssueStatus>

@Prop(TypeNumber(), tracker.string.Priority)
priority!: IssuePriority
Expand Down Expand Up @@ -141,7 +176,72 @@ export class TDocument extends TDoc implements Document {
}

export function createModel (builder: Builder): void {
builder.createModel(TTeam, TIssue)
builder.createModel(TTeam, TIssue, TIssueStatus, TIssueStatusCategory)

builder.createDoc(
tracker.class.IssueStatusCategory,
core.space.Model,
{
label: tracker.string.CategoryBacklog,
icon: tracker.icon.CategoryBacklog,
color: 0,
defaultStatusName: 'Backlog',
order: 0
},
tracker.issueStatusCategory.Backlog
)

builder.createDoc(
tracker.class.IssueStatusCategory,
core.space.Model,
{
label: tracker.string.CategoryUnstarted,
icon: tracker.icon.CategoryUnstarted,
color: 1,
haiodo marked this conversation as resolved.
Show resolved Hide resolved
defaultStatusName: 'Todo',
order: 1
},
tracker.issueStatusCategory.Unstarted
)

builder.createDoc(
tracker.class.IssueStatusCategory,
core.space.Model,
{
label: tracker.string.CategoryStarted,
icon: tracker.icon.CategoryStarted,
color: 2,
defaultStatusName: 'In Progress',
order: 2
},
tracker.issueStatusCategory.Started
)

builder.createDoc(
tracker.class.IssueStatusCategory,
core.space.Model,
{
label: tracker.string.CategoryCompleted,
icon: tracker.icon.CategoryCompleted,
color: 3,
defaultStatusName: 'Done',
order: 3
},
tracker.issueStatusCategory.Completed
)

builder.createDoc(
tracker.class.IssueStatusCategory,
core.space.Model,
{
label: tracker.string.CategoryCanceled,
icon: tracker.icon.CategoryCanceled,
color: 4,
defaultStatusName: 'Canceled',
order: 4
},
tracker.issueStatusCategory.Canceled
)

builder.createDoc(
workbench.class.Application,
Expand Down
118 changes: 115 additions & 3 deletions models/tracker/src/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,60 @@
// limitations under the License.
//

import core, { TxOperations } from '@anticrm/core'
import core, { generateId, Ref, TxOperations } from '@anticrm/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@anticrm/model'
import { Team } from '@anticrm/tracker'
import { IssueStatus, IssueStatusCategory, Team, genRanks } from '@anticrm/tracker'
import tracker from './plugin'

enum DeprecatedIssueStatus {
Backlog,
Todo,
InProgress,
Done,
Canceled
}

interface CreateTeamIssueStatusesArgs {
tx: TxOperations
teamId: Ref<Team>
categories: IssueStatusCategory[]
defaultStatusId?: Ref<IssueStatus>
defaultCategoryId?: Ref<IssueStatusCategory>
}

const categoryByDeprecatedIssueStatus = {
[DeprecatedIssueStatus.Backlog]: tracker.issueStatusCategory.Backlog,
[DeprecatedIssueStatus.Todo]: tracker.issueStatusCategory.Unstarted,
[DeprecatedIssueStatus.InProgress]: tracker.issueStatusCategory.Started,
[DeprecatedIssueStatus.Done]: tracker.issueStatusCategory.Completed,
[DeprecatedIssueStatus.Canceled]: tracker.issueStatusCategory.Canceled
} as const

async function createTeamIssueStatuses ({
tx,
teamId: attachedTo,
categories,
defaultStatusId,
defaultCategoryId = tracker.issueStatusCategory.Backlog
}: CreateTeamIssueStatusesArgs): Promise<void> {
const issueStatusRanks = [...genRanks(categories.length)]

for (const [i, statusCategory] of categories.entries()) {
const { _id: category, defaultStatusName } = statusCategory
const rank = issueStatusRanks[i]

await tx.addCollection(
tracker.class.IssueStatus,
attachedTo,
attachedTo,
tracker.class.Team,
'issueStatuses',
{ name: defaultStatusName, category, rank },
category === defaultCategoryId ? defaultStatusId : undefined
)
}
}

async function createDefaultTeam (tx: TxOperations): Promise<void> {
const current = await tx.findOne(tracker.class.Team, {
_id: tracker.team.DefaultTeam
Expand All @@ -29,6 +78,9 @@ async function createDefaultTeam (tx: TxOperations): Promise<void> {

// Create new if not deleted by customers.
if (current === undefined && currentDeleted === undefined) {
const defaultStatusId: Ref<IssueStatus> = generateId()
const categories = await tx.findAll(tracker.class.IssueStatusCategory, {})

await tx.createDoc<Team>(
tracker.class.Team,
core.space.Space,
Expand All @@ -39,21 +91,81 @@ async function createDefaultTeam (tx: TxOperations): Promise<void> {
members: [],
archived: false,
identifier: 'TSK',
sequence: 0
sequence: 0,
issueStatuses: 0,
defaultIssueStatus: defaultStatusId
},
tracker.team.DefaultTeam
)
await createTeamIssueStatuses({ tx, teamId: tracker.team.DefaultTeam, categories, defaultStatusId })
haiodo marked this conversation as resolved.
Show resolved Hide resolved
}
}

async function upgradeTeamIssueStatuses (tx: TxOperations): Promise<void> {
const teams = await tx.findAll(tracker.class.Team, { issueStatuses: undefined })

if (teams.length > 0) {
const categories = await tx.findAll(tracker.class.IssueStatusCategory, {})

for (const team of teams) {
const defaultStatusId: Ref<IssueStatus> = generateId()

await tx.update(team, { issueStatuses: 0, defaultIssueStatus: defaultStatusId })
await createTeamIssueStatuses({ tx, teamId: team._id, categories, defaultStatusId })
}
}
}

async function upgradeIssueStatuses (tx: TxOperations): Promise<void> {
const deprecatedStatuses = [
DeprecatedIssueStatus.Backlog,
DeprecatedIssueStatus.Canceled,
DeprecatedIssueStatus.Done,
DeprecatedIssueStatus.InProgress,
DeprecatedIssueStatus.Todo
]
const issues = await tx.findAll(tracker.class.Issue, { status: { $in: deprecatedStatuses as any } })

if (issues.length > 0) {
const statusByDeprecatedStatus = new Map<DeprecatedIssueStatus, Ref<IssueStatus>>()

for (const issue of issues) {
const deprecatedStatus = issue.status as unknown as DeprecatedIssueStatus

if (!statusByDeprecatedStatus.has(deprecatedStatus)) {
const category = categoryByDeprecatedIssueStatus[deprecatedStatus]
const issueStatus = await tx.findOne(tracker.class.IssueStatus, { category })

if (issueStatus === undefined) {
throw new Error(`Could not find a new status for "${DeprecatedIssueStatus[deprecatedStatus]}"`)
}

statusByDeprecatedStatus.set(deprecatedStatus, issueStatus._id)
}

await tx.update(issue, { status: statusByDeprecatedStatus.get(deprecatedStatus) })
}
}
}

async function createDefaults (tx: TxOperations): Promise<void> {
await createDefaultTeam(tx)
}

async function upgradeTeams (tx: TxOperations): Promise<void> {
await upgradeTeamIssueStatuses(tx)
}

async function upgradeIssues (tx: TxOperations): Promise<void> {
await upgradeIssueStatuses(tx)
}

export const trackerOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
await upgradeTeams(tx)
await upgradeIssues(tx)
}
}
6 changes: 2 additions & 4 deletions packages/kanban/src/components/Kanban.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
import core, { AttachedDoc, Class, Doc, DocumentQuery, DocumentUpdate, FindOptions, Ref, Space } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import { getPlatformColor, ScrollBox } from '@anticrm/ui'
import { createEventDispatcher, tick } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { slide } from 'svelte/transition'
import { DocWithRank } from '../types'
import { DocWithRank, StateType, TypeState } from '../types'
import { calcRank } from '../utils'

type StateType = any
type Item = DocWithRank & { state: StateType; doneState: StateType | null }
type TypeState = { _id: StateType; title: string; color: number }
type ExtItem = { prev?: Item; it: Item; next?: Item, pos: number }
type CardDragEvent = DragEvent & { currentTarget: EventTarget & HTMLDivElement }

Expand Down
11 changes: 11 additions & 0 deletions packages/kanban/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,14 @@ import { Doc } from '@anticrm/core'
export interface DocWithRank extends Doc {
rank: string
}

export type StateType = any

/**
* @public
*/
export interface TypeState {
_id: StateType
title: string
color: number
}
12 changes: 9 additions & 3 deletions packages/ui/src/components/SelectPopup.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
export let placeholder: IntlString | undefined = undefined
export let placeholderParam: any | undefined = undefined
export let searchable: boolean = false
export let value: Array<{id: number | string, icon: Asset, label: IntlString}>
export let value: Array<{id: number | string, icon: Asset, label?: IntlString, text?: string}>

let search: string = ''

Expand All @@ -39,10 +39,16 @@
{/if}
<div class="scroll">
<div class="box">
{#each value.filter(el => el.label.toLowerCase().includes(search.toLowerCase())) as item}
{#each value.filter(el => (el.label ?? el.text ?? '').toLowerCase().includes(search.toLowerCase())) as item}
<button class="menu-item" on:click={() => { dispatch('close', item.id) }}>
<div class="icon"><Icon icon={item.icon} size={'small'} /></div>
<span class="label"><Label label={item.label} /></span>
<span class="label">
{#if item.label}
<Label label={item.label} />
{:else if item.text}
<span>{item.text}</span>
{/if}
</span>
</button>
{/each}
</div>
Expand Down
Loading