mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-19 23:00:13 +00:00
[UBER-180] Fix status order (#3297)
Signed-off-by: Sergei Ogorelkov <sergei.ogorelkov@icloud.com>
This commit is contained in:
parent
0d1139f385
commit
1568c4b437
@ -21,7 +21,6 @@ import core, {
|
|||||||
DocumentUpdate,
|
DocumentUpdate,
|
||||||
Ref,
|
Ref,
|
||||||
SortingOrder,
|
SortingOrder,
|
||||||
StatusCategory,
|
|
||||||
TxCreateDoc,
|
TxCreateDoc,
|
||||||
TxOperations,
|
TxOperations,
|
||||||
TxResult,
|
TxResult,
|
||||||
@ -41,6 +40,7 @@ import {
|
|||||||
Project,
|
Project,
|
||||||
TimeReportDayType,
|
TimeReportDayType,
|
||||||
calcRank,
|
calcRank,
|
||||||
|
createStatuses,
|
||||||
genRanks
|
genRanks
|
||||||
} from '@hcengineering/tracker'
|
} from '@hcengineering/tracker'
|
||||||
import { DOMAIN_TRACKER } from '.'
|
import { DOMAIN_TRACKER } from '.'
|
||||||
@ -54,14 +54,6 @@ enum DeprecatedIssueStatus {
|
|||||||
Canceled
|
Canceled
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateProjectIssueStatusesArgs {
|
|
||||||
tx: TxOperations
|
|
||||||
projectId: Ref<Project>
|
|
||||||
categories: StatusCategory[]
|
|
||||||
defaultStatusId?: Ref<IssueStatus>
|
|
||||||
defaultCategoryId?: Ref<StatusCategory>
|
|
||||||
}
|
|
||||||
|
|
||||||
const categoryByDeprecatedIssueStatus = {
|
const categoryByDeprecatedIssueStatus = {
|
||||||
[DeprecatedIssueStatus.Backlog]: tracker.issueStatusCategory.Backlog,
|
[DeprecatedIssueStatus.Backlog]: tracker.issueStatusCategory.Backlog,
|
||||||
[DeprecatedIssueStatus.Todo]: tracker.issueStatusCategory.Unstarted,
|
[DeprecatedIssueStatus.Todo]: tracker.issueStatusCategory.Unstarted,
|
||||||
@ -70,30 +62,6 @@ const categoryByDeprecatedIssueStatus = {
|
|||||||
[DeprecatedIssueStatus.Canceled]: tracker.issueStatusCategory.Canceled
|
[DeprecatedIssueStatus.Canceled]: tracker.issueStatusCategory.Canceled
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
async function createProjectIssueStatuses ({
|
|
||||||
tx,
|
|
||||||
projectId: attachedTo,
|
|
||||||
categories,
|
|
||||||
defaultStatusId,
|
|
||||||
defaultCategoryId = tracker.issueStatusCategory.Backlog
|
|
||||||
}: CreateProjectIssueStatusesArgs): Promise<void> {
|
|
||||||
const issueStatusRanks = [...genRanks(categories.length)]
|
|
||||||
|
|
||||||
for (const [i, statusCategory] of categories.entries()) {
|
|
||||||
const { _id: category, defaultStatusName } = statusCategory
|
|
||||||
const rank = issueStatusRanks[i]
|
|
||||||
|
|
||||||
if (defaultStatusName !== undefined) {
|
|
||||||
await tx.createDoc(
|
|
||||||
tracker.class.IssueStatus,
|
|
||||||
attachedTo,
|
|
||||||
{ ofAttribute: tracker.attribute.IssueStatus, name: defaultStatusName, category, rank },
|
|
||||||
category === defaultCategoryId ? defaultStatusId : undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createDefaultProject (tx: TxOperations): Promise<void> {
|
async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||||
const current = await tx.findOne(tracker.class.Project, {
|
const current = await tx.findOne(tracker.class.Project, {
|
||||||
_id: tracker.project.DefaultProject
|
_id: tracker.project.DefaultProject
|
||||||
@ -106,7 +74,6 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
|||||||
// Create new if not deleted by customers.
|
// Create new if not deleted by customers.
|
||||||
if (current === undefined && currentDeleted === undefined) {
|
if (current === undefined && currentDeleted === undefined) {
|
||||||
const defaultStatusId: Ref<IssueStatus> = generateId()
|
const defaultStatusId: Ref<IssueStatus> = generateId()
|
||||||
const categories = await tx.findAll(core.class.StatusCategory, {}, { sort: { order: SortingOrder.Ascending } })
|
|
||||||
|
|
||||||
await tx.createDoc<Project>(
|
await tx.createDoc<Project>(
|
||||||
tracker.class.Project,
|
tracker.class.Project,
|
||||||
@ -126,14 +93,20 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
|||||||
},
|
},
|
||||||
tracker.project.DefaultProject
|
tracker.project.DefaultProject
|
||||||
)
|
)
|
||||||
await createProjectIssueStatuses({ tx, projectId: tracker.project.DefaultProject, categories, defaultStatusId })
|
await createStatuses(
|
||||||
|
tx,
|
||||||
|
tracker.project.DefaultProject,
|
||||||
|
tracker.class.IssueStatus,
|
||||||
|
tracker.attribute.IssueStatus,
|
||||||
|
defaultStatusId
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fixProjectIssueStatusesOrder (tx: TxOperations, project: Project): Promise<TxResult> {
|
async function fixProjectIssueStatusesOrder (tx: TxOperations, project: Project): Promise<TxResult> {
|
||||||
const statuses = await tx.findAll(
|
const statuses = await tx.findAll(
|
||||||
tracker.class.IssueStatus,
|
tracker.class.IssueStatus,
|
||||||
{ attachedTo: project._id },
|
{ space: project._id },
|
||||||
{ lookup: { category: core.class.StatusCategory } }
|
{ lookup: { category: core.class.StatusCategory } }
|
||||||
)
|
)
|
||||||
statuses.sort((a, b) => (a.$lookup?.category?.order ?? 0) - (b.$lookup?.category?.order ?? 0))
|
statuses.sort((a, b) => (a.$lookup?.category?.order ?? 0) - (b.$lookup?.category?.order ?? 0))
|
||||||
@ -167,13 +140,11 @@ async function upgradeProjectIssueStatuses (tx: TxOperations): Promise<void> {
|
|||||||
const projects = await tx.findAll(tracker.class.Project, { issueStatuses: undefined })
|
const projects = await tx.findAll(tracker.class.Project, { issueStatuses: undefined })
|
||||||
|
|
||||||
if (projects.length > 0) {
|
if (projects.length > 0) {
|
||||||
const categories = await tx.findAll(core.class.StatusCategory, {}, { sort: { order: SortingOrder.Ascending } })
|
|
||||||
|
|
||||||
for (const project of projects) {
|
for (const project of projects) {
|
||||||
const defaultStatusId: Ref<IssueStatus> = generateId()
|
const defaultStatusId: Ref<IssueStatus> = generateId()
|
||||||
|
|
||||||
await tx.update(project, { issueStatuses: 0, defaultIssueStatus: defaultStatusId })
|
await tx.update(project, { issueStatuses: 0, defaultIssueStatus: defaultStatusId })
|
||||||
await createProjectIssueStatuses({ tx, projectId: project._id, categories, defaultStatusId })
|
await createStatuses(tx, project._id, tracker.class.IssueStatus, tracker.attribute.IssueStatus, defaultStatusId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ export interface StatusCategory extends Doc {
|
|||||||
icon: Asset
|
icon: Asset
|
||||||
label: IntlString
|
label: IntlString
|
||||||
color: number
|
color: number
|
||||||
defaultStatusName?: string
|
defaultStatusName: string
|
||||||
order: number // category order
|
order: number // category order
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
@ -67,7 +67,7 @@
|
|||||||
|
|
||||||
showPopup(
|
showPopup(
|
||||||
SelectPopup,
|
SelectPopup,
|
||||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus, searchable: true },
|
{ value: statusesInfo, placeholder: tracker.string.SetStatus },
|
||||||
eventToHTMLElement(event),
|
eventToHTMLElement(event),
|
||||||
changeStatus
|
changeStatus
|
||||||
)
|
)
|
||||||
|
@ -15,19 +15,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Employee } from '@hcengineering/contact'
|
import { Employee } from '@hcengineering/contact'
|
||||||
import { AccountArrayEditor, AssigneeBox } from '@hcengineering/contact-resources'
|
import { AccountArrayEditor, AssigneeBox } from '@hcengineering/contact-resources'
|
||||||
import core, {
|
import core, { Account, DocumentUpdate, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||||
Account,
|
|
||||||
ApplyOperations,
|
|
||||||
DocumentUpdate,
|
|
||||||
Ref,
|
|
||||||
SortingOrder,
|
|
||||||
generateId,
|
|
||||||
getCurrentAccount
|
|
||||||
} from '@hcengineering/core'
|
|
||||||
import { Asset } from '@hcengineering/platform'
|
import { Asset } from '@hcengineering/platform'
|
||||||
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
|
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||||
import { IssueStatus, Project, TimeReportDayType, genRanks } from '@hcengineering/tracker'
|
import { IssueStatus, Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker'
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
EditBox,
|
EditBox,
|
||||||
@ -159,7 +151,7 @@
|
|||||||
|
|
||||||
isSaving = true
|
isSaving = true
|
||||||
await ops.createDoc(tracker.class.Project, core.space.Space, projectData, projectId)
|
await ops.createDoc(tracker.class.Project, core.space.Space, projectData, projectId)
|
||||||
await createProjectIssueStatuses(ops, projectId, defaultStatusId)
|
await createStatuses(ops, projectId, tracker.class.IssueStatus, tracker.attribute.IssueStatus, defaultStatusId)
|
||||||
const succeeded = await ops.commit()
|
const succeeded = await ops.commit()
|
||||||
isSaving = false
|
isSaving = false
|
||||||
|
|
||||||
@ -170,39 +162,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createProjectIssueStatuses (
|
|
||||||
ops: ApplyOperations,
|
|
||||||
projectId: Ref<Project>,
|
|
||||||
defaultStatusId: Ref<IssueStatus>,
|
|
||||||
defaultCategoryId = tracker.issueStatusCategory.Backlog
|
|
||||||
): Promise<void> {
|
|
||||||
const categories = await ops.findAll(
|
|
||||||
core.class.StatusCategory,
|
|
||||||
{ ofAttribute: tracker.attribute.IssueStatus },
|
|
||||||
{ sort: { order: SortingOrder.Ascending } }
|
|
||||||
)
|
|
||||||
const issueStatusRanks = [...genRanks(categories.length)]
|
|
||||||
|
|
||||||
for (const [i, statusCategory] of categories.entries()) {
|
|
||||||
const { _id: category, defaultStatusName } = statusCategory
|
|
||||||
const rank = issueStatusRanks[i]
|
|
||||||
|
|
||||||
if (defaultStatusName !== undefined) {
|
|
||||||
await ops.createDoc(
|
|
||||||
tracker.class.IssueStatus,
|
|
||||||
projectId,
|
|
||||||
{
|
|
||||||
ofAttribute: tracker.attribute.IssueStatus,
|
|
||||||
name: defaultStatusName,
|
|
||||||
category,
|
|
||||||
rank
|
|
||||||
},
|
|
||||||
category === defaultCategoryId ? defaultStatusId : undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function chooseIcon (ev: MouseEvent) {
|
function chooseIcon (ev: MouseEvent) {
|
||||||
showPopup(ProjectIconChooser, { icon, color }, 'top', (result) => {
|
showPopup(ProjectIconChooser, { icon, color }, 'top', (result) => {
|
||||||
if (result !== undefined && result !== null) {
|
if (result !== undefined && result !== null) {
|
||||||
|
@ -68,9 +68,7 @@
|
|||||||
|
|
||||||
async function addStatus () {
|
async function addStatus () {
|
||||||
if (editingStatus?.name && editingStatus?.category) {
|
if (editingStatus?.name && editingStatus?.category) {
|
||||||
const categoryStatuses = $statusStore.statuses.filter((s) => s.category === editingStatus!.category)
|
const [prevStatus, nextStatus] = getSiblingsForNewStatus(editingStatus.category)
|
||||||
const prevStatus = categoryStatuses[categoryStatuses.length - 1]
|
|
||||||
const nextStatus = $statusStore.statuses[$statusStore.statuses.findIndex(({ _id }) => _id === prevStatus._id) + 1]
|
|
||||||
|
|
||||||
isSaving = true
|
isSaving = true
|
||||||
await client.createDoc(tracker.class.IssueStatus, projectId, {
|
await client.createDoc(tracker.class.IssueStatus, projectId, {
|
||||||
@ -152,8 +150,8 @@
|
|||||||
await client.removeDoc(status._class, status.space, status._id)
|
await client.removeDoc(status._class, status.space, status._id)
|
||||||
|
|
||||||
if (project.defaultIssueStatus === status._id) {
|
if (project.defaultIssueStatus === status._id) {
|
||||||
const newDefaultStatus = $statusStore.statuses.find(
|
const newDefaultStatus = projectStatuses.find(
|
||||||
(s) => s._id !== status._id && s.category === status.category && s.space === status.space
|
(s) => s._id !== status._id && s.category === status.category
|
||||||
)
|
)
|
||||||
if (newDefaultStatus?._id) {
|
if (newDefaultStatus?._id) {
|
||||||
await updateProjectDefaultStatus(newDefaultStatus._id)
|
await updateProjectDefaultStatus(newDefaultStatus._id)
|
||||||
@ -188,8 +186,8 @@
|
|||||||
const fromIndex = getStatusIndex(draggingStatus)
|
const fromIndex = getStatusIndex(draggingStatus)
|
||||||
const toIndex = getStatusIndex(toItem)
|
const toIndex = getStatusIndex(toItem)
|
||||||
const [prev, next] = [
|
const [prev, next] = [
|
||||||
$statusStore.statuses[fromIndex < toIndex ? toIndex : toIndex - 1],
|
projectStatuses[fromIndex < toIndex ? toIndex : toIndex - 1],
|
||||||
$statusStore.statuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
|
projectStatuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
|
||||||
]
|
]
|
||||||
|
|
||||||
isSaving = true
|
isSaving = true
|
||||||
@ -203,7 +201,34 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getStatusIndex (status: IssueStatus) {
|
function getStatusIndex (status: IssueStatus) {
|
||||||
return $statusStore.statuses.findIndex(({ _id }) => _id === status._id) ?? -1
|
return projectStatuses.findIndex(({ _id }) => _id === status._id) ?? -1
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSiblingsForNewStatus (
|
||||||
|
categoryId: StatusCategory['_id']
|
||||||
|
): readonly [] | readonly [IssueStatus | undefined, IssueStatus | undefined] {
|
||||||
|
const categoryStatuses = projectStatuses.filter((s) => s.category === categoryId)
|
||||||
|
if (categoryStatuses.length > 0) {
|
||||||
|
const prev = categoryStatuses[categoryStatuses.length - 1]
|
||||||
|
const next = projectStatuses[getStatusIndex(prev) + 1]
|
||||||
|
|
||||||
|
return [prev, next]
|
||||||
|
}
|
||||||
|
|
||||||
|
const category = statusCategories?.find(({ _id }) => _id === categoryId)
|
||||||
|
if (!category) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const { order } = category
|
||||||
|
const prev = projectStatuses.findLast(
|
||||||
|
(s) => s.$lookup?.category?.order !== undefined && s.$lookup.category.order < order
|
||||||
|
)
|
||||||
|
const next = projectStatuses.find(
|
||||||
|
(s) => s.$lookup?.category?.order !== undefined && s.$lookup.category.order > order
|
||||||
|
)
|
||||||
|
|
||||||
|
return [prev, next]
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetDrag () {
|
function resetDrag () {
|
||||||
@ -213,6 +238,7 @@
|
|||||||
|
|
||||||
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
|
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
|
||||||
$: updateStatusCategories()
|
$: updateStatusCategories()
|
||||||
|
$: projectStatuses = $statusStore.statuses.filter((status) => status.space === projectId)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
|
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
|
||||||
@ -232,14 +258,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
{#if project === undefined || statusCategories === undefined || $statusStore.statuses.length === 0}
|
{#if project === undefined || statusCategories === undefined || projectStatuses.length === 0}
|
||||||
<Loading />
|
<Loading />
|
||||||
{:else}
|
{:else}
|
||||||
<Scroller>
|
<Scroller>
|
||||||
<div class="popupPanel-body__main-content py-10 clear-mins flex-no-shrink">
|
<div class="popupPanel-body__main-content py-10 clear-mins flex-no-shrink">
|
||||||
{#each statusCategories as category}
|
{#each statusCategories as category}
|
||||||
{@const statuses =
|
{@const statuses = projectStatuses.filter((s) => s.category === category._id) ?? []}
|
||||||
$statusStore.statuses.filter((s) => s.space === projectId && s.category === category._id) ?? []}
|
|
||||||
{@const isSingle = statuses.length === 1}
|
{@const isSingle = statuses.length === 1}
|
||||||
<div class="flex-between category-name">
|
<div class="flex-between category-name">
|
||||||
<Label label={category.label} />
|
<Label label={category.label} />
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import core, { ApplyOperations, SortingOrder, Status, TxOperations, generateId } from '@hcengineering/core'
|
||||||
import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
|
import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
|
||||||
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
|
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
|
||||||
|
|
||||||
@ -44,3 +45,40 @@ export const calcRank = (prev?: { rank: string }, next?: { rank: string }): stri
|
|||||||
}
|
}
|
||||||
return a.between(b).toString()
|
return a.between(b).toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates statuses for provided space.
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export async function createStatuses (
|
||||||
|
client: TxOperations | ApplyOperations,
|
||||||
|
spaceId: Status['space'],
|
||||||
|
statusClass: Status['_class'],
|
||||||
|
categoryOfAttribute: Status['ofAttribute'],
|
||||||
|
defaultStatusId: Status['_id']
|
||||||
|
): Promise<void> {
|
||||||
|
const categories = await client.findAll(
|
||||||
|
core.class.StatusCategory,
|
||||||
|
{ ofAttribute: categoryOfAttribute },
|
||||||
|
{ sort: { order: SortingOrder.Ascending } }
|
||||||
|
)
|
||||||
|
const ranks = [...genRanks(categories.length)]
|
||||||
|
|
||||||
|
for (const [i, category] of categories.entries()) {
|
||||||
|
const statusId = i === 0 ? defaultStatusId : generateId<Status>()
|
||||||
|
const rank = ranks[i]
|
||||||
|
|
||||||
|
await client.createDoc(
|
||||||
|
statusClass,
|
||||||
|
spaceId,
|
||||||
|
{
|
||||||
|
ofAttribute: categoryOfAttribute,
|
||||||
|
name: category.defaultStatusName,
|
||||||
|
category: category._id,
|
||||||
|
rank
|
||||||
|
},
|
||||||
|
statusId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user