UBER-408 Issue as task (#3497)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov 2023-07-13 19:33:23 +06:00 committed by GitHub
parent d29a2df4d9
commit ab02db5890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 216 additions and 175 deletions

View File

@ -26,6 +26,7 @@ const object: AttachedData<Issue> = {
milestone: null,
number: 0,
rank: '',
doneState: null,
status: '' as Ref<IssueStatus>,
priority: IssuePriority.NoPriority,
dueDate: null,
@ -86,6 +87,7 @@ async function genIssue (client: TxOperations, statuses: Ref<IssueStatus>[]): Pr
component: object.component,
milestone: object.milestone,
number: (incResult as any).object.sequence,
doneState: null,
status: faker.random.arrayElement(statuses),
priority: faker.random.arrayElement(Object.values(IssuePriority)) as IssuePriority,
rank: calcRank(lastOne, undefined),

View File

@ -158,7 +158,7 @@ async function genApplicant (
const applicant: AttachedData<Applicant> = {
number: faker.datatype.number(),
assignee: faker.random.arrayElement(emoloyeeIds),
state: faker.random.arrayElement(states),
status: faker.random.arrayElement(states),
doneState: null,
rank: rank as string,
startDate: null,
@ -201,7 +201,7 @@ async function genApplicant (
recruit.class.Applicant,
'applications',
{
state: faker.random.arrayElement(states)
status: faker.random.arrayElement(states)
}
)
)

View File

@ -17,22 +17,20 @@
import automation, { AutomationSupport } from '@hcengineering/automation'
import { Board, boardId, Card, CardCover, CommonBoardPreference, MenuPage } from '@hcengineering/board'
import type { Employee } from '@hcengineering/contact'
import { Class, DOMAIN_MODEL, IndexKind, Markup, Ref, Type } from '@hcengineering/core'
import { Class, DOMAIN_MODEL, IndexKind, Markup, Ref, Timestamp, Type } from '@hcengineering/core'
import {
ArrOf,
Builder,
Collection,
Index,
Model,
Prop,
TypeBoolean,
TypeDate,
TypeMarkup,
TypeRef,
TypeString,
UX
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import contact from '@hcengineering/model-contact'
import core, { TDoc, TType } from '@hcengineering/model-core'
import preference, { TPreference } from '@hcengineering/model-preference'
@ -90,12 +88,6 @@ export class TCard extends TTask implements Card {
@Index(IndexKind.FullText)
location?: string
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@Prop(TypeRef(contact.class.Employee), board.string.Assignee)
declare assignee: Ref<Employee> | null
@ -104,6 +96,9 @@ export class TCard extends TTask implements Card {
@Prop(TypeCardCover(), board.string.Cover)
cover?: CardCover
@Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null
}
@Model(board.class.MenuPage, core.class.Doc, DOMAIN_MODEL)

View File

@ -15,7 +15,7 @@
// To help typescript locate view plugin properly
import type { Employee } from '@hcengineering/contact'
import { FindOptions, IndexKind, Ref, SortingOrder } from '@hcengineering/core'
import { FindOptions, IndexKind, Ref, SortingOrder, Timestamp } from '@hcengineering/core'
import { Customer, Funnel, Lead, leadId } from '@hcengineering/lead'
import {
Builder,
@ -25,6 +25,7 @@ import {
Model,
Prop,
ReadOnly,
TypeDate,
TypeMarkup,
TypeRef,
TypeString,
@ -69,16 +70,13 @@ export class TLead extends TTask implements Lead {
@ReadOnly()
declare attachedTo: Ref<Customer>
@Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null
@Prop(TypeString(), lead.string.Title)
@Index(IndexKind.FullText)
title!: string
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@Prop(TypeRef(contact.class.Employee), lead.string.Assignee)
declare assignee: Ref<Employee> | null
}
@ -247,7 +245,7 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues
},
'state',
'status',
'doneState',
'attachments',
'comments',
@ -272,9 +270,9 @@ export function createModel (builder: Builder): void {
)
const leadViewOptions: ViewOptionsModel = {
groupBy: ['state', 'assignee'],
groupBy: ['status', 'assignee'],
orderBy: [
['state', SortingOrder.Ascending],
['status', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['createdOn', SortingOrder.Descending],
['dueDate', SortingOrder.Ascending],
@ -304,7 +302,7 @@ export function createModel (builder: Builder): void {
config: [
{ key: '', displayProps: { fixed: 'left', key: 'lead' } },
{
key: 'state',
key: 'status',
props: { kind: 'list', size: 'small', shouldShowName: false }
},
{
@ -402,7 +400,7 @@ export function createModel (builder: Builder): void {
lead.class.Lead,
lead.ids.LeadNotificationGroup,
[],
['comments', 'state', 'doneState']
['comments', 'status', 'doneState']
)
builder.createDoc(

View File

@ -55,6 +55,7 @@ import {
recruitId
} from '@hcengineering/recruit'
import setting from '@hcengineering/setting'
import { State } from '@hcengineering/task'
import { KeyBinding, ViewOptionsModel } from '@hcengineering/view'
import recruit from './plugin'
import { createReviewModel, reviewTableConfig, reviewTableOptions } from './review'
@ -158,14 +159,14 @@ export class TApplicant extends TTask implements Applicant {
@Index(IndexKind.Indexed)
declare space: Ref<Vacancy>
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null
@Prop(TypeRef(contact.class.Employee), recruit.string.AssignedRecruiter)
declare assignee: Ref<Employee> | null
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: task.attribute.State })
declare status: Ref<State>
}
@Model(recruit.class.ApplicantMatch, core.class.AttachedDoc, DOMAIN_TASK)
@ -425,7 +426,7 @@ export function createModel (builder: Builder): void {
{
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table,
config: ['', '$lookup.attachedTo', 'state', 'doneState', 'modifiedOn'],
config: ['', '$lookup.attachedTo', 'status', 'doneState', 'modifiedOn'],
configOptions: {
sortable: true
},
@ -440,7 +441,7 @@ export function createModel (builder: Builder): void {
{
attachTo: recruit.class.Applicant,
descriptor: view.viewlet.Table,
config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'state', 'comments', 'doneState'],
config: ['', '$lookup.space.name', '$lookup.space.$lookup.company', 'status', 'comments', 'doneState'],
configOptions: {
sortable: true
},
@ -535,7 +536,7 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues
},
'state',
'status',
'doneState',
'attachments',
'comments',
@ -583,7 +584,7 @@ export function createModel (builder: Builder): void {
presenter: tracker.component.RelatedIssueSelector,
label: tracker.string.Issues
},
'state',
'status',
'attachments',
'comments',
'modifiedOn',
@ -651,9 +652,9 @@ export function createModel (builder: Builder): void {
const applicantViewOptions = (colors: boolean): ViewOptionsModel => {
const model: ViewOptionsModel = {
groupBy: ['state', 'assignee', 'space', 'createdBy', 'modifiedBy'],
groupBy: ['status', 'assignee', 'space', 'createdBy', 'modifiedBy'],
orderBy: [
['state', SortingOrder.Ascending],
['status', SortingOrder.Ascending],
['modifiedOn', SortingOrder.Descending],
['createdOn', SortingOrder.Descending],
['dueDate', SortingOrder.Ascending],
@ -684,7 +685,7 @@ export function createModel (builder: Builder): void {
config: [
{ key: '', displayProps: { fixed: 'left', key: 'app' } },
{
key: 'state',
key: 'status',
props: { kind: 'list', size: 'small', shouldShowName: false }
},
{
@ -778,7 +779,7 @@ export function createModel (builder: Builder): void {
'',
'space',
'assignee',
'state',
'status',
'attachments',
'dueDate',
'comments',
@ -1125,7 +1126,7 @@ export function createModel (builder: Builder): void {
action: view.actionImpl.ValueSelector,
actionPopup: view.component.ValueSelector,
actionProps: {
attribute: 'state',
attribute: 'status',
_class: task.class.State,
query: {},
searchField: 'name',
@ -1285,7 +1286,7 @@ export function createModel (builder: Builder): void {
recruit.class.Applicant,
recruit.ids.ApplicationNotificationGroup,
[],
['comments', 'state', 'doneState', 'dueDate']
['comments', 'status', 'doneState', 'dueDate']
)
builder.createDoc(

View File

@ -15,6 +15,8 @@
import type { Employee } from '@hcengineering/contact'
import contact from '@hcengineering/contact'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import { Arr, Attribute, Class, Doc, Domain, IndexKind, Ref, Space, Status, Timestamp } from '@hcengineering/core'
import {
Builder,
@ -88,8 +90,8 @@ export class TLostState extends TDoneState implements LostState {}
@Model(task.class.Task, core.class.AttachedDoc, DOMAIN_TASK)
@UX(task.string.Task, task.icon.Task, task.string.Task)
export class TTask extends TAttachedDoc implements Task {
@Prop(TypeRef(task.class.State), task.string.TaskState, { _id: task.attribute.State })
state!: Ref<State>
@Prop(TypeRef(core.class.Status), task.string.TaskState, { _id: task.attribute.State })
status!: Ref<Status>
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: task.attribute.DoneState })
doneState!: Ref<DoneState> | null
@ -105,13 +107,16 @@ export class TTask extends TAttachedDoc implements Task {
@Prop(TypeDate(), task.string.DueDate, { editor: task.component.DueDateEditor })
dueDate!: Timestamp | null
@Prop(TypeDate(), task.string.StartDate)
startDate!: Timestamp | null
declare rank: string
@Prop(Collection(tags.class.TagReference, task.string.TaskLabels), task.string.TaskLabels)
labels!: number
labels?: number
@Prop(Collection(chunter.class.Comment), chunter.string.Comments)
comments?: number
@Prop(Collection(attachment.class.Attachment), attachment.string.Attachments, { shortLabel: attachment.string.Files })
attachments?: number
}
@Model(task.class.TodoItem, core.class.AttachedDoc, DOMAIN_TASK)

View File

@ -13,11 +13,13 @@
// limitations under the License.
//
import { Class, Doc, Domain, Ref, Space, TxOperations } from '@hcengineering/core'
import { Class, DOMAIN_TX, Doc, Domain, Ref, Space, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model'
import core from '@hcengineering/model-core'
import tags from '@hcengineering/model-tags'
import { DoneStateTemplate, KanbanTemplate, StateTemplate, genRanks } from '@hcengineering/task'
import view, { Filter } from '@hcengineering/view'
import { DOMAIN_TASK } from '.'
import task from './plugin'
/**
@ -125,8 +127,72 @@ async function createDefaults (tx: TxOperations): Promise<void> {
await createDefaultSequence(tx)
}
async function renameState (client: MigrationClient): Promise<void> {
const toUpdate = await client.find(DOMAIN_TASK, { state: { $exists: true } })
if (toUpdate.length > 0) {
for (const doc of toUpdate) {
await client.update(
DOMAIN_TX,
{ objectId: doc._id },
{ $rename: { 'attributes.state': 'attributes.status', 'operations.state': 'operations.status' } }
)
await client.update(
DOMAIN_TX,
{ 'tx.objectId': doc._id },
{ $rename: { 'tx.attributes.state': 'tx.attributes.status', 'tx.operations.state': 'tx.operations.status' } }
)
}
await client.update(DOMAIN_TASK, { _id: { $in: toUpdate.map((p) => p._id) } }, { $rename: { state: 'status' } })
}
}
async function renameStatePrefs (client: MigrationUpgradeClient): Promise<void> {
const txop = new TxOperations(client, core.account.System)
const prefs = await client.findAll(view.class.ViewletPreference, {})
for (const pref of prefs) {
let update = false
const config = pref.config
for (let index = 0; index < config.length; index++) {
const conf = config[index]
if (typeof conf === 'string') {
if (conf === 'state') {
config[index] = 'status'
update = true
} else if (conf === '$lookup.state') {
config[index] = '$lookup.status'
update = true
}
} else if (conf.key === 'state') {
conf.key = 'status'
update = true
}
}
if (update) {
await txop.update(pref, {
config
})
}
}
const res = await client.findAll(view.class.FilteredView, { filters: /"key":"state"/ as any })
if (res.length > 0) {
for (const doc of res) {
const filters = JSON.parse(doc.filters) as Filter[]
for (const filter of filters) {
if (filter.key.key === 'state') {
filter.key.key = 'status'
}
}
await txop.update(doc, {
filters: JSON.stringify(filters)
})
}
}
}
export const taskOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await renameState(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
@ -144,5 +210,6 @@ export const taskOperation: MigrateOperation = {
},
task.category.TaskTag
)
await renameStatePrefs(client)
}
}

View File

@ -40,6 +40,7 @@
"@hcengineering/model-notification": "^0.6.0",
"@hcengineering/tracker": "^0.6.10",
"@hcengineering/tracker-resources": "^0.6.0",
"@hcengineering/model-task": "^0.6.0",
"@hcengineering/model-chunter": "^0.6.0",
"@hcengineering/workbench": "^0.6.8",
"@hcengineering/view": "^0.6.8",

View File

@ -46,6 +46,7 @@ import {
} from '@hcengineering/model'
import attachment from '@hcengineering/model-attachment'
import chunter from '@hcengineering/model-chunter'
import { TTask } from '@hcengineering/model-task'
import core, { DOMAIN_SPACE, TAttachedDoc, TDoc, TSpace, TStatus, TType } from '@hcengineering/model-core'
import view, { actionTemplates, classPresenter, createAction, showColorsViewOption } from '@hcengineering/model-view'
import workbench, { createNavigateAction } from '@hcengineering/model-workbench'
@ -53,7 +54,7 @@ import notification from '@hcengineering/notification'
import { IntlString } from '@hcengineering/platform'
import setting from '@hcengineering/setting'
import tags, { TagElement } from '@hcengineering/tags'
import task from '@hcengineering/task'
import task, { DoneState } from '@hcengineering/task'
import {
Component,
Issue,
@ -150,9 +151,9 @@ export function TypeReportedTime (): Type<number> {
/**
* @public
*/
@Model(tracker.class.Issue, core.class.AttachedDoc, DOMAIN_TRACKER)
@Model(tracker.class.Issue, task.class.Task)
@UX(tracker.string.Issue, tracker.icon.Issue, 'TSK', 'title')
export class TIssue extends TAttachedDoc implements Issue {
export class TIssue extends TTask implements Issue {
@Prop(TypeRef(tracker.class.Issue), tracker.string.Parent)
declare attachedTo: Ref<Issue>
@ -202,14 +203,12 @@ export class TIssue extends TAttachedDoc implements Issue {
parents!: IssueParentInfo[]
@Prop(Collection(chunter.class.Comment), tracker.string.Comments, { icon: chunter.icon.Chunter })
comments!: number
@Prop(Collection(attachment.class.Attachment), tracker.string.Attachments, { icon: attachment.icon.Attachment })
attachments!: number
@Prop(Collection(tags.class.TagReference), tracker.string.Labels)
labels?: number
declare labels: number
@Prop(TypeRef(task.class.DoneState), task.string.TaskStateDone, { _id: task.attribute.DoneState })
@Hidden()
declare doneState: Ref<DoneState> | null
@Prop(TypeRef(tracker.class.Project), tracker.string.Project, { icon: tracker.icon.Issues })
@Index(IndexKind.Indexed)

View File

@ -18,6 +18,8 @@ import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpda
import tags from '@hcengineering/tags'
import { IssueStatus, Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker'
import tracker from './plugin'
import { DOMAIN_TRACKER } from '.'
import { DOMAIN_TASK } from '@hcengineering/model-task'
async function createDefaultProject (tx: TxOperations): Promise<void> {
const current = await tx.findOne(tracker.class.Project, {
@ -86,8 +88,17 @@ async function fixProjectIcons (tx: TxOperations): Promise<void> {
await Promise.all(promises)
}
async function moveIssues (client: MigrationClient): Promise<void> {
const docs = await client.find(DOMAIN_TRACKER, { _class: tracker.class.Issue })
if (docs.length > 0) {
await client.move(DOMAIN_TRACKER, { _class: tracker.class.Issue }, DOMAIN_TASK)
}
}
export const trackerOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await moveIssues(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)

View File

@ -62,7 +62,7 @@ export async function createApplication (
await client.addCollection(recruit.class.Applicant, _space, doc._id, recruit.mixin.Candidate, 'applications', {
...data,
state: state._id,
status: state._id,
number: (incResult as any).object.sequence,
rank: calcRank(lastOne, undefined)
})

View File

@ -517,8 +517,8 @@ export async function convert (
}
if (existing !== undefined) {
if (existing.state !== state?._id) {
update.state = state._id
if (existing.status !== state?._id) {
update.status = state._id
}
if (Object.keys(update).length > 0) {
await ops.update(existing, update)

View File

@ -52,7 +52,7 @@
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const value: AttachedData<BoardCard> = {
state: state._id,
status: state._id,
doneState: null,
number: (incResult as any).object.sequence,
title,

View File

@ -63,7 +63,7 @@
let checklists: TodoItem[] = []
const mixins: Mixin<Doc>[] = []
const allowedCollections = ['labels']
const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'state', 'number', 'assignee', 'doneState']
const ignoreKeys = ['isArchived', 'location', 'title', 'description', 'status', 'number', 'assignee', 'doneState']
function change (field: string, value: any) {
if (object) {
@ -79,8 +79,8 @@
object = result[0]
})
$: object?.state &&
stateQuery.query(task.class.State, { _id: object.state }, (result) => {
$: object?.status &&
stateQuery.query(task.class.State, { _id: object.status }, (result) => {
state = result[0]
})
@ -128,7 +128,7 @@
<div class="flex fs-title flex-gap-1">
<span class="over-underline" on:click={handleMove}>{space?.name}</span>><span
class="over-underline"
on:click={handleMove}>{state?.title}</span
on:click={handleMove}>{state?.name}</span
>
</div>
</svelte:fragment>

View File

@ -125,7 +125,7 @@
}
)
$: listProvider.update(cards)
$: groupByDocs = groupBy(cards, 'state')
$: groupByDocs = groupBy(cards, 'status')
const getUpdateProps = (doc: Doc, category: CategoryType): DocumentUpdate<DocWithRank> | undefined => {
const groupValue =

View File

@ -23,7 +23,7 @@
{_class}
config={[
'title',
'state',
'status',
{
key: '',
presenter: tags.component.TagsPresenter,
@ -40,5 +40,5 @@
'modifiedOn'
]}
{options}
query={{ isArchived, state: { $in: states } }}
query={{ isArchived, status: { $in: states } }}
/>

View File

@ -40,7 +40,7 @@
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const value: AttachedData<BoardCard> = {
state: state._id,
status: state._id,
doneState: null,
number: (incResult as any).object.sequence,
title,

View File

@ -24,7 +24,7 @@
let status: Status = OK
const selected = {
space: value.space,
state: value.state,
status: value.status,
rank: value.rank
}
@ -39,7 +39,7 @@
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const copy: AttachedData<Card> = {
state: selected.state,
status: selected.status,
doneState: null,
number: (incResult as any).object.sequence,
title,
@ -119,18 +119,18 @@
<Label label={board.string.List} />
</div>
{#key selected.space}
<StateSelect label={board.string.List} object={value} space={selected.space} bind:selected={selected.state} />
<StateSelect label={board.string.List} object={value} space={selected.space} bind:selected={selected.status} />
{/key}
</div>
<div class="w-full flex ml-2">
<div style:flex-basis="10%" class="text-md">
<Label label={board.string.Position} />
</div>
{#key selected.state}
{#key selected.status}
<RankSelect
label={board.string.Position}
object={value}
state={selected.state}
state={selected.status}
bind:selected={selected.rank}
isCopying={true}
/>

View File

@ -19,7 +19,7 @@
let status: Status = OK
const selected = {
space: value.space,
state: value.state,
status: value.status,
rank: value.rank
}
@ -30,7 +30,7 @@
update.space = selected.space
}
if (selected.state !== value.state) update.state = selected.state
if (selected.status !== value.status) update.status = selected.status
if (selected.rank !== value.rank) update.rank = selected.rank
client.update(value, update)
dispatch('close')
@ -60,7 +60,7 @@
<Popup
label={board.string.MoveCard}
canSave={status === OK && (value.state !== selected.state || value.rank !== selected.rank)}
canSave={status === OK && (value.status !== selected.status || value.rank !== selected.rank)}
okAction={move}
okLabel={board.string.Move}
on:close={() => {
@ -82,15 +82,15 @@
<Label label={board.string.List} />
</div>
{#key selected.space}
<StateSelect label={board.string.List} object={value} space={selected.space} bind:selected={selected.state} />
<StateSelect label={board.string.List} object={value} space={selected.space} bind:selected={selected.status} />
{/key}
</div>
<div class="w-full flex ml-2">
<div style:flex-basis="10%" class="text-md">
<Label label={board.string.Position} />
</div>
{#key selected.state}
<RankSelect label={board.string.Position} object={value} state={selected.state} bind:selected={selected.rank} />
{#key selected.status}
<RankSelect label={board.string.Position} object={value} state={selected.status} bind:selected={selected.rank} />
{/key}
</div>
</Popup>

View File

@ -17,7 +17,7 @@
const tasksQuery = createQuery()
tasksQuery.query(
board.class.Card,
{ state, isArchived: { $nin: [true] } },
{ status: state, isArchived: { $nin: [true] } },
async (result) => {
const filteredResult = isCopying ? result : result.filter((t) => t._id !== object._id)
@ -28,7 +28,7 @@
let selectedRank = ranks.slice(-1)[0].id
if (object.state === state) {
if (object.status === state) {
const index = result.findIndex((t) => t._id === object._id)
if (index !== -1) {

View File

@ -19,16 +19,16 @@
{ space, isArchived: { $nin: [true] } },
async (result) => {
if (!result) return
states = result.map(({ _id, title }) => ({ id: _id, label: title }))
states = result.map(({ _id, name }) => ({ id: _id, label: name }))
;[{ _id: selected }] = result
if (object.space === space) {
const index = states.findIndex(({ id }) => id === object.state)
const index = states.findIndex(({ id }) => id === object.status)
states[index].label = await translate(
board.string.Current,
{ label: states[index].label },
$themeStore.language
)
selected = object.state
selected = object.status
}
},
{ sort: { rank: SortingOrder.Ascending } }

View File

@ -44,7 +44,7 @@ async function ConvertToCard (object: TodoItem): Promise<void> {
const client = getClient()
const todoItemCard = await getCardFromTodoItem(client, object)
if (todoItemCard === undefined) return
await createCard(client, todoItemCard.space, todoItemCard.state, {
await createCard(client, todoItemCard.space, todoItemCard.status, {
title: object.name,
assignee: object.assignee,
dueDate: object.dueTo

View File

@ -16,7 +16,7 @@ import board from '../plugin'
export async function createCard (
client: Client,
space: Ref<Space>,
state: Ref<State>,
status: Ref<State>,
attribues: Partial<AttachedData<Card>>
): Promise<Ref<Card>> {
const sequence = await client.findOne(task.class.Sequence, { attachedTo: board.class.Card })
@ -29,7 +29,7 @@ export async function createCard (
const value: AttachedData<Card> = {
title: '',
state,
status,
doneState: null,
startDate: null,
dueDate: null,

View File

@ -15,11 +15,11 @@
//
import { Employee } from '@hcengineering/contact'
import type { Class, Doc, Markup, Ref, Type } from '@hcengineering/core'
import type { Class, Doc, Markup, Ref, Timestamp, Type } from '@hcengineering/core'
import type { Asset, IntlString, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { Preference } from '@hcengineering/preference'
import type { DoneState, KanbanTemplateSpace, SpaceWithStates, Task } from '@hcengineering/task'
import type { DoneState, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task'
import type { AnyComponent } from '@hcengineering/ui'
import { Action, ActionCategory } from '@hcengineering/view'
import { TagCategory } from '@hcengineering/tags'
@ -63,9 +63,8 @@ export interface Card extends Task {
location?: string
cover?: CardCover | null
comments?: number
attachments?: number
status: Ref<State>
startDate: Timestamp | null
}
/**

View File

@ -67,7 +67,7 @@
const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true)
const value: AttachedData<Lead> = {
state: state._id,
status: state._id,
doneState: null,
number: (incResult as any).object.sequence,
title,

View File

@ -42,7 +42,7 @@
<Scroller horizontal>
<Table
_class={lead.class.Lead}
config={['', 'state', 'doneState']}
config={['', 'status', 'doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>
@ -50,7 +50,7 @@
{:else}
<Table
_class={lead.class.Lead}
config={['', 'state', 'doneState']}
config={['', 'status', 'doneState']}
query={{ attachedTo: objectId }}
{loadingProps}
/>

View File

@ -21,4 +21,4 @@
export let value: Customer
</script>
<Table _class={leads.class.Lead} config={['', 'state', 'doneState']} query={{ attachedTo: value._id }} />
<Table _class={leads.class.Lead} config={['', 'status', 'doneState']} query={{ attachedTo: value._id }} />

View File

@ -15,11 +15,11 @@
//
import type { Contact } from '@hcengineering/contact'
import type { Class, Doc, Ref } from '@hcengineering/core'
import type { Class, Doc, Ref, Timestamp } from '@hcengineering/core'
import { Mixin } from '@hcengineering/core'
import type { Asset, Plugin } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@hcengineering/task'
import type { KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task'
/**
* @public
@ -44,11 +44,9 @@ export interface Customer extends Contact {
*/
export interface Lead extends Task {
attachedTo: Ref<Customer>
status: Ref<State>
startDate: Timestamp | null
title: string
comments?: number
attachments?: number
}
/**

View File

@ -33,7 +33,7 @@
<div class="popup-table">
<Table
_class={recruit.class.Applicant}
config={['', '$lookup.space.name', '$lookup.space.company', 'state', 'doneState']}
config={['', '$lookup.space.name', '$lookup.space.company', 'status', 'doneState']}
query={{ attachedTo: value._id }}
loadingProps={{ length: value.applications ?? 0 }}
/>

View File

@ -78,7 +78,7 @@
$: _candidate = candidate
const doc: Applicant = {
state: '' as Ref<State>,
status: '' as Ref<State>,
doneState: null,
number: 0,
assignee,
@ -142,7 +142,7 @@
'applications',
{
...doc,
state: state._id,
status: state._id,
doneState: null,
number: (incResult as any).object.sequence,
assignee: doc.assignee,

View File

@ -89,14 +89,14 @@
</div>
{/if}
<div class="card-labels mb-2">
{#if groupByKey !== 'state' && enabledConfig(config, 'state')}
{#if groupByKey !== 'status' && enabledConfig(config, 'status')}
<StateRefPresenter
size={'small'}
kind={'link-bordered'}
shrink={1}
value={object.state}
onChange={(state) => {
client.update(object, { state })
value={object.status}
onChange={(status) => {
client.update(object, { status })
}}
/>
{/if}

View File

@ -69,7 +69,7 @@
const op = client.apply('application.states')
for (const a of selected) {
await moveToSpace(op, a, _space, { state: state._id, doneState: null })
await moveToSpace(op, a, _space, { status: state._id, doneState: null })
}
await op.commit()
loading = false

View File

@ -27,7 +27,7 @@
const options: FindOptions<Applicant> = {
lookup: {
state: task.class.State,
status: task.class.State,
space: core.class.Space,
doneState: task.class.DoneState,
attachedTo: recruit.mixin.Candidate

View File

@ -23,7 +23,7 @@
const options: FindOptions<Applicant> = {
lookup: {
state: task.class.State,
status: task.class.State,
space: core.class.Space,
doneState: task.class.DoneState,
attachedTo: recruit.mixin.Candidate
@ -35,7 +35,7 @@
<div class="popup-table">
<Table
_class={recruit.class.Applicant}
config={['', 'attachedTo', 'state', 'doneState', 'modifiedOn']}
config={['', 'attachedTo', 'status', 'doneState', 'modifiedOn']}
{options}
query={{ ...(resultQuery ?? {}), space: { $in: value } }}
loadingProps={{ length: 0 }}

View File

@ -19,7 +19,7 @@ import type { AttachedData, AttachedDoc, Class, Doc, Mixin, Ref, Space, Timestam
import type { Asset, Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { TagReference } from '@hcengineering/tags'
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@hcengineering/task'
import type { KanbanTemplateSpace, SpaceWithStates, State, Task } from '@hcengineering/task'
import { AnyComponent, ResolvedLocation } from '@hcengineering/ui'
/**
@ -87,8 +87,8 @@ export interface CandidateDraft {
export interface Applicant extends Task {
space: Ref<Vacancy>
attachedTo: Ref<Candidate>
attachments?: number
comments?: number
status: Ref<State>
startDate: Timestamp | null
}
/**

View File

@ -117,7 +117,7 @@
})
)
for (const value of result) {
const group = template.get(value.state)
const group = template.get(value.status)
if (group === undefined) continue
if (value.doneState === null) {
group.values[0].value++
@ -127,13 +127,13 @@
const index = won ? 1 : 2
group.values[index].value++
}
template.set(value.state, group)
template.set(value.status, group)
}
items = Array.from(template.values())
},
{
projection: {
state: 1,
status: 1,
doneState: 1
}
}

View File

@ -43,7 +43,7 @@
function updateConfig (config: string[]): string[] {
if (state !== undefined) {
return config.filter((p) => p !== 'state')
return config.filter((p) => p !== 'status')
}
if (selectedDoneStates.size === 1) {
return config.filter((p) => p !== 'doneState')
@ -85,7 +85,7 @@
resConfig = updateConfig(config)
const result = client.getHierarchy().clone(query)
if (state) {
result.state = state
result.status = state
}
if (selectedDoneStates.size > 0) {
result.doneState = {

View File

@ -55,7 +55,7 @@
return contact.class.Employee
}
const taskKeys = ['state', 'assignee', 'doneState']
const taskKeys = ['status', 'assignee', 'doneState']
$: filtredKeys = keys.filter((p) => !taskKeys.includes(p.key)) // todo
</script>
@ -76,7 +76,7 @@
<AttributesBar {object} _class={object._class} keys={filtredKeys} />
</div>
</div>
<AttributesBar {object} _class={object._class} keys={['doneState', 'state']} showHeader={false} />
<AttributesBar {object} _class={object._class} keys={['doneState', 'status']} showHeader={false} />
</div>
{:else}
<DocAttributeBar {object} {ignoreKeys} {mixins} {allowedCollections} on:update />

View File

@ -145,7 +145,7 @@
lookup: {
...options?.lookup,
space: task.class.SpaceWithStates,
state: task.class.State,
status: task.class.State,
doneState: task.class.DoneState
},
sort: {

View File

@ -63,7 +63,7 @@
if (hierarchy.isDerived(state._class, task.class.DoneState)) {
query = { doneState: state._id }
} else {
query = { state: state._id }
query = { status: state._id }
}
const objectsInThisState = await client.findAll(containingClass, query)

View File

@ -79,12 +79,13 @@ export interface LostState extends DoneState {}
* @public
*/
export interface Task extends AttachedDoc, DocWithRank {
state: Ref<State>
status: Ref<Status>
doneState: Ref<DoneState> | null
number: number
assignee: Ref<Employee> | null
dueDate: Timestamp | null
startDate: Timestamp | null
comments?: number
attachments?: number
todoItems?: number
labels?: number
}

View File

@ -359,6 +359,7 @@
)
const value: AttachedData<Issue> = {
doneState: null,
title: getTitle(object.title),
description: object.description,
assignee: object.assignee,

View File

@ -77,6 +77,7 @@
)
const childId = subIssue._id
const cvalue: AttachedData<Issue> = {
doneState: null,
title: subIssue.title.trim(),
description: subIssue.description,
assignee: subIssue.assignee,

View File

@ -33,6 +33,7 @@ import {
} from '@hcengineering/core'
import { Asset, IntlString, Plugin, Resource, plugin } from '@hcengineering/platform'
import { TagCategory, TagElement, TagReference } from '@hcengineering/tags'
import { Task } from '@hcengineering/task'
import { AnyComponent, Location, ResolvedLocation } from '@hcengineering/ui'
import { Action, ActionCategory, IconProps } from '@hcengineering/view'
@ -133,15 +134,13 @@ export interface Milestone extends Doc {
/**
* @public
*/
export interface Issue extends AttachedDoc {
export interface Issue extends Task {
attachedTo: Ref<Issue>
title: string
description: Markup
status: Ref<IssueStatus>
priority: IssuePriority
number: number
assignee: Ref<Employee> | null
component: Ref<Component> | null
// For subtasks
@ -150,16 +149,8 @@ export interface Issue extends AttachedDoc {
relations?: RelatedDocument[]
parents: IssueParentInfo[]
comments: number
attachments?: number
labels?: number
space: Ref<Project>
dueDate: Timestamp | null
rank: string
milestone?: Ref<Milestone> | null
// Estimation in man days

View File

@ -14,37 +14,8 @@
//
import core, { ApplyOperations, SortingOrder, Status, TxOperations, generateId } from '@hcengineering/core'
import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
/**
* @public
*/
export const genRanks = (count: number): Generator<string, void, unknown> =>
(function * () {
const sys = new LexoNumeralSystem36()
const base = 36
const max = base ** 6
const gap = LexoDecimal.parse(Math.trunc(max / (count + 2)).toString(base), sys)
let cur = LexoDecimal.parse('0', sys)
for (let i = 0; i < count; i++) {
cur = cur.add(gap)
yield new LexoRank(LexoRankBucket.BUCKET_0, cur).toString()
}
})()
/**
* @public
*/
export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => {
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
if (a.equals(b)) {
return a.genNext().toString()
}
return a.between(b).toString()
}
import { genRanks } from '@hcengineering/task'
export { calcRank, genRanks } from '@hcengineering/task'
/**
* Generates statuses for provided space.

View File

@ -50,13 +50,13 @@
async function move (doc: Doc): Promise<void> {
const needStates = currentSpace ? hierarchy.isDerived(currentSpace._class, task.class.SpaceWithStates) : false
if (needStates) {
const state = await client.findOne(task.class.State, { space: doc.space })
if (state === undefined) {
throw new Error('Move: state not found')
const status = await client.findOne(task.class.State, { space: doc.space })
if (status === undefined) {
throw new Error('Move: status not found')
}
const lastOne = await client.findOne((doc as Task)._class, {}, { sort: { rank: SortingOrder.Descending } })
await moveToSpace(client, doc, space, {
state: state._id,
status: status._id,
rank: calcRank(lastOne, undefined)
})
} else {