mirror of
https://github.com/hcengineering/platform.git
synced 2024-11-22 11:42:30 +03: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,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
StatusCategory,
|
||||
TxCreateDoc,
|
||||
TxOperations,
|
||||
TxResult,
|
||||
@ -41,6 +40,7 @@ import {
|
||||
Project,
|
||||
TimeReportDayType,
|
||||
calcRank,
|
||||
createStatuses,
|
||||
genRanks
|
||||
} from '@hcengineering/tracker'
|
||||
import { DOMAIN_TRACKER } from '.'
|
||||
@ -54,14 +54,6 @@ enum DeprecatedIssueStatus {
|
||||
Canceled
|
||||
}
|
||||
|
||||
interface CreateProjectIssueStatusesArgs {
|
||||
tx: TxOperations
|
||||
projectId: Ref<Project>
|
||||
categories: StatusCategory[]
|
||||
defaultStatusId?: Ref<IssueStatus>
|
||||
defaultCategoryId?: Ref<StatusCategory>
|
||||
}
|
||||
|
||||
const categoryByDeprecatedIssueStatus = {
|
||||
[DeprecatedIssueStatus.Backlog]: tracker.issueStatusCategory.Backlog,
|
||||
[DeprecatedIssueStatus.Todo]: tracker.issueStatusCategory.Unstarted,
|
||||
@ -70,30 +62,6 @@ const categoryByDeprecatedIssueStatus = {
|
||||
[DeprecatedIssueStatus.Canceled]: tracker.issueStatusCategory.Canceled
|
||||
} 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> {
|
||||
const current = await tx.findOne(tracker.class.Project, {
|
||||
_id: tracker.project.DefaultProject
|
||||
@ -106,7 +74,6 @@ async function createDefaultProject (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(core.class.StatusCategory, {}, { sort: { order: SortingOrder.Ascending } })
|
||||
|
||||
await tx.createDoc<Project>(
|
||||
tracker.class.Project,
|
||||
@ -126,14 +93,20 @@ async function createDefaultProject (tx: TxOperations): Promise<void> {
|
||||
},
|
||||
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> {
|
||||
const statuses = await tx.findAll(
|
||||
tracker.class.IssueStatus,
|
||||
{ attachedTo: project._id },
|
||||
{ space: project._id },
|
||||
{ lookup: { category: core.class.StatusCategory } }
|
||||
)
|
||||
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 })
|
||||
|
||||
if (projects.length > 0) {
|
||||
const categories = await tx.findAll(core.class.StatusCategory, {}, { sort: { order: SortingOrder.Ascending } })
|
||||
|
||||
for (const project of projects) {
|
||||
const defaultStatusId: Ref<IssueStatus> = generateId()
|
||||
|
||||
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
|
||||
label: IntlString
|
||||
color: number
|
||||
defaultStatusName?: string
|
||||
defaultStatusName: string
|
||||
order: number // category order
|
||||
}
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@
|
||||
|
||||
showPopup(
|
||||
SelectPopup,
|
||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus, searchable: true },
|
||||
{ value: statusesInfo, placeholder: tracker.string.SetStatus },
|
||||
eventToHTMLElement(event),
|
||||
changeStatus
|
||||
)
|
||||
|
@ -15,19 +15,11 @@
|
||||
<script lang="ts">
|
||||
import { Employee } from '@hcengineering/contact'
|
||||
import { AccountArrayEditor, AssigneeBox } from '@hcengineering/contact-resources'
|
||||
import core, {
|
||||
Account,
|
||||
ApplyOperations,
|
||||
DocumentUpdate,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
generateId,
|
||||
getCurrentAccount
|
||||
} from '@hcengineering/core'
|
||||
import core, { Account, DocumentUpdate, Ref, generateId, getCurrentAccount } from '@hcengineering/core'
|
||||
import { Asset } from '@hcengineering/platform'
|
||||
import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation'
|
||||
import { StyledTextBox } from '@hcengineering/text-editor'
|
||||
import { IssueStatus, Project, TimeReportDayType, genRanks } from '@hcengineering/tracker'
|
||||
import { IssueStatus, Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker'
|
||||
import {
|
||||
Button,
|
||||
EditBox,
|
||||
@ -159,7 +151,7 @@
|
||||
|
||||
isSaving = true
|
||||
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()
|
||||
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) {
|
||||
showPopup(ProjectIconChooser, { icon, color }, 'top', (result) => {
|
||||
if (result !== undefined && result !== null) {
|
||||
|
@ -68,9 +68,7 @@
|
||||
|
||||
async function addStatus () {
|
||||
if (editingStatus?.name && editingStatus?.category) {
|
||||
const categoryStatuses = $statusStore.statuses.filter((s) => s.category === editingStatus!.category)
|
||||
const prevStatus = categoryStatuses[categoryStatuses.length - 1]
|
||||
const nextStatus = $statusStore.statuses[$statusStore.statuses.findIndex(({ _id }) => _id === prevStatus._id) + 1]
|
||||
const [prevStatus, nextStatus] = getSiblingsForNewStatus(editingStatus.category)
|
||||
|
||||
isSaving = true
|
||||
await client.createDoc(tracker.class.IssueStatus, projectId, {
|
||||
@ -152,8 +150,8 @@
|
||||
await client.removeDoc(status._class, status.space, status._id)
|
||||
|
||||
if (project.defaultIssueStatus === status._id) {
|
||||
const newDefaultStatus = $statusStore.statuses.find(
|
||||
(s) => s._id !== status._id && s.category === status.category && s.space === status.space
|
||||
const newDefaultStatus = projectStatuses.find(
|
||||
(s) => s._id !== status._id && s.category === status.category
|
||||
)
|
||||
if (newDefaultStatus?._id) {
|
||||
await updateProjectDefaultStatus(newDefaultStatus._id)
|
||||
@ -188,8 +186,8 @@
|
||||
const fromIndex = getStatusIndex(draggingStatus)
|
||||
const toIndex = getStatusIndex(toItem)
|
||||
const [prev, next] = [
|
||||
$statusStore.statuses[fromIndex < toIndex ? toIndex : toIndex - 1],
|
||||
$statusStore.statuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
|
||||
projectStatuses[fromIndex < toIndex ? toIndex : toIndex - 1],
|
||||
projectStatuses[fromIndex < toIndex ? toIndex + 1 : toIndex]
|
||||
]
|
||||
|
||||
isSaving = true
|
||||
@ -203,7 +201,34 @@
|
||||
}
|
||||
|
||||
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 () {
|
||||
@ -213,6 +238,7 @@
|
||||
|
||||
$: projectQuery.query(projectClass, { _id: projectId }, (result) => ([project] = result), { limit: 1 })
|
||||
$: updateStatusCategories()
|
||||
$: projectStatuses = $statusStore.statuses.filter((status) => status.space === projectId)
|
||||
</script>
|
||||
|
||||
<Panel isHeader={false} isAside={false} on:fullsize on:close={() => dispatch('close')}>
|
||||
@ -232,14 +258,13 @@
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
{#if project === undefined || statusCategories === undefined || $statusStore.statuses.length === 0}
|
||||
{#if project === undefined || statusCategories === undefined || projectStatuses.length === 0}
|
||||
<Loading />
|
||||
{:else}
|
||||
<Scroller>
|
||||
<div class="popupPanel-body__main-content py-10 clear-mins flex-no-shrink">
|
||||
{#each statusCategories as category}
|
||||
{@const statuses =
|
||||
$statusStore.statuses.filter((s) => s.space === projectId && s.category === category._id) ?? []}
|
||||
{@const statuses = projectStatuses.filter((s) => s.category === category._id) ?? []}
|
||||
{@const isSingle = statuses.length === 1}
|
||||
<div class="flex-between category-name">
|
||||
<Label label={category.label} />
|
||||
|
@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { ApplyOperations, SortingOrder, Status, TxOperations, generateId } from '@hcengineering/core'
|
||||
import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
|
||||
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()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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