From 2422baa108e3a26cd7048a96b4100b3c492a9f96 Mon Sep 17 00:00:00 2001 From: Denis Bykhov Date: Mon, 4 Sep 2023 23:06:34 +0600 Subject: [PATCH] UBER-808 (#3656) Signed-off-by: Denis Bykhov --- dev/generator/src/kanban.ts | 41 +-- dev/generator/src/recruit.ts | 14 +- models/board/src/index.ts | 3 +- models/board/src/migration.ts | 54 ++- models/board/src/plugin.ts | 6 +- models/core/src/status.ts | 10 +- models/lead/src/index.ts | 12 +- models/lead/src/migration.ts | 54 ++- models/lead/src/plugin.ts | 8 +- models/task/src/index.ts | 19 +- models/task/src/migration.ts | 158 ++++++++- models/tracker/src/index.ts | 4 - models/tracker/src/migration.ts | 24 +- models/view/src/index.ts | 41 +-- packages/core/src/status.ts | 28 +- plugins/bitrix/src/hr.ts | 9 +- .../board-resources/src/utils/BoardUtils.ts | 21 +- .../src/components/MessagesBrowser.svelte | 5 +- .../src/components/Contacts.svelte | 1 + plugins/contact-resources/src/utils.ts | 14 +- .../src/components/MyLeads.svelte | 2 +- plugins/lead/src/index.ts | 1 + .../src/components/CreateApplication.svelte | 20 +- .../src/components/CreateVacancy.svelte | 16 +- .../src/components/KanbanCard.svelte | 1 + .../src/components/MoveApplication.svelte | 18 +- .../src/components/Organizations.svelte | 1 + .../src/components/Vacancies.svelte | 1 + .../src/components/AssignedTasks.svelte | 2 +- .../src/components/CreateStatePopup.svelte | 78 +++-- .../CreateStateTemplatePopup.svelte | 84 +++++ .../src/components/StatusTableView.svelte | 66 ++-- .../components/kanban/KanbanDragDone.svelte | 29 +- .../src/components/kanban/KanbanEditor.svelte | 77 ----- .../kanban/KanbanTemplateEditor.svelte | 17 +- .../src/components/kanban/KanbanView.svelte | 41 +-- .../components/state/DoneStateEditor.svelte | 2 +- .../state/DoneStateRefPresenter.svelte | 5 +- .../components/state/DoneStatesPopup.svelte | 35 +- .../src/components/state/EditStatuses.svelte | 111 ++++--- .../src/components/state/StateEditor.svelte | 17 +- .../components/state/StateRefPresenter.svelte | 7 +- .../src/components/state/StatesBar.svelte | 19 +- .../src/components/state/StatesEditor.svelte | 4 +- .../src/components/state/StatesPopup.svelte | 27 +- .../state/StatesTemplateEditor.svelte | 313 ++++++++++++++++++ plugins/task-resources/src/index.ts | 4 +- plugins/task/src/index.ts | 107 +----- plugins/task/src/utils.ts | 124 ++++++- .../SetParentIssueActionPopup.svelte | 2 +- .../src/components/activity/StatusIcon.svelte | 10 +- .../components/ComponentBrowser.svelte | 1 + .../src/components/issues/IssueItem.svelte | 2 +- .../issues/IssueNotification.svelte | 19 +- .../issues/IssueStatusActivity.svelte | 4 +- .../components/issues/IssueStatusIcon.svelte | 27 +- .../src/components/issues/Issues.svelte | 5 +- .../src/components/issues/IssuesView.svelte | 8 +- .../src/components/issues/KanbanView.svelte | 3 +- .../src/components/issues/Move.svelte | 62 ++-- .../src/components/issues/StatusEditor.svelte | 27 +- .../issues/StatusFilterValuePresenter.svelte | 8 +- .../components/issues/StatusPresenter.svelte | 6 +- .../issues/StatusRefPresenter.svelte | 4 +- .../issues/edit/SubIssueSelector.svelte | 4 +- .../issues/edit/SubIssuesSelector.svelte | 20 +- .../issues/move/SelectReplacement.svelte | 24 +- .../issues/move/StatusMovePresenter.svelte | 5 +- .../issues/move/StatusReplacementPopup.svelte | 7 +- .../related/RelatedIssuePresenter.svelte | 4 +- .../related/RelatedIssueSelector.svelte | 3 +- .../milestones/IssueStatistics.svelte | 4 +- .../milestones/MilestoneBrowser.svelte | 1 + .../components/projects/CreateProject.svelte | 22 +- .../templates/IssueTemplatesView.svelte | 1 + .../components/workflow/RemoveStatus.svelte | 36 +- .../workflow/StatusPresenter.svelte | 6 +- .../src/components/workflow/Statuses.svelte | 110 +++--- plugins/tracker-resources/src/utils.ts | 86 +++-- plugins/tracker/src/utils.ts | 30 +- .../src/components/Table.svelte | 2 +- .../src/components/filter/FilterBar.svelte | 5 +- .../components/filter/FilterSection.svelte | 9 +- .../components/filter/FilterTypePopup.svelte | 2 +- .../src/components/filter/StatusFilter.svelte | 204 ++++++++++++ .../src/components/list/ListCategories.svelte | 9 +- .../status/StatusRefPresenter.svelte | 2 +- plugins/view-resources/src/index.ts | 9 +- plugins/view-resources/src/plugin.ts | 8 +- plugins/view-resources/src/status.ts | 293 +--------------- plugins/view-resources/src/utils.ts | 73 +--- plugins/view-resources/src/viewOptions.ts | 7 +- plugins/view/src/index.ts | 9 +- .../src/components/SpaceBrowser.svelte | 2 +- .../src/components/SpaceContent.svelte | 1 + .../src/components/SpecialView.svelte | 1 + server-plugins/task-resources/src/index.ts | 4 +- 97 files changed, 1678 insertions(+), 1268 deletions(-) create mode 100644 plugins/task-resources/src/components/CreateStateTemplatePopup.svelte delete mode 100644 plugins/task-resources/src/components/kanban/KanbanEditor.svelte create mode 100644 plugins/task-resources/src/components/state/StatesTemplateEditor.svelte create mode 100644 plugins/view-resources/src/components/filter/StatusFilter.svelte diff --git a/dev/generator/src/kanban.ts b/dev/generator/src/kanban.ts index 5e6f13f0e5..5f66f51303 100644 --- a/dev/generator/src/kanban.ts +++ b/dev/generator/src/kanban.ts @@ -1,12 +1,12 @@ import { MeasureContext, Ref, TxOperations } from '@hcengineering/core' -import task, { DoneState, genRanks, Kanban, SpaceWithStates, State } from '@hcengineering/task' +import task, { DoneState, SpaceWithStates, State } from '@hcengineering/task' import { findOrUpdate } from './utils' export async function createUpdateSpaceKanban ( ctx: MeasureContext, spaceId: Ref, client: TxOperations -): Promise[]> { +): Promise<[Ref[], Ref[]]> { const rawStates = [ { color: 9, name: 'Initial' }, { color: 10, name: 'Intermidiate' }, @@ -15,55 +15,34 @@ export async function createUpdateSpaceKanban ( { color: 11, name: 'Invalid' } ] const states: Array> = [] - const stateRanks = genRanks(rawStates.length) for (const st of rawStates) { - const rank = stateRanks.next().value - - if (rank === undefined) { - console.error('Failed to generate rank') - break - } - const sid = ('generated-' + spaceId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref await ctx.with('find-or-update', {}, (ctx) => - findOrUpdate(ctx, client, spaceId, task.class.State, sid, { + findOrUpdate(ctx, client, task.space.Statuses, task.class.State, sid, { ofAttribute: task.attribute.State, name: st.name, - color: st.color, - rank + color: st.color }) ) states.push(sid) } + const done: Array> = [] + const doneStates = [ { class: task.class.WonState, name: 'Won' }, { class: task.class.LostState, name: 'Lost' } ] - const doneStateRanks = genRanks(doneStates.length) for (const st of doneStates) { - const rank = doneStateRanks.next().value - - if (rank === undefined) { - console.error('Failed to generate rank') - break - } - const sid = `generated-${spaceId}.done-state.${st.name.toLowerCase().replace(' ', '_')}` as Ref await ctx.with('gen-done-state', {}, (ctx) => - findOrUpdate(ctx, client, spaceId, st.class, sid, { + findOrUpdate(ctx, client, task.space.Statuses, st.class, sid, { ofAttribute: task.attribute.DoneState, - name: st.name, - rank + name: st.name }) ) + done.push(sid) } - - await ctx.with('create-kanban', {}, (ctx) => - findOrUpdate(ctx, client, spaceId, task.class.Kanban, ('generated-' + spaceId + '.kanban') as Ref, { - attachedTo: spaceId - }) - ) - return states + return [states, done] } diff --git a/dev/generator/src/recruit.ts b/dev/generator/src/recruit.ts index 27e6c398fa..8f6ad02bc8 100644 --- a/dev/generator/src/recruit.ts +++ b/dev/generator/src/recruit.ts @@ -1,4 +1,4 @@ -import contact, { Channel, PersonAccount, Person, Employee } from '@hcengineering/contact' +import contact, { Channel, Employee, Person, PersonAccount } from '@hcengineering/contact' import core, { AttachedData, Data, @@ -87,6 +87,10 @@ async function genVacansyApplicants ( candidates: Ref[], emoloyeeIds: Ref[] ): Promise { + const [states, doneStates] = await ctx.with('create-kanbad', {}, (ctx) => + createUpdateSpaceKanban(ctx, vacancyId, client) + ) + const vacancy: Data = { name: faker.company.companyName(), description: faker.lorem.sentences(2), @@ -95,7 +99,9 @@ async function genVacansyApplicants ( members: accountIds, number: faker.datatype.number(), private: false, - archived: false + archived: false, + states, + doneStates } const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref @@ -125,10 +131,6 @@ async function genVacansyApplicants ( console.log('Vacancy attachments generated', vacancy.name) - const states = await ctx.with('create-kanbad', {}, (ctx) => createUpdateSpaceKanban(ctx, vacancyId, client)) - - console.log('States generated', vacancy.name) - const applicantsForCount = options.applicants.min + faker.datatype.number(options.applicants.max) const applicantsFor = faker.random.arrayElements(candidates, applicantsForCount) diff --git a/models/board/src/index.ts b/models/board/src/index.ts index e807b4e6a0..70b40261ed 100644 --- a/models/board/src/index.ts +++ b/models/board/src/index.ts @@ -265,8 +265,7 @@ export function createModel (builder: Builder): void { core.space.Model, { ofAttribute: task.attribute.DoneState, - name: board.string.Completed, - rank: '0' + name: board.string.Completed }, board.state.Completed ) diff --git a/models/board/src/migration.ts b/models/board/src/migration.ts index a4f551d538..bd66670296 100644 --- a/models/board/src/migration.ts +++ b/models/board/src/migration.ts @@ -14,31 +14,14 @@ // import { Ref, TxOperations } from '@hcengineering/core' -import { createOrUpdate, MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' +import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' import core from '@hcengineering/model-core' import { createKanbanTemplate, createSequence } from '@hcengineering/model-task' import tags from '@hcengineering/tags' -import task, { createKanban, KanbanTemplate } from '@hcengineering/task' +import task, { KanbanTemplate, createStates } from '@hcengineering/task' import board from './plugin' async function createSpace (tx: TxOperations): Promise { - const current = await tx.findOne(core.class.Space, { - _id: board.space.DefaultBoard - }) - if (current === undefined) { - await tx.createDoc( - board.class.Board, - core.space.Space, - { - name: 'Default', - description: 'Default board', - private: false, - archived: false, - members: [] - }, - board.space.DefaultBoard - ) - } const currentTemplate = await tx.findOne(core.class.Space, { _id: board.space.BoardTemplates }) @@ -58,6 +41,28 @@ async function createSpace (tx: TxOperations): Promise { board.space.BoardTemplates ) } + + const current = await tx.findOne(core.class.Space, { + _id: board.space.DefaultBoard + }) + if (current === undefined) { + const defaultTmpl = await createDefaultKanbanTemplate(tx) + const [states, doneStates] = await createStates(tx, defaultTmpl) + await tx.createDoc( + board.class.Board, + core.space.Space, + { + name: 'Default', + description: 'Default board', + private: false, + archived: false, + members: [], + states, + doneStates + }, + board.space.DefaultBoard + ) + } } async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { @@ -80,20 +85,9 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise { - const current = await tx.findOne(task.class.Kanban, { - attachedTo: board.space.DefaultBoard - }) - if (current !== undefined) return - const defaultTmpl = await createDefaultKanbanTemplate(tx) - await createKanban(tx, board.space.DefaultBoard, defaultTmpl) -} - async function createDefaults (tx: TxOperations): Promise { await createSpace(tx) await createSequence(tx, board.class.Card) - await createDefaultKanban(tx) await createOrUpdate( tx, tags.class.TagCategory, diff --git a/models/board/src/plugin.ts b/models/board/src/plugin.ts index 5ec0b2b9af..31acbe36d9 100644 --- a/models/board/src/plugin.ts +++ b/models/board/src/plugin.ts @@ -14,9 +14,9 @@ // limitations under the License. // -import { boardId } from '@hcengineering/board' +import { Board, boardId } from '@hcengineering/board' import board from '@hcengineering/board-resources/src/plugin' -import type { Ref, Space } from '@hcengineering/core' +import type { Ref } from '@hcengineering/core' import { IntlString, mergeIds } from '@hcengineering/platform' import { KanbanTemplate, Sequence } from '@hcengineering/task' import type { AnyComponent } from '@hcengineering/ui' @@ -42,7 +42,7 @@ export default mergeIds(boardId, board, { CardCoverEditor: '' as AnyComponent }, space: { - DefaultBoard: '' as Ref + DefaultBoard: '' as Ref }, template: { DefaultBoard: '' as Ref diff --git a/models/core/src/status.ts b/models/core/src/status.ts index 7ec8002365..12f3768b0e 100644 --- a/models/core/src/status.ts +++ b/models/core/src/status.ts @@ -13,7 +13,7 @@ // limitations under the License. // -import { Attribute, DOMAIN_STATUS, DOMAIN_MODEL, Ref, Status, StatusCategory } from '@hcengineering/core' +import { Attribute, DOMAIN_MODEL, DOMAIN_STATUS, Ref, Status, StatusCategory } from '@hcengineering/core' import { Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model' import { Asset, IntlString } from '@hcengineering/platform' import core from './component' @@ -22,13 +22,13 @@ import { TDoc } from './core' // S T A T U S @Model(core.class.Status, core.class.Doc, DOMAIN_STATUS) -@UX(core.string.Status) +@UX(core.string.Status, undefined, undefined, undefined, 'name') export class TStatus extends TDoc implements Status { // We attach to attribute, so we could distinguish between ofAttribute!: Ref> @Prop(TypeRef(core.class.StatusCategory), core.string.StatusCategory) - category!: Ref + category?: Ref @Prop(TypeString(), core.string.Name) name!: string @@ -38,9 +38,6 @@ export class TStatus extends TDoc implements Status { @Prop(TypeString(), core.string.Description) description!: string - - // @Prop(TypeString(), core.string.Rank) - rank!: string } @Model(core.class.StatusCategory, core.class.Doc, DOMAIN_MODEL) @@ -48,7 +45,6 @@ export class TStatus extends TDoc implements Status { export class TStatusCategory extends TDoc implements StatusCategory { // We attach to attribute, so we could distinguish between ofAttribute!: Ref> - icon!: Asset label!: IntlString color!: number diff --git a/models/lead/src/index.ts b/models/lead/src/index.ts index d4bdbc44df..2fd6a7dd7b 100644 --- a/models/lead/src/index.ts +++ b/models/lead/src/index.ts @@ -35,16 +35,16 @@ import attachment from '@hcengineering/model-attachment' import chunter from '@hcengineering/model-chunter' import contact, { TContact } from '@hcengineering/model-contact' import core from '@hcengineering/model-core' +import { generateClassNotificationTypes } from '@hcengineering/model-notification' import task, { TSpaceWithStates, TTask, actionTemplates } from '@hcengineering/model-task' +import tracker from '@hcengineering/model-tracker' import view, { createAction, actionTemplates as viewTemplates } from '@hcengineering/model-view' import workbench from '@hcengineering/model-workbench' -import setting from '@hcengineering/setting' -import { ViewOptionsModel } from '@hcengineering/view' -import { generateClassNotificationTypes } from '@hcengineering/model-notification' import notification from '@hcengineering/notification' -import lead from './plugin' -import tracker from '@hcengineering/model-tracker' +import setting from '@hcengineering/setting' import { State } from '@hcengineering/task' +import { ViewOptionsModel } from '@hcengineering/view' +import lead from './plugin' export { leadId } from '@hcengineering/lead' export { leadOperation } from './migration' @@ -83,6 +83,8 @@ export class TLead extends TTask implements Lead { @Prop(TypeRef(task.class.State), task.string.TaskState, { _id: task.attribute.State }) declare status: Ref + + declare space: Ref } @Mixin(lead.mixin.Customer, contact.class.Contact) diff --git a/models/lead/src/migration.ts b/models/lead/src/migration.ts index e8eda401c8..060c3849f3 100644 --- a/models/lead/src/migration.ts +++ b/models/lead/src/migration.ts @@ -17,29 +17,11 @@ import { Ref, TxOperations } from '@hcengineering/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model' import core from '@hcengineering/model-core' import { createKanbanTemplate, createSequence } from '@hcengineering/model-task' -import task, { createKanban, KanbanTemplate } from '@hcengineering/task' -import lead from './plugin' +import task, { KanbanTemplate, createStates } from '@hcengineering/task' import { PaletteColorIndexes } from '@hcengineering/ui/src/colors' +import lead from './plugin' async function createSpace (tx: TxOperations): Promise { - const current = await tx.findOne(core.class.Space, { - _id: lead.space.DefaultFunnel - }) - if (current === undefined) { - await tx.createDoc( - lead.class.Funnel, - core.space.Space, - { - name: 'Funnel', - description: 'Default funnel', - private: false, - archived: false, - members: [] - }, - lead.space.DefaultFunnel - ) - } - const currentTemplate = await tx.findOne(core.class.Space, { _id: lead.space.FunnelTemplates }) @@ -59,6 +41,28 @@ async function createSpace (tx: TxOperations): Promise { lead.space.FunnelTemplates ) } + + const current = await tx.findOne(core.class.Space, { + _id: lead.space.DefaultFunnel + }) + if (current === undefined) { + const defaultTmpl = await createDefaultKanbanTemplate(tx) + const [states, doneStates] = await createStates(tx, defaultTmpl) + await tx.createDoc( + lead.class.Funnel, + core.space.Space, + { + name: 'Funnel', + description: 'Default funnel', + private: false, + archived: false, + members: [], + states, + doneStates + }, + lead.space.DefaultFunnel + ) + } } async function createDefaultKanbanTemplate (tx: TxOperations): Promise> { @@ -86,15 +90,6 @@ async function createDefaultKanbanTemplate (tx: TxOperations): Promise { - const current = await tx.findOne(task.class.Kanban, { - attachedTo: lead.space.DefaultFunnel - }) - if (current !== undefined) return - const defaultTmpl = await createDefaultKanbanTemplate(tx) - await createKanban(tx, lead.space.DefaultFunnel, defaultTmpl) -} - async function fixTemplateSpace (tx: TxOperations): Promise { const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: lead.space.FunnelTemplates }) if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) { @@ -105,7 +100,6 @@ async function fixTemplateSpace (tx: TxOperations): Promise { async function createDefaults (tx: TxOperations): Promise { await createSpace(tx) await createSequence(tx, lead.class.Lead) - await createDefaultKanban(tx) await fixTemplateSpace(tx) } diff --git a/models/lead/src/plugin.ts b/models/lead/src/plugin.ts index 227c3def19..d3933d974d 100644 --- a/models/lead/src/plugin.ts +++ b/models/lead/src/plugin.ts @@ -14,10 +14,10 @@ // limitations under the License. // -import type { Ref, Space } from '@hcengineering/core' -import { leadId } from '@hcengineering/lead' -import { NotificationGroup, NotificationType } from '@hcengineering/notification' +import type { Ref } from '@hcengineering/core' +import { Funnel, leadId } from '@hcengineering/lead' import lead from '@hcengineering/lead-resources/src/plugin' +import { NotificationGroup, NotificationType } from '@hcengineering/notification' import type { IntlString } from '@hcengineering/platform' import { mergeIds } from '@hcengineering/platform' import { KanbanTemplate } from '@hcengineering/task' @@ -45,7 +45,7 @@ export default mergeIds(leadId, lead, { NewItemsHeader: '' as AnyComponent }, space: { - DefaultFunnel: '' as Ref + DefaultFunnel: '' as Ref }, template: { DefaultFunnel: '' as Ref diff --git a/models/task/src/index.ts b/models/task/src/index.ts index 3ceb17f187..e5b1aa1b3d 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -15,9 +15,7 @@ import type { Employee, Person } 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 { Arr, Attribute, Class, Doc, Domain, IndexKind, Ref, Status, Timestamp } from '@hcengineering/core' import { Builder, Collection, @@ -34,6 +32,8 @@ import { TypeString, UX } from '@hcengineering/model' +import attachment from '@hcengineering/model-attachment' +import chunter from '@hcengineering/model-chunter' import core, { TAttachedDoc, TClass, TDoc, TSpace, TStatus } from '@hcengineering/model-core' import view, { createAction, template, actionTemplates as viewTemplates } from '@hcengineering/model-view' import {} from '@hcengineering/notification' @@ -42,7 +42,6 @@ import tags from '@hcengineering/tags' import { DoneState, DoneStateTemplate, - Kanban, KanbanCard, KanbanTemplate, KanbanTemplateSpace, @@ -146,16 +145,11 @@ export class TKanbanCard extends TClass implements KanbanCard { card!: AnyComponent } -@Model(task.class.Kanban, core.class.Doc, DOMAIN_KANBAN) -export class TKanban extends TDoc implements Kanban { - states!: Arr> - doneStates!: Arr> - attachedTo!: Ref -} - @Model(task.class.SpaceWithStates, core.class.Space) export class TSpaceWithStates extends TSpace { templateId!: Ref + states!: Arr> + doneStates!: Arr> } @Model(task.class.KanbanTemplateSpace, core.class.Space) @@ -171,7 +165,6 @@ export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace export class TStateTemplate extends TDoc implements StateTemplate { // We attach to attribute, so we could distinguish between ofAttribute!: Ref> - attachedTo!: Ref @Prop(TypeString(), task.string.StateTemplateTitle) @@ -187,7 +180,6 @@ export class TStateTemplate extends TDoc implements StateTemplate { export class TDoneStateTemplate extends TDoc implements DoneStateTemplate { // We attach to attribute, so we could distinguish between ofAttribute!: Ref> - attachedTo!: Ref @Prop(TypeString(), task.string.StateTemplateTitle) @@ -298,7 +290,6 @@ export function createModel (builder: Builder): void { TWonState, TLostState, TKanbanCard, - TKanban, TKanbanTemplateSpace, TStateTemplate, TDoneStateTemplate, diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts index 26dc0a189c..6b6ba82abd 100644 --- a/models/task/src/migration.ts +++ b/models/task/src/migration.ts @@ -13,12 +13,26 @@ // limitations under the License. // -import { Class, DOMAIN_TX, Doc, Domain, Ref, Space, TxOperations } from '@hcengineering/core' +import { + Class, + DOMAIN_STATUS, + DOMAIN_TX, + Doc, + Domain, + Ref, + Space, + Status, + TxCollectionCUD, + TxCreateDoc, + TxOperations, + TxUpdateDoc +} from '@hcengineering/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' -import core from '@hcengineering/model-core' +import core, { DOMAIN_SPACE } 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_VIEW } from '@hcengineering/model-view' +import { DoneStateTemplate, KanbanTemplate, StateTemplate, Task, genRanks } from '@hcengineering/task' +import view, { Filter, FilteredView } from '@hcengineering/view' import { DOMAIN_TASK } from '.' import task from './plugin' @@ -27,6 +41,8 @@ import task from './plugin' */ export const DOMAIN_STATE = 'state' as Domain +type OldStatus = Status & { rank: string } + /** * @public */ @@ -123,8 +139,29 @@ async function createDefaultSequence (tx: TxOperations): Promise { } } +async function createDefaultStatesSpace (tx: TxOperations): Promise { + const current = await tx.findOne(core.class.Space, { + _id: task.space.Statuses + }) + if (current === undefined) { + await tx.createDoc( + core.class.Space, + core.space.Space, + { + name: 'Statuses', + description: 'Internal space to store all Statuses', + members: [], + private: false, + archived: false + }, + task.space.Statuses + ) + } +} + async function createDefaults (tx: TxOperations): Promise { await createDefaultSequence(tx) + await createDefaultStatesSpace(tx) } async function renameState (client: MigrationClient): Promise { @@ -189,9 +226,122 @@ async function renameStatePrefs (client: MigrationUpgradeClient): Promise } } +async function migrateStatuses (client: MigrationClient): Promise { + const oldStatuses = await client.find(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }) + const newStatuses: Map = new Map() + const oldStatusesMap = new Map, Ref>() + oldStatuses.sort((a, b) => a.rank.localeCompare(b.rank)) + for (const oldStatus of oldStatuses) { + const name = oldStatus.name.toLowerCase().trim() + const mapId = `${oldStatus.ofAttribute}_${name}` + const current = newStatuses.get(mapId) + if (current !== undefined) { + oldStatusesMap.set(oldStatus._id, current._id) + if (client.hierarchy.isDerived(oldStatus._class, task.class.DoneState)) { + await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { doneStates: current._id } }) + } else { + await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { states: current._id } }) + } + } else { + newStatuses.set(mapId, oldStatus) + if (client.hierarchy.isDerived(oldStatus._class, task.class.DoneState)) { + await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { doneStates: oldStatus._id } }) + } else { + await client.update(DOMAIN_SPACE, { _id: oldStatus.space }, { $addToSet: { states: oldStatus._id } }) + } + } + } + if (oldStatusesMap.size > 0) { + const tasks = await client.find(DOMAIN_TASK, {}) + for (const task of tasks) { + const update: any = {} + const newStatus = oldStatusesMap.get(task.status) + if (newStatus !== undefined) { + update.status = newStatus + } + if (task.doneState != null) { + const newDoneStatus = oldStatusesMap.get(task.doneState) + if (newStatus !== undefined) { + update.doneState = newDoneStatus + } + } + if (Object.keys(update).length > 0) { + await client.update(DOMAIN_TASK, { _id: task._id }, update) + } + const txes = await client.find(DOMAIN_TX, { 'tx.objectId': task._id }) + for (const tx of txes) { + const update: any = {} + const ctx = tx as TxCollectionCUD + if (ctx.tx._class === core.class.TxCreateDoc) { + const createTx = ctx.tx as TxCreateDoc + const newStatus = oldStatusesMap.get(createTx.attributes.status) + if (newStatus !== undefined) { + update['tx.attributes.status'] = newStatus + } + if (createTx.attributes.doneState != null) { + const newDoneStatus = oldStatusesMap.get(createTx.attributes.doneState) + if (newStatus !== undefined) { + update['tx.attributes.doneState'] = newDoneStatus + } + } + } else if (ctx.tx._class === core.class.TxUpdateDoc) { + const updateTx = ctx.tx as TxUpdateDoc + if (updateTx.operations.status !== undefined) { + const newStatus = oldStatusesMap.get(updateTx.operations.status) + if (newStatus !== undefined) { + update['tx.operations.status'] = newStatus + } + } + if (updateTx.operations.doneState != null) { + const newDoneStatus = oldStatusesMap.get(updateTx.operations.doneState) + if (newStatus !== undefined) { + update['tx.operations.doneState'] = newDoneStatus + } + } + } + if (Object.keys(update).length > 0) { + await client.update(DOMAIN_TX, { _id: tx._id }, update) + } + } + } + + const descendants = client.hierarchy.getDescendants(task.class.Task) + const filters = await client.find(DOMAIN_VIEW, { + _class: view.class.FilteredView, + filterClass: { $in: descendants } + }) + for (const filter of filters) { + const filters = JSON.parse(filter.filters) as Filter[] + let changed = false + for (const filter of filters) { + if (['status, doneStatus'].includes(filter.key.key)) { + for (let index = 0; index < filter.value.length; index++) { + const val = filter.value[index] + const newVal = oldStatusesMap.get(val) + if (newVal !== undefined) { + filter.value[index] = newVal + changed = true + } + } + } + } + if (changed) { + await client.update(DOMAIN_VIEW, { _id: filter._id }, { filters: JSON.stringify(filters) }) + } + } + } + const toRemove = Array.from(oldStatusesMap.keys()) + for (const remove of toRemove) { + await client.delete(DOMAIN_STATUS, remove) + } + await client.update(DOMAIN_STATUS, { rank: { $exists: true } }, { $unset: { rank: '' } }) + await client.update(DOMAIN_STATUS, { space: { $ne: task.space.Statuses } }, { space: task.space.Statuses }) +} + export const taskOperation: MigrateOperation = { async migrate (client: MigrationClient): Promise { await renameState(client) + await migrateStatuses(client) }, async upgrade (client: MigrationUpgradeClient): Promise { const tx = new TxOperations(client, core.account.System) diff --git a/models/tracker/src/index.ts b/models/tracker/src/index.ts index 2b41cf917d..25e07f175e 100644 --- a/models/tracker/src/index.ts +++ b/models/tracker/src/index.ts @@ -958,10 +958,6 @@ export function createModel (builder: Builder): void { createAggregationManager: tracker.aggregation.CreateComponentAggregationManager }) - builder.mixin(tracker.class.Component, core.class.Class, view.mixin.Groupping, { - grouppingManager: tracker.aggregation.GrouppingComponentManager - }) - builder.mixin(tracker.class.Milestone, core.class.Class, view.mixin.ObjectPresenter, { presenter: tracker.component.MilestonePresenter }) diff --git a/models/tracker/src/migration.ts b/models/tracker/src/migration.ts index 2519236cfd..cc37e75938 100644 --- a/models/tracker/src/migration.ts +++ b/models/tracker/src/migration.ts @@ -13,13 +13,13 @@ // limitations under the License. // -import core, { Ref, TxOperations, generateId } from '@hcengineering/core' +import core, { TxOperations } from '@hcengineering/core' import { MigrateOperation, MigrationClient, MigrationUpgradeClient, createOrUpdate } from '@hcengineering/model' -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' +import tags from '@hcengineering/tags' +import { Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker' +import { DOMAIN_TRACKER } from '.' +import tracker from './plugin' async function createDefaultProject (tx: TxOperations): Promise { const current = await tx.findOne(tracker.class.Project, { @@ -32,7 +32,7 @@ async function createDefaultProject (tx: TxOperations): Promise { // Create new if not deleted by customers. if (current === undefined && currentDeleted === undefined) { - const defaultStatusId: Ref = generateId() + const states = await createStatuses(tx, tracker.class.IssueStatus, tracker.attribute.IssueStatus) await tx.createDoc( tracker.class.Project, @@ -45,19 +45,13 @@ async function createDefaultProject (tx: TxOperations): Promise { archived: false, identifier: 'TSK', sequence: 0, - defaultIssueStatus: defaultStatusId, + defaultIssueStatus: states[0], defaultTimeReportDay: TimeReportDayType.PreviousWorkDay, - defaultAssignee: undefined + defaultAssignee: undefined, + states }, tracker.project.DefaultProject ) - await createStatuses( - tx, - tracker.project.DefaultProject, - tracker.class.IssueStatus, - tracker.attribute.IssueStatus, - defaultStatusId - ) } } diff --git a/models/view/src/index.ts b/models/view/src/index.ts index 899281e20a..ea13852d02 100644 --- a/models/view/src/index.ts +++ b/models/view/src/index.ts @@ -13,27 +13,18 @@ // limitations under the License. // -import Core, { - DOMAIN_MODEL, - Account, - Class, - Client, - Data, - Doc, - DocumentQuery, - Domain, - Ref, - Space -} from '@hcengineering/core' +import { Account, Class, Client, DOMAIN_MODEL, Data, Doc, DocumentQuery, Domain, Ref, Space } from '@hcengineering/core' import { Builder, Mixin, Model } from '@hcengineering/model' import core, { TClass, TDoc } from '@hcengineering/model-core' import preference, { TPreference } from '@hcengineering/model-preference' +import presentation from '@hcengineering/model-presentation' import { Asset, IntlString, Resource, Status } from '@hcengineering/platform' import { AnyComponent, Location } from '@hcengineering/ui' import { Action, ActionCategory, ActivityAttributePresenter, + Aggregation, AllValuesFunc, ArrayEditor, AttributeEditor, @@ -45,10 +36,13 @@ import { ClassSortFuncs, CollectionEditor, CollectionPresenter, + CreateAggregationManagerFunc, Filter, FilterMode, FilteredView, GetAllValuesFunc, + Groupping, + GrouppingManagerResource, IgnoreActions, InlineAttributEditor, KeyBinding, @@ -79,13 +73,8 @@ import { ViewOptionsModel, Viewlet, ViewletDescriptor, - ViewletPreference, - Aggregation, - CreateAggregationManagerFunc, - GrouppingManagerResource, - Groupping + ViewletPreference } from '@hcengineering/view' -import presentation from '@hcengineering/model-presentation' import view from './plugin' export { viewId } from '@hcengineering/view' @@ -765,6 +754,10 @@ export function createModel (builder: Builder): void { component: view.component.ObjectFilter }) + builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributeFilter, { + component: view.component.StatusFilter + }) + builder.mixin(core.class.TypeTimestamp, core.class.Class, view.mixin.AttributeFilter, { component: view.component.DateFilter, group: 'bottom' @@ -1019,10 +1012,6 @@ export function createModel (builder: Builder): void { view.action.Open ) - builder.mixin(core.class.Status, core.class.Class, view.mixin.SortFuncs, { - func: view.function.StatusSort - }) - builder.mixin(core.class.Status, core.class.Class, view.mixin.ObjectPresenter, { presenter: view.component.StatusPresenter }) @@ -1030,14 +1019,6 @@ export function createModel (builder: Builder): void { builder.mixin(core.class.Status, core.class.Class, view.mixin.AttributePresenter, { presenter: view.component.StatusRefPresenter }) - - builder.mixin(Core.class.Status, core.class.Class, view.mixin.Aggregation, { - createAggregationManager: view.aggregation.CreateStatusAggregationManager - }) - - builder.mixin(Core.class.Status, core.class.Class, view.mixin.Groupping, { - grouppingManager: view.aggregation.GrouppingStatusManager - }) } export default view diff --git a/packages/core/src/status.ts b/packages/core/src/status.ts index 275b6fadfb..a9107643a9 100644 --- a/packages/core/src/status.ts +++ b/packages/core/src/status.ts @@ -15,8 +15,7 @@ import { Asset, IntlString } from '@hcengineering/platform' import { Attribute, Doc, Domain, Ref } from './classes' -import { AggregateValue, AggregateValueData, DocManager, IdMap } from './utils' -import { WithLookup } from './storage' +import { AggregateValue, AggregateValueData } from './utils' /** * @public @@ -53,8 +52,6 @@ export interface Status extends Doc { color?: number // Optional description description?: string - // Lexorank rank for ordering. - rank: string } /** @@ -69,26 +66,3 @@ export class StatusValue extends AggregateValue { super(name, values) } } - -/** - * @public - * - * Allow to query for status keys/values. - */ -export class StatusManager extends DocManager { - get (ref: Ref>): WithLookup | undefined { - return this.getIdMap().get(ref) as WithLookup - } - - getDocs (): Array> { - return this.docs as Status[] - } - - getIdMap (): IdMap> { - return this.byId as IdMap> - } - - filter (predicate: (value: Status) => boolean): Status[] { - return this.getDocs().filter(predicate) - } -} diff --git a/plugins/bitrix/src/hr.ts b/plugins/bitrix/src/hr.ts index e0a3359a0a..93123f85db 100644 --- a/plugins/bitrix/src/hr.ts +++ b/plugins/bitrix/src/hr.ts @@ -1,7 +1,7 @@ import { Organization } from '@hcengineering/contact' import core, { Account, Client, Data, Doc, Ref, SortingOrder, TxOperations } from '@hcengineering/core' import recruit, { Applicant, Vacancy } from '@hcengineering/recruit' -import task, { KanbanTemplate, State, calcRank, createKanban } from '@hcengineering/task' +import task, { KanbanTemplate, State, calcRank, createStates } from '@hcengineering/task' export async function createVacancy ( rawClient: Client, @@ -23,6 +23,8 @@ export async function createVacancy ( const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) + const [states, doneStates] = await createStates(client, templateId) + const id = await client.createDoc(recruit.class.Vacancy, core.space.Space, { name, description: template.shortDescription ?? '', @@ -31,10 +33,11 @@ export async function createVacancy ( archived: false, company, number: (incResult as any).object.sequence, - members: [] + members: [], + states, + doneStates }) - await createKanban(client, id, templateId) return id } diff --git a/plugins/board-resources/src/utils/BoardUtils.ts b/plugins/board-resources/src/utils/BoardUtils.ts index 814c4c96fd..58ac57696f 100644 --- a/plugins/board-resources/src/utils/BoardUtils.ts +++ b/plugins/board-resources/src/utils/BoardUtils.ts @@ -1,23 +1,23 @@ -import { readable } from 'svelte/store' import board, { Board, CommonBoardPreference } from '@hcengineering/board' -import core, { getCurrentAccount, Ref, TxOperations } from '@hcengineering/core' -import type { KanbanTemplate, TodoItem } from '@hcengineering/task' +import core, { Ref, TxOperations, getCurrentAccount } from '@hcengineering/core' import preference from '@hcengineering/preference' -import { createKanban } from '@hcengineering/task' import { createQuery, getClient } from '@hcengineering/presentation' +import type { KanbanTemplate, TodoItem } from '@hcengineering/task' +import { createStates } from '@hcengineering/task' import { + EastSideColor, + FeijoaColor, FernColor, FlamingoColor, MalibuColor, MediumTurquoiseColor, MoodyBlueColor, - SeaBuckthornColor, - FeijoaColor, - EastSideColor, SalmonColor, + SeaBuckthornColor, SeagullColor, areDatesEqual } from '@hcengineering/ui' +import { readable } from 'svelte/store' export async function createBoard ( client: TxOperations, @@ -25,16 +25,19 @@ export async function createBoard ( description: string, templateId?: Ref ): Promise> { + const [states, doneStates] = await createStates(client, templateId) + const boardRef = await client.createDoc(board.class.Board, core.space.Space, { name, description, private: false, archived: false, members: [getCurrentAccount()._id], - templateId + templateId, + states, + doneStates }) - await Promise.all([createKanban(client, boardRef, templateId)]) return boardRef } diff --git a/plugins/chunter-resources/src/components/MessagesBrowser.svelte b/plugins/chunter-resources/src/components/MessagesBrowser.svelte index c01dbfe143..52f0816e13 100644 --- a/plugins/chunter-resources/src/components/MessagesBrowser.svelte +++ b/plugins/chunter-resources/src/components/MessagesBrowser.svelte @@ -10,7 +10,7 @@ import MessageComponent from './Message.svelte' export let withHeader: boolean = true - + export let filterClass = chunter.class.ChunterMessage export let search: string = '' let searchQuery: DocumentQuery = { $search: search } @@ -22,7 +22,6 @@ $: updateSearchQuery(search) const client = getClient() - export let filterClass = chunter.class.ChunterMessage let messages: ChunterMessage[] = [] let resultQuery: DocumentQuery = { ...searchQuery } @@ -90,7 +89,7 @@ /> {/if} - (resultQuery = e.detail)} /> + (resultQuery = e.detail)} /> {#if messages.length > 0} {#each messages as message} diff --git a/plugins/contact-resources/src/components/Contacts.svelte b/plugins/contact-resources/src/components/Contacts.svelte index 0dfcaaf32c..3a9b94164b 100644 --- a/plugins/contact-resources/src/components/Contacts.svelte +++ b/plugins/contact-resources/src/components/Contacts.svelte @@ -91,6 +91,7 @@ (resultQuery = e.detail)} diff --git a/plugins/contact-resources/src/utils.ts b/plugins/contact-resources/src/utils.ts index aac669ee01..9feab8ed58 100644 --- a/plugins/contact-resources/src/utils.ts +++ b/plugins/contact-resources/src/utils.ts @@ -28,7 +28,17 @@ import { getName, Person } from '@hcengineering/contact' -import { Client, Doc, IdMap, ObjQueryType, Ref, Timestamp, getCurrentAccount, toIdMap } from '@hcengineering/core' +import { + Client, + Doc, + IdMap, + ObjQueryType, + Ref, + Timestamp, + TxOperations, + getCurrentAccount, + toIdMap +} from '@hcengineering/core' import notification, { DocUpdateTx, DocUpdates } from '@hcengineering/notification' import { getResource } from '@hcengineering/platform' import { createQuery, getClient } from '@hcengineering/presentation' @@ -48,7 +58,7 @@ export function formatDate (dueDateMs: Timestamp): string { }) } -export async function employeeSort (value: Array>): Promise>> { +export async function employeeSort (client: TxOperations, value: Array>): Promise>> { const h = getClient().getHierarchy() return value.sort((a, b) => { const employeeId1 = a as Ref | null | undefined diff --git a/plugins/lead-resources/src/components/MyLeads.svelte b/plugins/lead-resources/src/components/MyLeads.svelte index 92adb013da..7fd5bc07f6 100644 --- a/plugins/lead-resources/src/components/MyLeads.svelte +++ b/plugins/lead-resources/src/components/MyLeads.svelte @@ -120,7 +120,7 @@ /> - (resultQuery = e.detail)} /> + (resultQuery = e.detail)} /> {#if viewlet} {#if loading} diff --git a/plugins/lead/src/index.ts b/plugins/lead/src/index.ts index 0c852871f4..20c49d2d2f 100644 --- a/plugins/lead/src/index.ts +++ b/plugins/lead/src/index.ts @@ -43,6 +43,7 @@ export interface Customer extends Contact { * @public */ export interface Lead extends Task { + space: Ref attachedTo: Ref status: Ref startDate: Timestamp | null diff --git a/plugins/recruit-resources/src/components/CreateApplication.svelte b/plugins/recruit-resources/src/components/CreateApplication.svelte index f3ee36ba50..80db49f188 100644 --- a/plugins/recruit-resources/src/components/CreateApplication.svelte +++ b/plugins/recruit-resources/src/components/CreateApplication.svelte @@ -40,7 +40,7 @@ getClient } from '@hcengineering/presentation' import type { Applicant, Candidate, Vacancy } from '@hcengineering/recruit' - import task, { State, calcRank } from '@hcengineering/task' + import task, { State, calcRank, getStates } from '@hcengineering/task' import ui, { Button, ColorPopup, @@ -60,6 +60,7 @@ import CandidateCard from './CandidateCard.svelte' import VacancyCard from './VacancyCard.svelte' import VacancyOrgPresenter from './VacancyOrgPresenter.svelte' + import { statusStore } from '@hcengineering/view-resources' export let space: Ref export let candidate: Ref @@ -108,10 +109,6 @@ if (selectedState === undefined) { throw new Error(`Please select initial state:${_space}`) } - const state = await client.findOne(task.class.State, { space: _space, _id: selectedState?._id }) - if (state === undefined) { - throw new Error(`create application: state not found space:${_space}`) - } const sequence = await client.findOne(task.class.Sequence, { attachedTo: recruit.class.Applicant }) if (sequence === undefined) { throw new Error('sequence object not found') @@ -142,7 +139,7 @@ 'applications', { ...doc, - status: state._id, + status: selectedState._id, doneState: null, number: (incResult as any).object.sequence, assignee: doc.assignee, @@ -193,21 +190,12 @@ let states: Array<{ id: number | string; color: number; label: string }> = [] let selectedState: State | undefined - let rawStates: State[] = [] - const statesQuery = createQuery() + $: rawStates = getStates(vacancy, $statusStore) const spaceQuery = createQuery() let vacancy: Vacancy | undefined $: if (_space) { - statesQuery.query( - task.class.State, - { space: _space }, - (res) => { - rawStates = res - }, - { sort: { rank: SortingOrder.Ascending } } - ) spaceQuery.query(recruit.class.Vacancy, { _id: _space }, (res) => { vacancy = res.shift() }) diff --git a/plugins/recruit-resources/src/components/CreateVacancy.svelte b/plugins/recruit-resources/src/components/CreateVacancy.svelte index efa07e9d46..af8c7e77fd 100644 --- a/plugins/recruit-resources/src/components/CreateVacancy.svelte +++ b/plugins/recruit-resources/src/components/CreateVacancy.svelte @@ -28,7 +28,7 @@ import { Card, createQuery, getClient, InlineAttributeBar, MessageBox } from '@hcengineering/presentation' import { Vacancy as VacancyClass } from '@hcengineering/recruit' import tags from '@hcengineering/tags' - import task, { createKanban, KanbanTemplate } from '@hcengineering/task' + import task, { createStates, KanbanTemplate } from '@hcengineering/task' import tracker, { calcRank, Issue, @@ -76,7 +76,8 @@ comments: 0, company: '' as Ref, fullDescription: '', - location: '' + location: '', + states: [] } export function canClose (): boolean { return name === '' && templateId !== undefined @@ -142,7 +143,8 @@ estimation: template.estimation, reports: 0, relations: [{ _id: id, _class: recruit.class.Vacancy }], - childInfo: [] + childInfo: [], + doneState: null }) if ((template.labels?.length ?? 0) > 0) { const tagElements = await client.findAll(tags.class.TagElement, { _id: { $in: template.labels } }) @@ -172,6 +174,8 @@ const incResult = await client.update(sequence, { $inc: { sequence: 1 } }, true) + const [states, doneStates] = await createStates(client, templateId) + const id = await client.createDoc( recruit.class.Vacancy, core.space.Space, @@ -185,7 +189,9 @@ number: (incResult as any).object.sequence, company, members: [getCurrentAccount()._id], - templateId + templateId, + states, + doneStates }, objectId ) @@ -199,8 +205,6 @@ } } - await createKanban(client, id, templateId) - await descriptionBox.createAttachments() objectId = generateId() diff --git a/plugins/recruit-resources/src/components/KanbanCard.svelte b/plugins/recruit-resources/src/components/KanbanCard.svelte index f9bce4c101..8df7f19e3f 100644 --- a/plugins/recruit-resources/src/components/KanbanCard.svelte +++ b/plugins/recruit-resources/src/components/KanbanCard.svelte @@ -93,6 +93,7 @@ { diff --git a/plugins/recruit-resources/src/components/MoveApplication.svelte b/plugins/recruit-resources/src/components/MoveApplication.svelte index 5e3f6d94e8..0bc826dddc 100644 --- a/plugins/recruit-resources/src/components/MoveApplication.svelte +++ b/plugins/recruit-resources/src/components/MoveApplication.svelte @@ -15,11 +15,11 @@ diff --git a/plugins/task-resources/src/components/CreateStateTemplatePopup.svelte b/plugins/task-resources/src/components/CreateStateTemplatePopup.svelte new file mode 100644 index 0000000000..0a1bb27eb7 --- /dev/null +++ b/plugins/task-resources/src/components/CreateStateTemplatePopup.svelte @@ -0,0 +1,84 @@ + + + + dispatch('close')} +> + + + {#if !canSave} + + diff --git a/plugins/task-resources/src/components/StatusTableView.svelte b/plugins/task-resources/src/components/StatusTableView.svelte index 6315c4f900..e660edd4be 100644 --- a/plugins/task-resources/src/components/StatusTableView.svelte +++ b/plugins/task-resources/src/components/StatusTableView.svelte @@ -14,16 +14,16 @@ // limitations under the License. --> - - diff --git a/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte b/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte index cb3b823e0e..373dde08af 100644 --- a/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte +++ b/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte @@ -14,21 +14,20 @@ // limitations under the License. --> - | undefined - $: currentSpace = space $: groupByKey = (viewOptions.groupBy[0] ?? noCategory) as TaskGrouping $: orderBy = viewOptions.orderBy $: sort = { [orderBy[0]]: orderBy[1] } @@ -88,13 +87,6 @@ accentColors = accentColors } - const spaceQuery = createQuery() - - let currentProject: SpaceWithStates | undefined - $: spaceQuery.query(task.class.SpaceWithStates, { _id: currentSpace }, (res) => { - currentProject = res.shift() - }) - let resultQuery: DocumentQuery = { ...query } $: getResultQuery(query, viewOptionsConfig, viewOptions).then((p) => (resultQuery = { ...p, ...query })) @@ -160,20 +152,21 @@ const queryId = generateId() - $: updateCategories(_class, tasks, groupByKey, viewOptions, viewOptionsConfig) + $: updateCategories(_class, space, tasks, groupByKey, viewOptions, viewOptionsConfig) function update () { - updateCategories(_class, tasks, groupByKey, viewOptions, viewOptionsConfig) + updateCategories(_class, space, tasks, groupByKey, viewOptions, viewOptionsConfig) } async function updateCategories ( _class: Ref>, + space: Ref | undefined, docs: Doc[], groupByKey: string, viewOptions: ViewOptions, viewOptionsModel: ViewOptionModel[] | undefined ) { - categories = await getCategories(client, _class, docs, groupByKey, viewlet.descriptor) + categories = await getCategories(client, _class, space, docs, groupByKey, viewlet.descriptor) for (const viewOption of viewOptionsModel ?? []) { if (viewOption.actionTarget !== 'category') continue const categoryFunc = viewOption as CategoryOption @@ -188,6 +181,7 @@ const res = await categoryAction( _class, spaces.length > 0 ? { space: { $in: Array.from(spaces.values()) } } : {}, + space, groupByKey, update, queryId, @@ -231,13 +225,6 @@ $: presenterMixin = client.getHierarchy().as(clazz, task.mixin.KanbanCard) $: cardPresenter = getResource(presenterMixin.card) - let kanban: Kanban - - const kanbanQuery = createQuery() - $: kanbanQuery.query(task.class.Kanban, { attachedTo: space }, (result) => { - kanban = result[0] - }) - const getDoneUpdate = (e: any) => ({ doneState: e.detail._id } as DocumentUpdate) @@ -301,13 +288,15 @@ - { - // eslint-disable-next-line no-undef - onDone(getDoneUpdate(e)) - }} - /> + {#if space} + { + // eslint-disable-next-line no-undef + onDone(getDoneUpdate(e)) + }} + /> + {/if} {/await} diff --git a/plugins/task-resources/src/components/state/DoneStateEditor.svelte b/plugins/task-resources/src/components/state/DoneStateEditor.svelte index b593898061..bbf37c0593 100644 --- a/plugins/task-resources/src/components/state/DoneStateEditor.svelte +++ b/plugins/task-resources/src/components/state/DoneStateEditor.svelte @@ -26,7 +26,7 @@ export let value: Ref | null | undefined export let onChange: (value: any) => void - export let space: Ref + export let space: Ref | undefined export let kind: ButtonKind = 'no-border' export let size: ButtonSize = 'small' export let justify: 'left' | 'center' = 'center' diff --git a/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte b/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte index 5ed3ee03fa..fc27e8c8f9 100644 --- a/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte +++ b/plugins/task-resources/src/components/state/DoneStateRefPresenter.svelte @@ -16,11 +16,12 @@ {#if spaceInstance?.name}{spaceInstance?.name}{/if} @@ -117,9 +150,13 @@
- {#if kanban !== undefined} - deleteState(e.detail)} /> - {/if} + deleteState(e.detail)} + on:move={(e) => onMove(e.detail.stateID, e.detail.position)} + />
diff --git a/plugins/task-resources/src/components/state/StateEditor.svelte b/plugins/task-resources/src/components/state/StateEditor.svelte index 2391652d4a..6df6629f16 100644 --- a/plugins/task-resources/src/components/state/StateEditor.svelte +++ b/plugins/task-resources/src/components/state/StateEditor.svelte @@ -15,10 +15,10 @@ -->
dispatch('changeContent')}> diff --git a/plugins/task-resources/src/components/state/StatesTemplateEditor.svelte b/plugins/task-resources/src/components/state/StatesTemplateEditor.svelte new file mode 100644 index 0000000000..2d5c4caafe --- /dev/null +++ b/plugins/task-resources/src/components/state/StatesTemplateEditor.svelte @@ -0,0 +1,313 @@ + + + +{#if spaceEditor} + +{/if} +
+
+
+ {#each states as state, i} + {@const color = getPlatformColorDef(state.color ?? getColorNumberByText(state.name), $themeStore.dark)} + {#if state} +
{ + dragover(ev, i) + }} + on:drop|preventDefault={() => { + onMove(i) + }} + on:dragstart={() => { + selected = i + dragState = states[i]._id + }} + on:dragend={() => { + selected = undefined + }} + > +
+ +
{ + showPopup(ColorsPopup, { selected: color.name }, elements[i], onColorChange(state)) + }} + /> +
+ +
+ +
{ + showPopup( + StatusesPopup, + { + onDelete: () => dispatch('delete', { state }), + showDelete: states.length > 1, + onUpdate: () => edit(state) + }, + eventToHTMLElement(ev), + () => {} + ) + }} + > + +
+
+ {/if} + {/each} +
+
+
+
+
+ {#each wonStates as state} + {@const color = getPlatformColorDef(PaletteColorIndexes.Crocodile, $themeStore.dark)} + {#if state} +
+
+
+ +
+
+ + +
+ +
{ + showPopup( + StatusesPopup, + { + onDelete: () => dispatch('delete', { state }), + showDelete: wonStates.length > 1, + onUpdate: () => edit(state) + }, + eventToHTMLElement(ev), + () => {} + ) + }} + > + +
+
+ {/if} + {/each} +
+
+
+
+
+
+ {#each lostStates as state} + {@const color = getPlatformColorDef(PaletteColorIndexes.Firework, $themeStore.dark)} + {#if state} +
+
+
+ +
+
+ + +
+ +
{ + showPopup( + StatusesPopup, + { + onDelete: () => dispatch('delete', { state }), + showDelete: lostStates.length > 1, + onUpdate: () => edit(state) + }, + eventToHTMLElement(ev), + () => {} + ) + }} + > + +
+
+ {/if} + {/each} +
+
+ + diff --git a/plugins/task-resources/src/index.ts b/plugins/task-resources/src/index.ts index 00b32d2ed4..3ae5453f4b 100644 --- a/plugins/task-resources/src/index.ts +++ b/plugins/task-resources/src/index.ts @@ -39,6 +39,7 @@ import DoneStateRefPresenter from './components/state/DoneStateRefPresenter.svel import StateRefPresenter from './components/state/StateRefPresenter.svelte' import DueDateEditor from './components/DueDateEditor.svelte' import CreateStatePopup from './components/CreateStatePopup.svelte' +import CreateStateTemplatePopup from './components/CreateStateTemplatePopup.svelte' export { default as AssigneePresenter } from './components/AssigneePresenter.svelte' export { StateRefPresenter } @@ -71,7 +72,8 @@ export default async (): Promise => ({ StateRefPresenter, TodoItemsPopup, DueDateEditor, - CreateStatePopup + CreateStatePopup, + CreateStateTemplatePopup }, actionImpl: { EditStatuses: editStatuses diff --git a/plugins/task/src/index.ts b/plugins/task/src/index.ts index a79c6db309..933d682dbe 100644 --- a/plugins/task/src/index.ts +++ b/plugins/task/src/index.ts @@ -25,15 +25,13 @@ import { Ref, Space, Status, - Timestamp, - TxOperations + Timestamp } from '@hcengineering/core' import { NotificationType } from '@hcengineering/notification' import type { Asset, IntlString, Plugin } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' import type { AnyComponent } from '@hcengineering/ui' import { Action, ViewletDescriptor } from '@hcengineering/view' -import { genRanks } from './utils' /** * @public @@ -47,6 +45,8 @@ export interface DocWithRank extends Doc { */ export interface SpaceWithStates extends Space { templateId?: Ref + states: Ref[] + doneStates?: Ref[] } // S T A T E @@ -61,9 +61,7 @@ export interface State extends Status { /** * @public */ -export interface DoneState extends Status { - name: string -} +export interface DoneState extends Status {} /** * @public @@ -108,13 +106,6 @@ export interface KanbanCard extends Class { card: AnyComponent } -/** - * @public - */ -export interface Kanban extends Doc { - attachedTo: Ref -} - /** * @public */ @@ -128,6 +119,7 @@ export interface Sequence extends Doc { */ export interface StateTemplate extends Doc, State { attachedTo: Ref + rank: string } /** @@ -135,6 +127,7 @@ export interface StateTemplate extends Doc, State { */ export interface DoneStateTemplate extends Doc, DoneState { attachedTo: Ref + rank: string } /** @@ -248,7 +241,6 @@ const task = plugin(taskId, { LostState: '' as Ref>, SpaceWithStates: '' as Ref>, Task: '' as Ref>, - Kanban: '' as Ref>, Sequence: '' as Ref>, StateTemplate: '' as Ref>, DoneStateTemplate: '' as Ref>, @@ -277,13 +269,15 @@ const task = plugin(taskId, { Task: '' as Ref }, space: { - Sequence: '' as Ref + Sequence: '' as Ref, + Statuses: '' as Ref }, component: { KanbanTemplateEditor: '' as AnyComponent, KanbanTemplateSelector: '' as AnyComponent, TodoItemsPopup: '' as AnyComponent, - CreateStatePopup: '' as AnyComponent + CreateStatePopup: '' as AnyComponent, + CreateStateTemplatePopup: '' as AnyComponent }, ids: { AssigneedNotification: '' as Ref @@ -292,84 +286,3 @@ const task = plugin(taskId, { export default task export * from './utils' - -/** - * @public - */ -export async function createKanban ( - client: TxOperations, - attachedTo: Ref, - templateId?: Ref -): Promise> { - if (templateId === undefined) { - await client.createDoc(task.class.State, attachedTo, { - ofAttribute: task.attribute.State, - name: 'New State', - color: 9, - rank: [...genRanks(1)][0] - }) - - const ranks = [...genRanks(2)] - await Promise.all([ - client.createDoc(task.class.WonState, attachedTo, { - ofAttribute: task.attribute.DoneState, - name: 'Won', - rank: ranks[0] - }), - client.createDoc(task.class.LostState, attachedTo, { - ofAttribute: task.attribute.DoneState, - name: 'Lost', - rank: ranks[1] - }) - ]) - return await client.createDoc(task.class.Kanban, attachedTo, { - attachedTo - }) - } - - const template = await client.findOne(task.class.KanbanTemplate, { _id: templateId }) - - if (template === undefined) { - throw Error(`Failed to find target kanban template: ${templateId}`) - } - - const tmplStates = await client.findAll(task.class.StateTemplate, { attachedTo: template._id }) - await Promise.all( - tmplStates.map( - async (state) => - await client.createDoc(task.class.State, attachedTo, { - ofAttribute: task.attribute.State, - color: state.color, - description: state.description, - name: state.name, - rank: state.rank - }) - ) - ) - - const doneClassMap = new Map>, Ref>>([ - [task.class.WonStateTemplate, task.class.WonState], - [task.class.LostStateTemplate, task.class.LostState] - ]) - const tmplDoneStates = await client.findAll(task.class.DoneStateTemplate, { attachedTo: template._id }) - await Promise.all( - tmplDoneStates.map(async (state) => { - const cl = doneClassMap.get(state._class) - - if (cl === undefined) { - return - } - - return await client.createDoc(cl, attachedTo, { - ofAttribute: task.attribute.DoneState, - description: state.description, - name: state.name, - rank: state.rank - }) - }) - ) - - return await client.createDoc(task.class.Kanban, attachedTo, { - attachedTo - }) -} diff --git a/plugins/task/src/utils.ts b/plugins/task/src/utils.ts index f8b573e900..765fb7184b 100644 --- a/plugins/task/src/utils.ts +++ b/plugins/task/src/utils.ts @@ -13,8 +13,10 @@ // limitations under the License. // -import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank' +import { Class, Data, DocumentQuery, IdMap, Ref, SortingOrder, Status, TxOperations } from '@hcengineering/core' +import { LexoDecimal, LexoNumeralSystem36, LexoRank } from 'lexorank' import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket' +import task, { DoneState, DoneStateTemplate, KanbanTemplate, SpaceWithStates, State } from '.' /** * @public @@ -44,3 +46,123 @@ export const calcRank = (prev?: { rank: string }, next?: { rank: string }): stri } return a.between(b).toString() } + +/** + * @public + */ +export function getStates (space: SpaceWithStates | undefined, statusStore: IdMap): Status[] { + if (space === undefined) { + return [] + } + + const states = space.states.map((x) => statusStore.get(x) as Status).filter((p) => p !== undefined) + + return states +} + +/** + * @public + */ +export async function createState ( + client: TxOperations, + _class: Ref>, + data: Data, + _id?: Ref +): Promise> { + const query: DocumentQuery = { name: data.name, ofAttribute: data.ofAttribute } + if (data.category !== undefined) { + query.category = data.category + } + const exists = await client.findOne(_class, query) + if (exists !== undefined) { + return exists._id as Ref + } + const res = await client.createDoc(_class, task.space.Statuses, data, _id) + return res +} + +/** + * @public + */ +export async function createStates ( + client: TxOperations, + templateId?: Ref +): Promise<[Ref[], Ref[]]> { + if (templateId === undefined) { + const state = await createState(client, task.class.State, { + ofAttribute: task.attribute.State, + name: 'New State', + color: 9 + }) + + const doneStates: Ref[] = [] + + doneStates.push( + await createState(client, task.class.WonState, { + ofAttribute: task.attribute.DoneState, + name: 'Won' + }) + ) + doneStates.push( + await createState(client, task.class.LostState, { + ofAttribute: task.attribute.DoneState, + name: 'Lost' + }) + ) + + return [[state], doneStates] + } + + const template = await client.findOne(task.class.KanbanTemplate, { _id: templateId }) + + if (template === undefined) { + throw Error(`Failed to find target kanban template: ${templateId}`) + } + + const states: Ref[] = [] + const doneStates: Ref[] = [] + + const tmplStates = await client.findAll( + task.class.StateTemplate, + { attachedTo: template._id }, + { sort: { rank: SortingOrder.Ascending } } + ) + + for (const state of tmplStates) { + states.push( + await createState(client, task.class.State, { + ofAttribute: task.attribute.State, + color: state.color, + description: state.description, + name: state.name + }) + ) + } + + const doneClassMap = new Map>, Ref>>([ + [task.class.WonStateTemplate, task.class.WonState], + [task.class.LostStateTemplate, task.class.LostState] + ]) + const tmplDoneStates = await client.findAll( + task.class.DoneStateTemplate, + { attachedTo: template._id }, + { sort: { rank: SortingOrder.Ascending } } + ) + for (const state of tmplDoneStates) { + const cl = doneClassMap.get(state._class) + + if (cl === undefined) { + continue + } + + doneStates.push( + await createState(client, cl, { + ofAttribute: task.attribute.DoneState, + description: state.description, + name: state.name + }) + ) + } + + return [states, doneStates] +} diff --git a/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte b/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte index b87527ba31..a41d685975 100644 --- a/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte +++ b/plugins/tracker-resources/src/components/SetParentIssueActionPopup.svelte @@ -102,7 +102,7 @@
{#if issue?.$lookup?.status}
- +
{/if} {issueId} diff --git a/plugins/tracker-resources/src/components/activity/StatusIcon.svelte b/plugins/tracker-resources/src/components/activity/StatusIcon.svelte index c9a6e8f1b8..20997b1fce 100644 --- a/plugins/tracker-resources/src/components/activity/StatusIcon.svelte +++ b/plugins/tracker-resources/src/components/activity/StatusIcon.svelte @@ -13,19 +13,21 @@ // limitations under the License. -->
{#if status} - + {/if}
diff --git a/plugins/tracker-resources/src/components/components/ComponentBrowser.svelte b/plugins/tracker-resources/src/components/components/ComponentBrowser.svelte index fd08324330..dcb7b839f2 100644 --- a/plugins/tracker-resources/src/components/components/ComponentBrowser.svelte +++ b/plugins/tracker-resources/src/components/components/ComponentBrowser.svelte @@ -105,6 +105,7 @@ (resultQuery = detail)} diff --git a/plugins/tracker-resources/src/components/issues/IssueItem.svelte b/plugins/tracker-resources/src/components/issues/IssueItem.svelte index 6aee5b88a6..367304e728 100644 --- a/plugins/tracker-resources/src/components/issues/IssueItem.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueItem.svelte @@ -29,7 +29,7 @@ {#if st} - + {/if} diff --git a/plugins/tracker-resources/src/components/issues/IssueNotification.svelte b/plugins/tracker-resources/src/components/issues/IssueNotification.svelte index 959fbc588a..0e10158d35 100644 --- a/plugins/tracker-resources/src/components/issues/IssueNotification.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueNotification.svelte @@ -1,8 +1,7 @@ @@ -93,6 +93,6 @@ {#each displaySt as st} - + {/each} diff --git a/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte b/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte index 8d8f5494c2..ce241a4ed6 100644 --- a/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte +++ b/plugins/tracker-resources/src/components/issues/IssueStatusIcon.svelte @@ -13,9 +13,10 @@ // limitations under the License. -->
{#each statuses as value, i} {#if value && i < 5}
- +
{/if} {/each} diff --git a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte index a61f8ee60e..cea93e3e21 100644 --- a/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/StatusPresenter.svelte @@ -13,10 +13,12 @@ // limitations under the License. --> {#if value} - + {/if} diff --git a/plugins/tracker-resources/src/components/issues/edit/SubIssueSelector.svelte b/plugins/tracker-resources/src/components/issues/edit/SubIssueSelector.svelte index 5742e5f94f..26206d925f 100644 --- a/plugins/tracker-resources/src/components/issues/edit/SubIssueSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/SubIssueSelector.svelte @@ -118,7 +118,7 @@ subIssuesQeury.unsubscribe() } - $: parentStatus = parentIssue ? $statusStore.getIdMap().get(parentIssue.status) : undefined + $: parentStatus = parentIssue ? $statusStore.get(parentIssue.status) : undefined {#if parentIssue} @@ -132,7 +132,7 @@ > {#if parentStatus}
- +
{/if} {#if issue.$lookup?.space} diff --git a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte index 8406f11733..6f35e0e635 100644 --- a/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/edit/SubIssuesSelector.svelte @@ -13,7 +13,7 @@ // limitations under the License. --> @@ -50,7 +51,7 @@
- +
@@ -83,7 +84,7 @@
- +
diff --git a/plugins/tracker-resources/src/components/issues/related/RelatedIssuePresenter.svelte b/plugins/tracker-resources/src/components/issues/related/RelatedIssuePresenter.svelte index f419973527..12a48fe575 100644 --- a/plugins/tracker-resources/src/components/issues/related/RelatedIssuePresenter.svelte +++ b/plugins/tracker-resources/src/components/issues/related/RelatedIssuePresenter.svelte @@ -23,7 +23,7 @@ export let issue: Issue export let size: 'small' | 'medium' | 'large' = 'small' - $: status = $statusStore.getIdMap().get(issue.status) + $: status = $statusStore.get(issue.status) $: huge = size === 'medium' || size === 'large' $: text = project ? `${getIssueId(project, issue)} ${issue.title}` : issue.title @@ -31,7 +31,7 @@
{#if status}
- +
{/if} diff --git a/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte b/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte index c2d64f1a57..e096956a63 100644 --- a/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte +++ b/plugins/tracker-resources/src/components/issues/related/RelatedIssueSelector.svelte @@ -70,8 +70,7 @@ } $: if (subIssues) { - const doneStatuses = $statusStore - .getDocs() + const doneStatuses = Array.from($statusStore.values()) .filter((s) => s.category === tracker.issueStatusCategory.Completed) .map((p) => p._id) countComplete = subIssues.filter((si) => doneStatuses.includes(si.status)).length diff --git a/plugins/tracker-resources/src/components/milestones/IssueStatistics.svelte b/plugins/tracker-resources/src/components/milestones/IssueStatistics.svelte index fc727e7063..8a8974678f 100644 --- a/plugins/tracker-resources/src/components/milestones/IssueStatistics.svelte +++ b/plugins/tracker-resources/src/components/milestones/IssueStatistics.svelte @@ -30,13 +30,13 @@ $: noParents = docs?.filter((it) => !ids.has(it.attachedTo as Ref)) $: rootNoBacklogIssues = noParents?.filter( - (it) => $statusStore.getIdMap().get(it.status)?.category !== tracker.issueStatusCategory.Backlog + (it) => $statusStore.get(it.status)?.category !== tracker.issueStatusCategory.Backlog ) $: totalEstimation = floorFractionDigits( (rootNoBacklogIssues ?? [{ estimation: 0, childInfo: [] } as unknown as Issue]) .map((it) => { - const cat = $statusStore.getIdMap().get(it.status)?.category + const cat = $statusStore.get(it.status)?.category let retEst = it.estimation if (it.childInfo?.length > 0) { diff --git a/plugins/tracker-resources/src/components/milestones/MilestoneBrowser.svelte b/plugins/tracker-resources/src/components/milestones/MilestoneBrowser.svelte index fdcec5f6ff..61313f390e 100644 --- a/plugins/tracker-resources/src/components/milestones/MilestoneBrowser.svelte +++ b/plugins/tracker-resources/src/components/milestones/MilestoneBrowser.svelte @@ -118,6 +118,7 @@ (resultQuery = e.detail)} /> diff --git a/plugins/tracker-resources/src/components/projects/CreateProject.svelte b/plugins/tracker-resources/src/components/projects/CreateProject.svelte index ba642cafe2..b17d23cf4b 100644 --- a/plugins/tracker-resources/src/components/projects/CreateProject.svelte +++ b/plugins/tracker-resources/src/components/projects/CreateProject.svelte @@ -28,7 +28,7 @@ import { Asset } from '@hcengineering/platform' import presentation, { Card, createQuery, getClient } from '@hcengineering/presentation' import { StyledTextBox } from '@hcengineering/text-editor' - import { IssueStatus, Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker' + import { Project, TimeReportDayType, createStatuses } from '@hcengineering/tracker' import { Button, EditBox, @@ -54,11 +54,9 @@ export let descriptionPlaceholder: string = '' export let statusFactory: ( client: TxOperations | ApplyOperations, - spaceId: Status['space'], statusClass: Status['_class'], - categoryOfAttribute: Status['ofAttribute'], - defaultStatusId: Status['_id'] - ) => Promise = createStatuses + categoryOfAttribute: Status['ofAttribute'] + ) => Promise[]> = createStatuses const client = getClient() const hierarchy = client.getHierarchy() @@ -92,8 +90,6 @@ let identifier: string = project?.identifier ?? 'TSK' - const defaultStatusId: Ref = generateId() - function getProjectData () { return { name, @@ -103,7 +99,6 @@ archived: false, identifier: identifier.toUpperCase(), sequence: 0, - defaultIssueStatus: defaultStatusId, defaultAssignee: defaultAssignee ?? undefined, icon, color, @@ -116,7 +111,7 @@ return } - const { sequence, defaultIssueStatus, ...projectData } = getProjectData() + const { sequence, ...projectData } = getProjectData() const update: DocumentUpdate = {} if (projectData.name !== project?.name) { update.name = projectData.name @@ -168,10 +163,15 @@ const ops = client .apply(projectId) .notMatch(tracker.class.Project, { identifier: projectData.identifier.toUpperCase() }) + const statuses = await statusFactory(ops, tracker.class.IssueStatus, tracker.attribute.IssueStatus) isSaving = true - await ops.createDoc(tracker.class.Project, core.space.Space, projectData, projectId) - await statusFactory(ops, projectId, tracker.class.IssueStatus, tracker.attribute.IssueStatus, defaultStatusId) + await ops.createDoc( + tracker.class.Project, + core.space.Space, + { ...projectData, states: statuses, defaultIssueStatus: statuses[0] }, + projectId + ) const succeeded = await ops.commit() isSaving = false diff --git a/plugins/tracker-resources/src/components/templates/IssueTemplatesView.svelte b/plugins/tracker-resources/src/components/templates/IssueTemplatesView.svelte index 794b4dad39..0f43234f6e 100644 --- a/plugins/tracker-resources/src/components/templates/IssueTemplatesView.svelte +++ b/plugins/tracker-resources/src/components/templates/IssueTemplatesView.svelte @@ -86,6 +86,7 @@ (resultQuery = e.detail)} diff --git a/plugins/tracker-resources/src/components/workflow/RemoveStatus.svelte b/plugins/tracker-resources/src/components/workflow/RemoveStatus.svelte index 66237f9b2f..b6aa76b467 100644 --- a/plugins/tracker-resources/src/components/workflow/RemoveStatus.svelte +++ b/plugins/tracker-resources/src/components/workflow/RemoveStatus.svelte @@ -1,12 +1,13 @@
- {#each statuses as status, _ (status._id)} + {#each statuses as status (status._id)}
draggingStatus.rank} + draggingStatus.category === status.category && + getStatusIndex(status) > getStatusIndex(draggingStatus)} draggable={!isSingle} animate:flip={{ duration: 200 }} on:dragstart={(ev) => handleDragStart(ev, status)} @@ -292,6 +283,7 @@ /> {:else} >, + space: Ref | undefined, viewletDescriptorId?: Ref -): Promise { +): Promise>> { + let _space: Project | undefined + if (space !== undefined) { + _space = await client.findOne(tracker.class.Project, { _id: space }) + } + const statuses = get(statusStore) // TODO: How we track category updates. if (viewletDescriptorId === tracker.viewlet.Kanban) { value.sort((a, b) => { + const aVal = statuses.get(a) as IssueStatus + const bVal = statuses.get(b) as IssueStatus const res = - listIssueKanbanStatusOrder.indexOf(a.values[0].category as Ref) - - listIssueKanbanStatusOrder.indexOf(b.values[0].category as Ref) + listIssueKanbanStatusOrder.indexOf(aVal?.category as Ref) - + listIssueKanbanStatusOrder.indexOf(bVal?.category as Ref) if (res === 0) { - return a.values[0].getRank().localeCompare(b.values[0].getRank()) + if (_space != null) { + const aIndex = _space.states.findIndex((s) => s === a) + const bIndex = _space.states.findIndex((s) => s === b) + return aIndex - bIndex + } else { + return aVal.name.localeCompare(bVal.name) + } } return res }) } else { value.sort((a, b) => { + const aVal = statuses.get(a) as IssueStatus + const bVal = statuses.get(b) as IssueStatus const res = - listIssueStatusOrder.indexOf(a.values[0].category as Ref) - - listIssueStatusOrder.indexOf(b.values[0].category as Ref) + listIssueStatusOrder.indexOf(aVal?.category as Ref) - + listIssueStatusOrder.indexOf(bVal?.category as Ref) if (res === 0) { - return a.values[0].getRank().localeCompare(b.values[0].getRank()) + if (_space != null) { + const aIndex = _space.states.findIndex((s) => s === a) + const bIndex = _space.states.findIndex((s) => s === b) + return aIndex - bIndex + } else { + return aVal.name.localeCompare(bVal.name) + } } return res }) @@ -310,7 +339,7 @@ export async function issueStatusSort ( return value } -export async function issuePrioritySort (value: IssuePriority[]): Promise { +export async function issuePrioritySort (client: TxOperations, value: IssuePriority[]): Promise { value.sort((a, b) => { const i1 = defaultPriorities.indexOf(a) const i2 = defaultPriorities.indexOf(b) @@ -320,7 +349,10 @@ export async function issuePrioritySort (value: IssuePriority[]): Promise>): Promise>> { +export async function milestoneSort ( + client: TxOperations, + value: Array> +): Promise>> { return await new Promise((resolve) => { const query = createQuery(true) query.query(tracker.class.Milestone, { _id: { $in: value } }, (res) => { @@ -610,26 +642,20 @@ export async function collectIssues (client: TxOperations, docs: Doc[]): Promise * @public */ export function findTargetStatus ( - mgr: StatusManager, status: Ref, - targetProject: Ref, + targetProject: Project, + statusStore: IdMap, useCategory = false ): Ref | undefined { - const s = mgr.get(status) - let targetStatus = mgr - .filter( - (it) => - it.space === targetProject && - it.ofAttribute === s?.ofAttribute && - (it.name ?? '').trim().toLowerCase() === (s?.name ?? '').trim().toLowerCase() - ) - .shift() - if (targetStatus === undefined && useCategory) { - targetStatus = mgr - .filter((it) => it.space === targetProject && it.ofAttribute === s?.ofAttribute && s?.category === it.category) - .shift() + if (targetProject.states.includes(status)) return status + + if (useCategory) { + const currentCategroy = statusStore.get(status)?.category + for (const status of targetProject.states) { + const st = statusStore.get(status) + if (st?.category === currentCategroy) return st?._id + } } - return targetStatus?._id } /** diff --git a/plugins/tracker/src/utils.ts b/plugins/tracker/src/utils.ts index 6dedf74328..2a949ef608 100644 --- a/plugins/tracker/src/utils.ts +++ b/plugins/tracker/src/utils.ts @@ -13,8 +13,8 @@ // limitations under the License. // -import core, { ApplyOperations, SortingOrder, Status, TxOperations, generateId } from '@hcengineering/core' -import { genRanks } from '@hcengineering/task' +import core, { ApplyOperations, Ref, SortingOrder, Status, TxOperations } from '@hcengineering/core' +import { createState } from '@hcengineering/task' export { calcRank, genRanks } from '@hcengineering/task' /** @@ -24,32 +24,26 @@ export { calcRank, genRanks } from '@hcengineering/task' */ export async function createStatuses ( client: TxOperations | ApplyOperations, - spaceId: Status['space'], statusClass: Status['_class'], - categoryOfAttribute: Status['ofAttribute'], - defaultStatusId: Status['_id'] -): Promise { + categoryOfAttribute: Status['ofAttribute'] +): Promise[]> { 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() - const rank = ranks[i] + const states: Ref[] = [] - await client.createDoc( - statusClass, - spaceId, - { + for (const category of categories) { + states.push( + await createState(client, statusClass, { ofAttribute: categoryOfAttribute, name: category.defaultStatusName, - category: category._id, - rank - }, - statusId + category: category._id + }) ) } + + return states } diff --git a/plugins/view-resources/src/components/Table.svelte b/plugins/view-resources/src/components/Table.svelte index c44cdfe73f..77ac2ffa2a 100644 --- a/plugins/view-resources/src/components/Table.svelte +++ b/plugins/view-resources/src/components/Table.svelte @@ -194,7 +194,7 @@ if (attribute.attribute?.type._class === core.class.EnumOf) { return { ...attribute.props, type: attribute.attribute.type } } - return attribute.props + return { ...attribute.props, space: object.space } } function getValue (attribute: AttributeModel, object: Doc): any { if (attribute.castRequest) { diff --git a/plugins/view-resources/src/components/filter/FilterBar.svelte b/plugins/view-resources/src/components/filter/FilterBar.svelte index d9b7ba3ca2..6104264e44 100644 --- a/plugins/view-resources/src/components/filter/FilterBar.svelte +++ b/plugins/view-resources/src/components/filter/FilterBar.svelte @@ -13,7 +13,7 @@ // limitations under the License. --> + +
dispatch('changeContent')}> + {#if clazz.filteringKey} +
+ +
+ {:else} +
{/if} - (resultQuery = e.detail)} /> + (resultQuery = e.detail)} />
{#each spaces as space (space._id)} diff --git a/plugins/workbench-resources/src/components/SpaceContent.svelte b/plugins/workbench-resources/src/components/SpaceContent.svelte index 1447db1e77..4c49dc6875 100644 --- a/plugins/workbench-resources/src/components/SpaceContent.svelte +++ b/plugins/workbench-resources/src/components/SpaceContent.svelte @@ -60,6 +60,7 @@ {:else} (resultQuery = { ...e.detail, ...query })} diff --git a/plugins/workbench-resources/src/components/SpecialView.svelte b/plugins/workbench-resources/src/components/SpecialView.svelte index a38e1b9cae..8fea7222d6 100644 --- a/plugins/workbench-resources/src/components/SpecialView.svelte +++ b/plugins/workbench-resources/src/components/SpecialView.svelte @@ -93,6 +93,7 @@ {:else if viewOptions && viewlet} { diff --git a/server-plugins/task-resources/src/index.ts b/server-plugins/task-resources/src/index.ts index c52c0dda87..31d14d80ac 100644 --- a/server-plugins/task-resources/src/index.ts +++ b/server-plugins/task-resources/src/index.ts @@ -47,7 +47,7 @@ export async function OnTemplateStateUpdate (tx: Tx, control: TriggerControl): P await control.findAll(classToChange, { space: { $in: ids }, name: prevDoc.name }) ) as Array return statesToChange.map((it) => { - const newAttributes = it._class === task.class.State ? { color: newDoc.color, rank: newDoc.rank } : {} + const newAttributes = it._class === task.class.State ? { color: newDoc.color } : {} return control.txFactory.createTxUpdateDoc(it._class, it.space, it._id, { name: newDoc.name, ...newAttributes @@ -75,7 +75,7 @@ export async function OnTemplateStateCreate (tx: Tx, control: TriggerControl): P const doc = TxProcessor.createDoc2Doc(actualTx) const ofAttribute = classToChange === task.class.State ? task.attribute.State : task.attribute.DoneState return ids.map((it) => { - const newAttributes = classToChange === task.class.State ? { color: doc.color, rank: doc.rank } : {} + const newAttributes = classToChange === task.class.State ? { color: doc.color } : {} return control.txFactory.createTxCreateDoc(classToChange, it, { ofAttribute, name: doc.name,