mirror of
https://github.com/hcengineering/platform.git
synced 2025-06-11 21:11:57 +00:00
Introduce createDeps (#702)
Signed-off-by: Ilya Sumbatyants <ilya.sumb@gmail.com>
This commit is contained in:
parent
ae34d53746
commit
d5c2c07b9e
@ -16,7 +16,7 @@
|
||||
|
||||
import contact from '@anticrm/contact'
|
||||
import core, { DOMAIN_TX, Tx } from '@anticrm/core'
|
||||
import builder, { migrateOperations } from '@anticrm/model-all'
|
||||
import builder, { migrateOperations, createDeps } from '@anticrm/model-all'
|
||||
import { existsSync } from 'fs'
|
||||
import { mkdir, open, readFile, writeFile } from 'fs/promises'
|
||||
import { Client } from 'minio'
|
||||
@ -39,6 +39,10 @@ export async function initWorkspace (
|
||||
transactorUrl: string,
|
||||
minio: Client
|
||||
): Promise<void> {
|
||||
if (txes.some(tx => tx.objectSpace !== core.space.Model)) {
|
||||
throw Error('Model txes must target only core.space.Model')
|
||||
}
|
||||
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
@ -48,17 +52,13 @@ export async function initWorkspace (
|
||||
await db.dropDatabase()
|
||||
|
||||
console.log('creating model...')
|
||||
const model = txes.filter((tx) => tx.objectSpace === core.space.Model)
|
||||
const model = txes
|
||||
const result = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
console.log(`${result.insertedCount} model transactions inserted.`)
|
||||
|
||||
console.log('creating data...')
|
||||
const data = txes.filter((tx) => tx.objectSpace !== core.space.Model)
|
||||
|
||||
const connection = await connect(transactorUrl, dbName)
|
||||
for (const tx of data) {
|
||||
await connection.tx(tx)
|
||||
}
|
||||
await createDeps(connection)
|
||||
await connection.close()
|
||||
|
||||
console.log('create minio bucket')
|
||||
@ -79,6 +79,10 @@ export async function upgradeWorkspace (
|
||||
transactorUrl: string,
|
||||
minio: Client
|
||||
): Promise<void> {
|
||||
if (txes.some(tx => tx.objectSpace !== core.space.Model)) {
|
||||
throw Error('Model txes must target only core.space.Model')
|
||||
}
|
||||
|
||||
const client = new MongoClient(mongoUrl)
|
||||
try {
|
||||
await client.connect()
|
||||
@ -94,7 +98,7 @@ export async function upgradeWorkspace (
|
||||
console.log(`${result.deletedCount} transactions deleted.`)
|
||||
|
||||
console.log('creating model...')
|
||||
const model = txes.filter((tx) => tx.objectSpace === core.space.Model)
|
||||
const model = txes
|
||||
const insert = await db.collection(DOMAIN_TX).insertMany(model as Document[])
|
||||
console.log(`${insert.insertedCount} model transactions inserted.`)
|
||||
|
||||
|
27
models/all/src/creation.ts
Normal file
27
models/all/src/creation.ts
Normal file
@ -0,0 +1,27 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import type { Client } from '@anticrm/core'
|
||||
import { createDeps as createTaskDeps } from '@anticrm/model-task'
|
||||
import { createDeps as createLeadDeps } from '@anticrm/model-lead'
|
||||
import { createDeps as createRecruitDeps } from '@anticrm/model-recruit'
|
||||
import { createDeps as createDemoDeps } from '@anticrm/model-demo'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
await createTaskDeps(client)
|
||||
await createLeadDeps(client)
|
||||
await createRecruitDeps(client)
|
||||
await createDemoDeps(client)
|
||||
}
|
@ -59,3 +59,5 @@ export default builder
|
||||
|
||||
// Export upgrade procedures
|
||||
export { migrateOperations } from './migration'
|
||||
|
||||
export { createDeps } from './creation'
|
||||
|
89
models/demo/src/creation.ts
Normal file
89
models/demo/src/creation.ts
Normal file
@ -0,0 +1,89 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { TxOperations } from '@anticrm/core'
|
||||
import type { Client } from '@anticrm/core'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import recruit from '@anticrm/model-recruit'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
const account = await client.findOne(contact.class.EmployeeAccount, { email: 'rosamund@hc.engineering' })
|
||||
|
||||
if (account === undefined) {
|
||||
throw Error('Failed to find EmployeeAccount')
|
||||
}
|
||||
|
||||
const tx = new TxOperations(client, account._id)
|
||||
|
||||
// Create missing Employee
|
||||
await tx.createDoc(
|
||||
contact.class.Employee,
|
||||
contact.space.Employee,
|
||||
{
|
||||
name: 'Chen,Rosamund',
|
||||
city: 'Mountain View',
|
||||
channels: []
|
||||
},
|
||||
account.employee
|
||||
)
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'P.,Andrey',
|
||||
title: 'Chief Architect',
|
||||
city: 'Monte Carlo',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'andrey@hc.engineering'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'M.,Marina',
|
||||
title: 'Chief Designer',
|
||||
city: 'Los Angeles',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'marina@hc.engineering'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
await tx.createDoc(
|
||||
recruit.class.Candidate,
|
||||
recruit.space.CandidatesPublic,
|
||||
{
|
||||
name: 'P.,Alex',
|
||||
title: 'Frontend Engineer',
|
||||
city: 'Krasnodar, Russia',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'alex@hc.engineering'
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
@ -15,60 +15,19 @@
|
||||
//
|
||||
|
||||
import { Employee, EmployeeAccount } from '@anticrm/contact'
|
||||
import core, { generateId, Ref } from '@anticrm/core'
|
||||
import core, { generateId } from '@anticrm/core'
|
||||
import { Builder } from '@anticrm/model'
|
||||
import contact from '@anticrm/model-contact'
|
||||
import recruit from '@anticrm/model-recruit'
|
||||
|
||||
export function createDemo (builder: Builder): void {
|
||||
const rosamund = generateId()
|
||||
const account: Ref<EmployeeAccount> = generateId()
|
||||
|
||||
builder.createDoc(contact.class.Employee, contact.space.Employee, {
|
||||
name: 'Chen,Rosamund',
|
||||
city: 'Mountain View',
|
||||
channels: []
|
||||
}, rosamund, account)
|
||||
const rosamund = generateId<Employee>()
|
||||
const account = generateId<EmployeeAccount>()
|
||||
|
||||
builder.createDoc<EmployeeAccount>(contact.class.EmployeeAccount, core.space.Model, {
|
||||
email: 'rosamund@hc.engineering',
|
||||
employee: rosamund as Ref<Employee>,
|
||||
employee: rosamund,
|
||||
name: 'Chen,Rosamund'
|
||||
}, account, account)
|
||||
|
||||
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
|
||||
name: 'P.,Andrey',
|
||||
title: 'Chief Architect',
|
||||
city: 'Monte Carlo',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'andrey@hc.engineering'
|
||||
}
|
||||
]
|
||||
}, undefined, account)
|
||||
|
||||
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
|
||||
name: 'M.,Marina',
|
||||
title: 'Chief Designer',
|
||||
city: 'Los Angeles',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'marina@hc.engineering'
|
||||
}
|
||||
]
|
||||
}, undefined, account)
|
||||
|
||||
builder.createDoc(recruit.class.Candidate, recruit.space.CandidatesPublic, {
|
||||
name: 'P.,Alex',
|
||||
title: 'Frontend Engineer',
|
||||
city: 'Krasnodar, Russia',
|
||||
channels: [
|
||||
{
|
||||
provider: contact.channelProvider.Email,
|
||||
value: 'alex@hc.engineering'
|
||||
}
|
||||
]
|
||||
}, undefined, account)
|
||||
}
|
||||
|
||||
export { createDeps } from './creation'
|
||||
|
61
models/lead/src/creation.ts
Normal file
61
models/lead/src/creation.ts
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import type { Client, Ref } from '@anticrm/core'
|
||||
import task, { createKanban } from '@anticrm/task'
|
||||
import type { KanbanTemplate } from '@anticrm/task'
|
||||
import { createKanbanTemplate } from '@anticrm/model-task'
|
||||
|
||||
import lead from './plugin'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
||||
await tx.createDoc(
|
||||
task.class.Sequence,
|
||||
task.space.Sequence,
|
||||
{
|
||||
attachedTo: lead.class.Lead,
|
||||
sequence: 0
|
||||
}
|
||||
)
|
||||
const defaultTmpl = await createDefaultKanbanTemplate(tx)
|
||||
await createKanban(tx, lead.space.DefaultFunnel, defaultTmpl)
|
||||
}
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'Incoming' },
|
||||
{ color: '#6F7BC5', title: 'Negotation' },
|
||||
{ color: '#77C07B', title: 'Offer preparing' },
|
||||
{ color: '#A5D179', title: 'Make a decision' },
|
||||
{ color: '#F28469', title: 'Contract conclusion' },
|
||||
{ color: '#7C6FCD', title: 'Done' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
const createDefaultKanbanTemplate = async (client: TxOperations): Promise<Ref<KanbanTemplate>> =>
|
||||
await createKanbanTemplate(client, {
|
||||
kanbanId: lead.template.DefaultFunnel,
|
||||
space: lead.space.FunnelTemplates,
|
||||
title: 'Default funnel',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
@ -16,9 +16,8 @@
|
||||
|
||||
// To help typescript locate view plugin properly
|
||||
import type { Contact, Employee } from '@anticrm/contact'
|
||||
import type { Class, Data, Doc, FindOptions, Ref, Space } from '@anticrm/core'
|
||||
import type { Doc, FindOptions, Ref } from '@anticrm/core'
|
||||
import type { Funnel, Lead } from '@anticrm/lead'
|
||||
import { createKanban } from '@anticrm/lead'
|
||||
import { Builder, Collection, Model, Prop, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
@ -28,7 +27,6 @@ import task, { TSpaceWithStates, TTask } from '@anticrm/model-task'
|
||||
import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { createDefaultKanbanTemplate } from '@anticrm/task'
|
||||
import type {} from '@anticrm/view'
|
||||
import lead from './plugin'
|
||||
|
||||
@ -146,11 +144,6 @@ export function createModel (builder: Builder): void {
|
||||
presenter: lead.component.LeadPresenter
|
||||
})
|
||||
|
||||
builder.createDoc(task.class.Sequence, task.space.Sequence, {
|
||||
attachedTo: lead.class.Lead,
|
||||
sequence: 0
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
task.class.KanbanTemplateSpace,
|
||||
core.space.Model,
|
||||
@ -164,29 +157,8 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
lead.space.FunnelTemplates
|
||||
)
|
||||
|
||||
createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => {
|
||||
builder.createDoc(_class, space, data, id)
|
||||
return await Promise.resolve()
|
||||
}).catch((err) => console.error(err))
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
createDefaultKanbanTemplate(async <T extends Doc>(
|
||||
props: {
|
||||
id?: Ref<T>
|
||||
space: Ref<Space>
|
||||
class: Ref<Class<T>>
|
||||
},
|
||||
attrs: Data<T>
|
||||
): Promise<void> => {
|
||||
builder.createDoc(
|
||||
props.class,
|
||||
props.space,
|
||||
attrs,
|
||||
props.id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export { leadOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
export { leadOperation } from './migration'
|
||||
export { createDeps } from './creation'
|
||||
|
@ -14,13 +14,9 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { Doc, TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import core from '@anticrm/model-core'
|
||||
import task, { DOMAIN_TASK } from '@anticrm/model-task'
|
||||
import { createDefaultKanbanTemplate, createKanban } from '@anticrm/lead'
|
||||
import lead from './plugin'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function logInfo (msg: string, result: MigrationResult): void {
|
||||
if (result.updated > 0) {
|
||||
console.log(`Lead: Migrate ${msg} ${result.updated}`)
|
||||
@ -28,46 +24,6 @@ function logInfo (msg: string, result: MigrationResult): void {
|
||||
}
|
||||
|
||||
export const leadOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
// Update done states for tasks
|
||||
logInfo('lead done states', await client.update(DOMAIN_TASK, { _class: lead.class.Lead, doneState: { $exists: false } }, { doneState: null }))
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
console.log('Lead: Performing model upgrades')
|
||||
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
if (await client.findOne(task.class.Kanban, { attachedTo: lead.space.DefaultFunnel }) === undefined) {
|
||||
console.info('Lead: Create kanban for default funnel.')
|
||||
await createKanban(lead.space.DefaultFunnel, async (_class, space, data, id) => {
|
||||
const doc = await ops.findOne<Doc>(_class, { _id: id })
|
||||
if (doc === undefined) {
|
||||
await ops.createDoc(_class, space, data, id)
|
||||
} else {
|
||||
await ops.updateDoc(_class, space, id, data)
|
||||
}
|
||||
}).catch((err) => console.error(err))
|
||||
} else {
|
||||
console.log('Lead: => default funnel Kanban is ok')
|
||||
}
|
||||
|
||||
if (await client.findOne(task.class.Sequence, { attachedTo: lead.class.Lead }) === undefined) {
|
||||
console.info('Lead: Create sequence for default task project.')
|
||||
// We need to create sequence
|
||||
await ops.createDoc(task.class.Sequence, task.space.Sequence, {
|
||||
attachedTo: lead.class.Lead,
|
||||
sequence: 0
|
||||
})
|
||||
} else {
|
||||
console.log('Lead: => sequence is ok')
|
||||
}
|
||||
|
||||
if (await client.findOne(core.class.TxCreateDoc, { objectId: lead.template.DefaultFunnel }) === undefined) {
|
||||
await createDefaultKanbanTemplate(async (
|
||||
props,
|
||||
attrs
|
||||
): Promise<void> => {
|
||||
await ops.createDoc(props.class, props.space, attrs, props.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import { leadId } from '@anticrm/lead'
|
||||
import lead from '@anticrm/lead-resources/src/plugin'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import '@anticrm/task'
|
||||
import { KanbanTemplate } from '@anticrm/task'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Application } from '@anticrm/workbench'
|
||||
|
||||
@ -42,5 +42,8 @@ export default mergeIds(leadId, lead, {
|
||||
},
|
||||
space: {
|
||||
DefaultFunnel: '' as Ref<Space>
|
||||
},
|
||||
template: {
|
||||
DefaultFunnel: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
61
models/recruit/src/creation.ts
Normal file
61
models/recruit/src/creation.ts
Normal file
@ -0,0 +1,61 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { Ref, TxOperations } from '@anticrm/core'
|
||||
import type { Client } from '@anticrm/core'
|
||||
import { createKanbanTemplate } from '@anticrm/model-task'
|
||||
|
||||
import recruit from './plugin'
|
||||
import task from '@anticrm/task'
|
||||
import type { KanbanTemplate } from '@anticrm/task'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
||||
await tx.createDoc(
|
||||
task.class.Sequence,
|
||||
task.space.Sequence,
|
||||
{
|
||||
attachedTo: recruit.class.Applicant,
|
||||
sequence: 0
|
||||
}
|
||||
)
|
||||
await createDefaultKanbanTemplate(tx)
|
||||
}
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'HR Interview' },
|
||||
{ color: '#6F7BC5', title: 'Technical Interview' },
|
||||
{ color: '#77C07B', title: 'Test task' },
|
||||
{ color: '#A5D179', title: 'Offer' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const createDefaultKanbanTemplate = async (client: TxOperations): Promise<Ref<KanbanTemplate>> =>
|
||||
await createKanbanTemplate(client, {
|
||||
kanbanId: recruit.template.DefaultVacancy,
|
||||
space: recruit.space.VacancyTemplates,
|
||||
title: 'Default vacancy',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
@ -14,7 +14,7 @@
|
||||
//
|
||||
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import { Class, Data, Doc, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { Doc, FindOptions, Ref, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
@ -25,7 +25,6 @@ import view from '@anticrm/model-view'
|
||||
import workbench from '@anticrm/model-workbench'
|
||||
import type { IntlString } from '@anticrm/platform'
|
||||
import { Applicant, Candidate, Candidates, Vacancy } from '@anticrm/recruit'
|
||||
import { createDefaultKanbanTemplate } from '@anticrm/task'
|
||||
import recruit from './plugin'
|
||||
|
||||
@Model(recruit.class.Vacancy, task.class.SpaceWithStates)
|
||||
@ -277,11 +276,6 @@ export function createModel (builder: Builder): void {
|
||||
action: task.action.CreateTask
|
||||
})
|
||||
|
||||
builder.createDoc(task.class.Sequence, task.space.Sequence, {
|
||||
attachedTo: recruit.class.Applicant,
|
||||
sequence: 0
|
||||
})
|
||||
|
||||
builder.createDoc(
|
||||
task.class.KanbanTemplateSpace,
|
||||
core.space.Model,
|
||||
@ -295,24 +289,8 @@ export function createModel (builder: Builder): void {
|
||||
},
|
||||
recruit.space.VacancyTemplates
|
||||
)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
createDefaultKanbanTemplate(async <T extends Doc>(
|
||||
props: {
|
||||
id?: Ref<T>
|
||||
space: Ref<Space>
|
||||
class: Ref<Class<T>>
|
||||
},
|
||||
attrs: Data<T>
|
||||
): Promise<void> => {
|
||||
builder.createDoc(
|
||||
props.class,
|
||||
props.space,
|
||||
attrs,
|
||||
props.id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export { recruitOperation } from './migration'
|
||||
export { default } from './plugin'
|
||||
export { recruitOperation } from './migration'
|
||||
export { createDeps } from './creation'
|
||||
|
@ -13,61 +13,15 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import { MigrateOperation, MigrationClient, MigrationResult, MigrationUpgradeClient } from '@anticrm/model'
|
||||
import contact, { DOMAIN_CONTACT } from '@anticrm/model-contact'
|
||||
import { DOMAIN_TASK } from '@anticrm/model-task'
|
||||
import { createDefaultKanbanTemplate } from '@anticrm/recruit'
|
||||
import recruit from './plugin'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function logInfo (msg: string, result: MigrationResult): void {
|
||||
if (result.updated > 0) {
|
||||
console.log(`Recruit: Migrate ${msg} ${result.updated}`)
|
||||
}
|
||||
}
|
||||
export const recruitOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
logInfo(
|
||||
'done for Applicants',
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _class: recruit.class.Applicant, doneState: { $exists: false } },
|
||||
{ doneState: null }
|
||||
)
|
||||
)
|
||||
|
||||
logInfo(
|
||||
'$move employee => assignee',
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _class: recruit.class.Applicant, employee: { $exists: true } },
|
||||
{ $rename: { employee: 'assignee' } }
|
||||
)
|
||||
)
|
||||
|
||||
const employees = (await client.find(DOMAIN_CONTACT, { _class: contact.class.Employee })).map((emp) => emp._id)
|
||||
|
||||
// update assignee to unassigned if there is no employee exists.
|
||||
logInfo(
|
||||
'applicants wrong assignee',
|
||||
await client.update(
|
||||
DOMAIN_TASK,
|
||||
{ _class: recruit.class.Applicant, assignee: { $not: { $in: employees } } },
|
||||
{ assignee: null }
|
||||
)
|
||||
)
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
console.log('Recruit: Performing model upgrades')
|
||||
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
if (await client.findOne(core.class.TxCreateDoc, { objectId: recruit.template.DefaultVacancy }) === undefined) {
|
||||
await createDefaultKanbanTemplate(async (
|
||||
props,
|
||||
attrs
|
||||
): Promise<void> => {
|
||||
await ops.createDoc(props.class, props.space, attrs, props.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ import recruit from '@anticrm/recruit-resources/src/plugin'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import type { Action } from '@anticrm/view'
|
||||
import { Application } from '@anticrm/workbench'
|
||||
import '@anticrm/task'
|
||||
import { KanbanTemplate } from '@anticrm/task'
|
||||
|
||||
export default mergeIds(recruitId, recruit, {
|
||||
app: {
|
||||
@ -59,5 +59,8 @@ export default mergeIds(recruitId, recruit, {
|
||||
},
|
||||
space: {
|
||||
CandidatesPublic: '' as Ref<Space>
|
||||
},
|
||||
template: {
|
||||
DefaultVacancy: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
117
models/task/src/creation.ts
Normal file
117
models/task/src/creation.ts
Normal file
@ -0,0 +1,117 @@
|
||||
//
|
||||
// Copyright © 2020, 2021 Anticrm Platform Contributors.
|
||||
//
|
||||
// Licensed under the Eclipse Public License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License. You may
|
||||
// obtain a copy of the License at https://www.eclipse.org/legal/epl-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
//
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import core, { TxOperations } from '@anticrm/core'
|
||||
import type { Client, Ref, Space } from '@anticrm/core'
|
||||
import { createKanban, genRanks } from '@anticrm/task'
|
||||
import type { DoneStateTemplate, KanbanTemplate, StateTemplate } from '@anticrm/task'
|
||||
|
||||
import task from './plugin'
|
||||
|
||||
export async function createDeps (client: Client): Promise<void> {
|
||||
const tx = new TxOperations(client, core.account.System)
|
||||
|
||||
await tx.createDoc(
|
||||
task.class.Sequence,
|
||||
task.space.Sequence,
|
||||
{
|
||||
attachedTo: task.class.Issue,
|
||||
sequence: 0
|
||||
}
|
||||
)
|
||||
const defaultTmpl = await createDefaultKanbanTemplate(tx)
|
||||
await createKanban(tx, task.space.TasksPublic, defaultTmpl)
|
||||
}
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'Open' },
|
||||
{ color: '#6F7BC5', title: 'In Progress' },
|
||||
{ color: '#77C07B', title: 'Under review' },
|
||||
{ color: '#A5D179', title: 'Done' },
|
||||
{ color: '#F28469', title: 'Invalid' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KanbanTemplateData {
|
||||
kanbanId: Ref<KanbanTemplate>
|
||||
space: Ref<Space>
|
||||
title: KanbanTemplate['title']
|
||||
states: Pick<StateTemplate, 'title' | 'color'>[]
|
||||
doneStates: (Pick<DoneStateTemplate, 'title'> & { isWon: boolean })[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createKanbanTemplate (client: TxOperations, data: KanbanTemplateData): Promise<Ref<KanbanTemplate>> {
|
||||
const tmpl = await client.createDoc(
|
||||
task.class.KanbanTemplate,
|
||||
data.space,
|
||||
{
|
||||
doneStatesC: 0,
|
||||
statesC: 0,
|
||||
title: data.title
|
||||
},
|
||||
data.kanbanId
|
||||
)
|
||||
|
||||
const doneStateRanks = [...genRanks(data.doneStates.length)]
|
||||
await Promise.all(
|
||||
data.doneStates.map((st, i) => client.addCollection(
|
||||
st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate,
|
||||
data.space,
|
||||
data.kanbanId,
|
||||
task.class.KanbanTemplate,
|
||||
'doneStatesC',
|
||||
{
|
||||
rank: doneStateRanks[i],
|
||||
title: st.title
|
||||
}))
|
||||
)
|
||||
|
||||
const stateRanks = [...genRanks(data.states.length)]
|
||||
await Promise.all(
|
||||
data.states.map((st, i) => client.addCollection(
|
||||
task.class.StateTemplate,
|
||||
data.space,
|
||||
data.kanbanId,
|
||||
task.class.KanbanTemplate,
|
||||
'statesC',
|
||||
{
|
||||
rank: stateRanks[i],
|
||||
title: st.title,
|
||||
color: st.color
|
||||
}))
|
||||
)
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
const createDefaultKanbanTemplate = async (client: TxOperations): Promise<Ref<KanbanTemplate>> =>
|
||||
await createKanbanTemplate(client, {
|
||||
kanbanId: task.template.DefaultProject,
|
||||
space: task.space.ProjectTemplates,
|
||||
title: 'Default project',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
@ -19,7 +19,7 @@ import type { ActionTarget } from '@anticrm/view'
|
||||
import attachment from '@anticrm/model-attachment'
|
||||
import type { Employee } from '@anticrm/contact'
|
||||
import contact from '@anticrm/contact'
|
||||
import { Arr, Class, Data, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { Arr, Class, Doc, Domain, DOMAIN_MODEL, FindOptions, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import { Builder, Collection, Implements, Mixin, Model, Prop, TypeBoolean, TypeDate, TypeRef, TypeString, UX } from '@anticrm/model'
|
||||
import chunter from '@anticrm/model-chunter'
|
||||
import core, { TAttachedDoc, TClass, TDoc, TSpace } from '@anticrm/model-core'
|
||||
@ -45,7 +45,6 @@ import type {
|
||||
Task,
|
||||
TodoItem
|
||||
} from '@anticrm/task'
|
||||
import { createProjectKanban, createDefaultKanbanTemplate } from '@anticrm/task'
|
||||
import task from './plugin'
|
||||
import { AnyComponent } from '@anticrm/ui'
|
||||
|
||||
@ -306,11 +305,6 @@ export function createModel (builder: Builder): void {
|
||||
editor: task.component.EditIssue
|
||||
})
|
||||
|
||||
builder.createDoc(task.class.Sequence, task.space.Sequence, {
|
||||
attachedTo: task.class.Issue,
|
||||
sequence: 0
|
||||
})
|
||||
|
||||
builder.createDoc(view.class.Viewlet, core.space.Model, {
|
||||
attachTo: task.class.Issue,
|
||||
descriptor: task.viewlet.Kanban,
|
||||
@ -361,11 +355,6 @@ export function createModel (builder: Builder): void {
|
||||
task.space.ProjectTemplates
|
||||
)
|
||||
|
||||
createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
|
||||
builder.createDoc(_class, space, data, id)
|
||||
return await Promise.resolve()
|
||||
}).catch((err) => console.error(err))
|
||||
|
||||
builder.createDoc(
|
||||
view.class.Action,
|
||||
core.space.Model,
|
||||
@ -514,23 +503,7 @@ export function createModel (builder: Builder): void {
|
||||
done: true
|
||||
}
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
createDefaultKanbanTemplate(async <T extends Doc>(
|
||||
props: {
|
||||
id?: Ref<T>
|
||||
space: Ref<Space>
|
||||
class: Ref<Class<T>>
|
||||
},
|
||||
attrs: Data<T>
|
||||
): Promise<void> => {
|
||||
builder.createDoc(
|
||||
props.class,
|
||||
props.space,
|
||||
attrs,
|
||||
props.id
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export { taskOperation } from './migration'
|
||||
export { createDeps, createKanbanTemplate } from './creation'
|
||||
|
@ -13,284 +13,21 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { AttachedDoc, Class, Client, Doc, Domain, DOMAIN_TX, Ref, Space, TxCUD, TxOperations } from '@anticrm/core'
|
||||
import {
|
||||
MigrateOperation,
|
||||
MigrateUpdate,
|
||||
MigrationClient,
|
||||
MigrationResult,
|
||||
MigrationUpgradeClient
|
||||
} from '@anticrm/model'
|
||||
import core from '@anticrm/model-core'
|
||||
import { createDefaultKanbanTemplate, createProjectKanban, KanbanTemplate, DocWithRank, genRanks } from '@anticrm/task'
|
||||
import { DOMAIN_TASK, DOMAIN_STATE, DOMAIN_KANBAN } from '.'
|
||||
import task from './plugin'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function logInfo (msg: string, result: MigrationResult): void {
|
||||
if (result.updated > 0) {
|
||||
console.log(`Task: Migrate ${msg} ${result.updated}`)
|
||||
}
|
||||
}
|
||||
async function migrateClass<T extends Doc> (
|
||||
client: MigrationClient,
|
||||
domain: Domain,
|
||||
from: Ref<Class<Doc>>,
|
||||
to: Ref<Class<T>>,
|
||||
extraOps: MigrateUpdate<T> = {},
|
||||
txExtraOps: MigrateUpdate<TxCUD<Doc>> = {}
|
||||
): Promise<void> {
|
||||
logInfo(`${from} => ${to}: `, await client.update<Doc>(domain, { _class: from }, { ...extraOps, _class: to }))
|
||||
logInfo(
|
||||
`${from} => ${to} Transactions`,
|
||||
await client.update<TxCUD<Doc>>(DOMAIN_TX, { objectClass: from }, { ...txExtraOps, objectClass: to })
|
||||
)
|
||||
}
|
||||
|
||||
export const taskOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
// Since we should not have Task class instances, we convert them all to Issue.
|
||||
await migrateClass(client, DOMAIN_TASK, task.class.Task, task.class.Issue)
|
||||
await migrateClass(client, DOMAIN_STATE, 'core:class:State' as Ref<Class<Doc>>, task.class.State)
|
||||
await migrateClass(client, DOMAIN_STATE, 'core:class:WonState' as Ref<Class<Doc>>, task.class.WonState)
|
||||
await migrateClass(client, DOMAIN_STATE, 'core:class:LostState' as Ref<Class<Doc>>, task.class.LostState)
|
||||
await migrateClass(client, DOMAIN_KANBAN, 'view:class:Kanban' as Ref<Class<Doc>>, task.class.Kanban)
|
||||
await migrateClass(
|
||||
client,
|
||||
DOMAIN_KANBAN,
|
||||
'view:class:Sequence' as Ref<Class<Doc>>,
|
||||
task.class.Sequence,
|
||||
{ space: task.space.Sequence },
|
||||
{ objectSpace: task.space.Sequence }
|
||||
)
|
||||
|
||||
// Update attached to for task
|
||||
await client.update(
|
||||
DOMAIN_KANBAN,
|
||||
{ _class: task.class.Sequence, attachedTo: task.class.Task },
|
||||
{ attachedTo: task.class.Issue }
|
||||
)
|
||||
|
||||
await migrateClass(client, DOMAIN_KANBAN, 'view:class:KanbanTemplate' as Ref<Class<Doc>>, task.class.KanbanTemplate)
|
||||
await migrateClass(client, DOMAIN_KANBAN, 'view:class:StateTemplate' as Ref<Class<Doc>>, task.class.StateTemplate)
|
||||
await migrateClass(
|
||||
client,
|
||||
DOMAIN_KANBAN,
|
||||
'view:class:DoneStateTemplate' as Ref<Class<Doc>>,
|
||||
task.class.DoneStateTemplate
|
||||
)
|
||||
await migrateClass(
|
||||
client,
|
||||
DOMAIN_KANBAN,
|
||||
'view:class:WonStateTemplate' as Ref<Class<Doc>>,
|
||||
task.class.WonStateTemplate
|
||||
)
|
||||
await migrateClass(
|
||||
client,
|
||||
DOMAIN_KANBAN,
|
||||
'view:class:LostStateTemplate' as Ref<Class<Doc>>,
|
||||
task.class.LostStateTemplate
|
||||
)
|
||||
|
||||
await client.move(
|
||||
'recruit' as Domain,
|
||||
{
|
||||
_class: 'recruit:class:Applicant' as Ref<Class<Doc>>
|
||||
},
|
||||
DOMAIN_TASK
|
||||
)
|
||||
|
||||
await client.move(
|
||||
'lead' as Domain,
|
||||
{
|
||||
_class: 'lead:class:Lead' as Ref<Class<Doc>>
|
||||
},
|
||||
DOMAIN_TASK
|
||||
)
|
||||
|
||||
// Update done states for tasks
|
||||
await client.update(DOMAIN_TASK, { _class: task.class.Issue, doneState: { $exists: false } }, { doneState: null })
|
||||
},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {
|
||||
console.log('Task: Performing model upgrades')
|
||||
|
||||
const ops = new TxOperations(client, core.account.System)
|
||||
if ((await client.findOne(task.class.Sequence, { attachedTo: task.class.Issue })) === undefined) {
|
||||
console.info('Task: Create sequence for default task project.')
|
||||
// We need to create sequence
|
||||
await ops.createDoc(task.class.Sequence, task.space.Sequence, {
|
||||
attachedTo: task.class.Issue,
|
||||
sequence: 0
|
||||
})
|
||||
} else {
|
||||
console.log('Task: => sequence is ok')
|
||||
}
|
||||
if ((await client.findOne(task.class.Kanban, { attachedTo: task.space.TasksPublic })) === undefined) {
|
||||
console.info('Task: Create kanban for default task project.')
|
||||
await createProjectKanban(task.space.TasksPublic, async (_class, space, data, id) => {
|
||||
const doc = await ops.findOne<Doc>(_class, { _id: id })
|
||||
if (doc === undefined) {
|
||||
await ops.createDoc(_class, space, data, id)
|
||||
} else {
|
||||
await ops.updateDoc(_class, space, id, data)
|
||||
}
|
||||
}).catch((err) => console.error(err))
|
||||
} else {
|
||||
console.log('Task: => public project Kanban is ok')
|
||||
}
|
||||
|
||||
if (await client.findOne(core.class.TxCreateDoc, { objectId: task.template.DefaultProject }) === undefined) {
|
||||
await createDefaultKanbanTemplate(async (
|
||||
props,
|
||||
attrs
|
||||
): Promise<void> => {
|
||||
await ops.createDoc(props.class, props.space, attrs, props.id)
|
||||
})
|
||||
}
|
||||
|
||||
console.log('View: Performing model upgrades')
|
||||
|
||||
await createMissingDoneStates(client, ops)
|
||||
await updateRankItems({ client, ops, _class: task.class.State, extractOrder: (kanban) => kanban.states })
|
||||
await updateRankItems({ client, ops, _class: task.class.DoneState, extractOrder: (kanban) => kanban.doneStates })
|
||||
await updateRankItems({ client, ops, _class: task.class.Task, extractOrder: (kanban) => kanban.order })
|
||||
await updateTemplateRankItems({ client, ops, _class: task.class.StateTemplate, extractOrder: (kanban) => kanban.states })
|
||||
await updateTemplateRankItems({ client, ops, _class: task.class.DoneStateTemplate, extractOrder: (kanban) => kanban.doneStates })
|
||||
}
|
||||
}
|
||||
|
||||
async function createMissingDoneStates (client: Client, ops: TxOperations): Promise<void> {
|
||||
const spacesWithStates = await client.findAll(task.class.SpaceWithStates, {})
|
||||
const doneStates = await client.findAll(task.class.DoneState, {})
|
||||
const spaceIdsWithDoneStates = new Set(doneStates.map(x => x.space))
|
||||
const outdatedSpaces = spacesWithStates.filter((space) => !spaceIdsWithDoneStates.has(space._id))
|
||||
|
||||
const pairRanks = [...genRanks(2)]
|
||||
|
||||
await Promise.all(
|
||||
outdatedSpaces
|
||||
.map(async (space) => {
|
||||
console.log(`Creating done states for space: ${space._id}`)
|
||||
try {
|
||||
await Promise.all([
|
||||
ops.createDoc(task.class.WonState, space._id, {
|
||||
title: 'Won',
|
||||
rank: pairRanks[0]
|
||||
}),
|
||||
ops.createDoc(task.class.LostState, space._id, {
|
||||
title: 'Lost',
|
||||
rank: pairRanks[1]
|
||||
})
|
||||
])
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
async function updateRankItems<T extends DocWithRank> ({
|
||||
client,
|
||||
ops,
|
||||
_class,
|
||||
extractOrder
|
||||
}: {
|
||||
client: Client
|
||||
ops: TxOperations
|
||||
_class: Ref<Class<T>>
|
||||
extractOrder: (kanban: any) => Ref<T>[]
|
||||
}): Promise<void> {
|
||||
const allItems = await client.findAll(_class, {})
|
||||
const unorderedItems = allItems
|
||||
.filter((item) => item.rank === undefined)
|
||||
const groupedUnsortedItems = new Map<Ref<Space>, T[]>()
|
||||
|
||||
unorderedItems.forEach((item) => {
|
||||
const existing = groupedUnsortedItems.get(item.space) ?? []
|
||||
groupedUnsortedItems.set(item.space, [...existing, item])
|
||||
})
|
||||
|
||||
for (const [space, items] of groupedUnsortedItems.entries()) {
|
||||
const kanban = await client.findOne(task.class.Kanban, { attachedTo: space })
|
||||
|
||||
if (kanban === undefined) {
|
||||
console.error(`Failed to find kanban attached to space '${space}'`)
|
||||
continue
|
||||
}
|
||||
|
||||
const order = extractOrder(kanban)
|
||||
|
||||
if (order === undefined) {
|
||||
console.error(`Kanban doesn't contain items order: ${kanban._id}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const orderedItems = order
|
||||
.map((id) => items.find(x => x._id === id))
|
||||
.filter((items): items is T => items !== undefined)
|
||||
const ranks = genRanks(orderedItems.length)
|
||||
|
||||
for (const item of orderedItems) {
|
||||
const rank = ranks.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
console.error('Failed to generate rank')
|
||||
break
|
||||
}
|
||||
|
||||
await ops.updateDoc(item._class as Ref<Class<DocWithRank>>, item.space, item._id, { rank })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateTemplateRankItems<T extends DocWithRank & AttachedDoc> ({
|
||||
client,
|
||||
ops,
|
||||
_class,
|
||||
extractOrder
|
||||
}: {
|
||||
client: Client
|
||||
ops: TxOperations
|
||||
_class: Ref<Class<T>>
|
||||
extractOrder: (kanban: any) => Ref<T>[]
|
||||
}): Promise<void> {
|
||||
const allItems = await client.findAll(_class, {})
|
||||
const unorderedItems = allItems
|
||||
.filter((state) => state.rank === undefined)
|
||||
const groupedUnsortedItems = new Map<Ref<Doc>, T[]>()
|
||||
|
||||
unorderedItems.forEach((item) => {
|
||||
const existing = groupedUnsortedItems.get(item.attachedTo) ?? []
|
||||
groupedUnsortedItems.set(item.attachedTo, [...existing, item])
|
||||
})
|
||||
|
||||
for (const [attachedTo, items] of groupedUnsortedItems.entries()) {
|
||||
const kanban = await client.findOne(task.class.KanbanTemplate, { _id: attachedTo as Ref<KanbanTemplate> })
|
||||
|
||||
if (kanban === undefined) {
|
||||
console.error(`Failed to find kanban '${attachedTo}'`)
|
||||
continue
|
||||
}
|
||||
|
||||
const order = extractOrder(kanban)
|
||||
|
||||
if (order === undefined) {
|
||||
console.error(`Kanban doesn't contain items order: ${kanban._id}`)
|
||||
continue
|
||||
}
|
||||
|
||||
const orderedItems = order
|
||||
.map((id) => items.find(x => x._id === id))
|
||||
.filter((items): items is T => items !== undefined)
|
||||
const ranks = genRanks(orderedItems.length)
|
||||
|
||||
for (const item of orderedItems) {
|
||||
const rank = ranks.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
console.error('Failed to generate rank')
|
||||
break
|
||||
}
|
||||
|
||||
await ops.updateDoc(item._class as Ref<Class<DocWithRank>>, item.space, item._id, { rank })
|
||||
}
|
||||
}
|
||||
async migrate (client: MigrationClient): Promise<void> {},
|
||||
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@
|
||||
import type { Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { IntlString, Resource } from '@anticrm/platform'
|
||||
import { mergeIds } from '@anticrm/platform'
|
||||
import task, { taskId } from '@anticrm/task'
|
||||
import task, { KanbanTemplate, taskId } from '@anticrm/task'
|
||||
import type { AnyComponent } from '@anticrm/ui'
|
||||
import { Application } from '@anticrm/workbench'
|
||||
import type { Action } from '@anticrm/view'
|
||||
@ -67,5 +67,8 @@ export default mergeIds(taskId, task, {
|
||||
},
|
||||
space: {
|
||||
TasksPublic: '' as Ref<Space>
|
||||
},
|
||||
template: {
|
||||
DefaultProject: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
@ -15,10 +15,10 @@
|
||||
//
|
||||
|
||||
import type { Contact } from '@anticrm/contact'
|
||||
import { Class, Data, Doc, Ref, Space } from '@anticrm/core'
|
||||
import type { Class, Ref } from '@anticrm/core'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import task, { CreateFn, genRanks, createKanbanTemplate, DoneState, Kanban, KanbanTemplate, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@anticrm/task'
|
||||
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -53,97 +53,7 @@ const lead = plugin(leadId, {
|
||||
},
|
||||
space: {
|
||||
FunnelTemplates: '' as Ref<KanbanTemplateSpace>
|
||||
},
|
||||
template: {
|
||||
DefaultFunnel: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
||||
export default lead
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'Incoming' },
|
||||
{ color: '#6F7BC5', title: 'Negotation' },
|
||||
{ color: '#77C07B', title: 'Offer preparing' },
|
||||
{ color: '#A5D179', title: 'Make a decision' },
|
||||
{ color: '#F28469', title: 'Contract conclusion' },
|
||||
{ color: '#7C6FCD', title: 'Done' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createKanban (
|
||||
funnelId: Ref<Funnel>,
|
||||
factory: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, data: Data<T>, id: Ref<T>) => Promise<void>
|
||||
): Promise<void> {
|
||||
const { states, doneStates } = defaultKanban
|
||||
const stateRank = genRanks(states.length)
|
||||
for (const st of states) {
|
||||
const sid = (funnelId + '.state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<State>
|
||||
const rank = stateRank.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
throw Error('Failed to generate rank')
|
||||
}
|
||||
|
||||
await factory(
|
||||
task.class.State,
|
||||
funnelId,
|
||||
{
|
||||
title: st.title,
|
||||
color: st.color,
|
||||
rank
|
||||
},
|
||||
sid
|
||||
)
|
||||
}
|
||||
|
||||
const doneStateRank = genRanks(doneStates.length)
|
||||
for (const st of doneStates) {
|
||||
const rank = doneStateRank.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
throw Error('Failed to generate rank')
|
||||
}
|
||||
|
||||
const sid = (funnelId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
|
||||
await factory(
|
||||
st.isWon ? task.class.WonState : task.class.LostState,
|
||||
funnelId,
|
||||
{
|
||||
title: st.title,
|
||||
rank
|
||||
},
|
||||
sid
|
||||
)
|
||||
}
|
||||
|
||||
await factory(
|
||||
task.class.Kanban,
|
||||
funnelId,
|
||||
{
|
||||
attachedTo: funnelId
|
||||
},
|
||||
(funnelId + '.kanban') as Ref<Kanban>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const createDefaultKanbanTemplate = async (create: CreateFn): Promise<void> => {
|
||||
await createKanbanTemplate(create)({
|
||||
kanbanId: lead.template.DefaultFunnel,
|
||||
space: lead.space.FunnelTemplates,
|
||||
title: 'Default funnel',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import type { Person } from '@anticrm/contact'
|
||||
import type { Class, Ref, Space, Timestamp } from '@anticrm/core'
|
||||
import type { Asset, Plugin } from '@anticrm/platform'
|
||||
import { plugin } from '@anticrm/platform'
|
||||
import { CreateFn, createKanbanTemplate, KanbanTemplate, KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
|
||||
import type { KanbanTemplateSpace, SpaceWithStates, Task } from '@anticrm/task'
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -79,36 +79,7 @@ const recruit = plugin(recruitId, {
|
||||
},
|
||||
space: {
|
||||
VacancyTemplates: '' as Ref<KanbanTemplateSpace>
|
||||
},
|
||||
template: {
|
||||
DefaultVacancy: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
||||
export default recruit
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'HR Interview' },
|
||||
{ color: '#6F7BC5', title: 'Technical Interview' },
|
||||
{ color: '#77C07B', title: 'Test task' },
|
||||
{ color: '#A5D179', title: 'Offer' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const createDefaultKanbanTemplate = async (create: CreateFn): Promise<void> => {
|
||||
await createKanbanTemplate(create)({
|
||||
kanbanId: recruit.template.DefaultVacancy,
|
||||
space: recruit.space.VacancyTemplates,
|
||||
title: 'Default vacancy',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ import type { Employee } from '@anticrm/contact'
|
||||
import {
|
||||
AttachedDoc,
|
||||
Class,
|
||||
Client,
|
||||
Data,
|
||||
Doc,
|
||||
Interface,
|
||||
Mixin,
|
||||
@ -223,91 +221,17 @@ const task = plugin(taskId, {
|
||||
space: {
|
||||
ProjectTemplates: '' as Ref<KanbanTemplateSpace>,
|
||||
Sequence: '' as Ref<Space>
|
||||
},
|
||||
template: {
|
||||
DefaultProject: '' as Ref<KanbanTemplate>
|
||||
}
|
||||
})
|
||||
|
||||
export default task
|
||||
|
||||
const defaultKanban = {
|
||||
states: [
|
||||
{ color: '#7C6FCD', title: 'Open' },
|
||||
{ color: '#6F7BC5', title: 'In Progress' },
|
||||
{ color: '#77C07B', title: 'Under review' },
|
||||
{ color: '#A5D179', title: 'Done' },
|
||||
{ color: '#F28469', title: 'Invalid' }
|
||||
],
|
||||
doneStates: [
|
||||
{ isWon: true, title: 'Won' },
|
||||
{ isWon: false, title: 'Lost' }
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createProjectKanban (
|
||||
projectId: Ref<Project>,
|
||||
factory: <T extends Doc>(_class: Ref<Class<T>>, space: Ref<Space>, data: Data<T>, id: Ref<T>) => Promise<void>
|
||||
): Promise<void> {
|
||||
const { states, doneStates } = defaultKanban
|
||||
const stateRank = genRanks(states.length)
|
||||
for (const st of states) {
|
||||
const rank = stateRank.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
throw Error('Failed to generate rank')
|
||||
}
|
||||
|
||||
const sid = (projectId + '.state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<State>
|
||||
await factory(
|
||||
task.class.State,
|
||||
projectId,
|
||||
{
|
||||
title: st.title,
|
||||
color: st.color,
|
||||
rank
|
||||
},
|
||||
sid
|
||||
)
|
||||
}
|
||||
|
||||
const doneStateRank = genRanks(doneStates.length)
|
||||
for (const st of doneStates) {
|
||||
const rank = doneStateRank.next().value
|
||||
|
||||
if (rank === undefined) {
|
||||
throw Error('Failed to generate rank')
|
||||
}
|
||||
|
||||
const sid = (projectId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
|
||||
await factory(
|
||||
st.isWon ? task.class.WonState : task.class.LostState,
|
||||
projectId,
|
||||
{
|
||||
title: st.title,
|
||||
rank
|
||||
},
|
||||
sid
|
||||
)
|
||||
}
|
||||
|
||||
await factory(
|
||||
task.class.Kanban,
|
||||
projectId,
|
||||
{
|
||||
attachedTo: projectId
|
||||
},
|
||||
(projectId + '.kanban') as Ref<Kanban>
|
||||
)
|
||||
}
|
||||
export * from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export async function createKanban (
|
||||
client: Client & TxOperations,
|
||||
client: TxOperations,
|
||||
attachedTo: Ref<Space>,
|
||||
templateId?: Ref<KanbanTemplate>
|
||||
): Promise<Ref<Kanban>> {
|
||||
@ -367,85 +291,3 @@ export async function createKanban (
|
||||
attachedTo
|
||||
})
|
||||
}
|
||||
|
||||
export * from './utils'
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type CreateFn = <T extends Doc>(
|
||||
props: {
|
||||
id?: Ref<T>
|
||||
space: Ref<Space>
|
||||
class: Ref<Class<T>>
|
||||
},
|
||||
attrs: Data<T>
|
||||
) => Promise<void>
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface KanbanTemplateData {
|
||||
kanbanId: Ref<KanbanTemplate>
|
||||
space: Ref<Space>
|
||||
title: KanbanTemplate['title']
|
||||
states: Pick<StateTemplate, 'title' | 'color'>[]
|
||||
doneStates: (Pick<DoneStateTemplate, 'title'> & { isWon: boolean })[]
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const createKanbanTemplate = (create: CreateFn) =>
|
||||
async (data: KanbanTemplateData) => {
|
||||
await create(
|
||||
{
|
||||
id: data.kanbanId,
|
||||
space: data.space,
|
||||
class: task.class.KanbanTemplate
|
||||
},
|
||||
{
|
||||
doneStatesC: data.doneStates.length,
|
||||
statesC: data.states.length,
|
||||
title: data.title
|
||||
}
|
||||
)
|
||||
|
||||
const doneStateRanks = [...genRanks(data.doneStates.length)]
|
||||
await Promise.all(data.doneStates.map((st, i) => create({
|
||||
space: data.space,
|
||||
class: st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate
|
||||
}, {
|
||||
attachedTo: data.kanbanId,
|
||||
attachedToClass: task.class.KanbanTemplate,
|
||||
collection: 'doneStatesC',
|
||||
rank: doneStateRanks[i],
|
||||
title: st.title
|
||||
})))
|
||||
|
||||
const stateRanks = [...genRanks(data.states.length)]
|
||||
await Promise.all(data.states.map((st, i) => create({
|
||||
space: data.space,
|
||||
class: task.class.StateTemplate
|
||||
}, {
|
||||
attachedTo: data.kanbanId,
|
||||
attachedToClass: task.class.KanbanTemplate,
|
||||
collection: 'statesC',
|
||||
rank: stateRanks[i],
|
||||
title: st.title,
|
||||
color: st.color
|
||||
})))
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export const createDefaultKanbanTemplate = async (create: CreateFn): Promise<void> => {
|
||||
await createKanbanTemplate(create)({
|
||||
kanbanId: task.template.DefaultProject,
|
||||
space: task.space.ProjectTemplates,
|
||||
title: 'Default project',
|
||||
states: defaultKanban.states,
|
||||
doneStates: defaultKanban.doneStates
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user