ATS-9: update states once template updates (#3496)

Signed-off-by: Vyacheslav Tumanov <me@slavatumanov.me>
This commit is contained in:
Vyacheslav Tumanov 2023-07-13 09:01:28 +05:00 committed by GitHub
parent f21750a20d
commit 9a7f75c1ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 203 additions and 67 deletions

View File

@ -52,7 +52,8 @@ async function createSpace (tx: TxOperations): Promise<void> {
icon: board.component.TemplatesIcon,
private: false,
archived: false,
members: []
members: [],
attachedToClass: board.class.Board
},
board.space.BoardTemplates
)
@ -108,6 +109,13 @@ async function createDefaults (tx: TxOperations): Promise<void> {
)
}
async function fixTemplateSpace (tx: TxOperations): Promise<void> {
const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: board.space.BoardTemplates })
if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) {
await tx.update(templateSpace, { attachedToClass: board.class.Board })
}
}
async function migrateLabels (client: MigrationClient): Promise<void> {}
export const boardOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
@ -116,5 +124,6 @@ export const boardOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const ops = new TxOperations(client, core.account.System)
await createDefaults(ops)
await fixTemplateSpace(ops)
}
}

View File

@ -53,7 +53,8 @@ async function createSpace (tx: TxOperations): Promise<void> {
icon: lead.component.TemplatesIcon,
private: false,
members: [],
archived: false
archived: false,
attachedToClass: lead.class.Funnel
},
lead.space.FunnelTemplates
)
@ -94,10 +95,18 @@ async function createDefaultKanban (tx: TxOperations): Promise<void> {
await createKanban(tx, lead.space.DefaultFunnel, defaultTmpl)
}
async function fixTemplateSpace (tx: TxOperations): Promise<void> {
const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: lead.space.FunnelTemplates })
if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) {
await tx.update(templateSpace, { attachedToClass: lead.class.Funnel })
}
}
async function createDefaults (tx: TxOperations): Promise<void> {
await createSpace(tx)
await createSequence(tx, lead.class.Lead)
await createDefaultKanban(tx)
await fixTemplateSpace(tx)
}
export const leadOperation: MigrateOperation = {

View File

@ -27,6 +27,14 @@ export const recruitOperation: MigrateOperation = {
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)
await createDefaults(tx)
await fixTemplateSpace(tx)
}
}
async function fixTemplateSpace (tx: TxOperations): Promise<void> {
const templateSpace = await tx.findOne(task.class.KanbanTemplateSpace, { _id: recruit.space.VacancyTemplates })
if (templateSpace !== undefined && templateSpace?.attachedToClass === undefined) {
await tx.update(templateSpace, { attachedToClass: recruit.class.Vacancy })
}
}
@ -148,7 +156,8 @@ async function createSpaces (tx: TxOperations): Promise<void> {
editor: recruit.component.VacancyTemplateEditor,
private: false,
members: [],
archived: false
archived: false,
attachedToClass: recruit.class.Vacancy
},
recruit.space.VacancyTemplates
)

View File

@ -14,7 +14,31 @@
//
import { Builder } from '@hcengineering/model'
import serverCore from '@hcengineering/server-core'
import core from '@hcengineering/core/lib/component'
import serverTask from '@hcengineering/server-task'
import task from '@hcengineering/task'
export { serverTaskId } from '@hcengineering/server-task'
export function createModel (builder: Builder): void {}
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTask.trigger.OnTemplateStateUpdate,
txMatch: {
_class: core.class.TxUpdateDoc,
objectClass: {
$in: [task.class.StateTemplate, task.class.LostStateTemplate, task.class.WonStateTemplate]
}
}
})
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTask.trigger.OnTemplateStateCreate,
txMatch: {
_class: core.class.TxCreateDoc,
objectClass: {
$in: [task.class.StateTemplate, task.class.LostStateTemplate, task.class.WonStateTemplate]
}
}
})
}

View File

@ -149,7 +149,9 @@ export class TKanban extends TDoc implements Kanban {
}
@Model(task.class.SpaceWithStates, core.class.Space)
export class TSpaceWithStates extends TSpace {}
export class TSpaceWithStates extends TSpace {
templateId!: Ref<KanbanTemplate>
}
@Model(task.class.KanbanTemplateSpace, core.class.Space)
export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace {
@ -157,13 +159,16 @@ export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace
description!: IntlString
icon!: AnyComponent
editor!: AnyComponent
attachedToClass!: Ref<Class<Doc>>
}
@Model(task.class.StateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN)
export class TStateTemplate extends TAttachedDoc implements StateTemplate {
@Model(task.class.StateTemplate, core.class.Doc, DOMAIN_KANBAN)
export class TStateTemplate extends TDoc implements StateTemplate {
// We attach to attribute, so we could distinguish between
ofAttribute!: Ref<Attribute<Status>>
attachedTo!: Ref<KanbanTemplate>
@Prop(TypeString(), task.string.StateTemplateTitle)
name!: string
@ -173,11 +178,13 @@ export class TStateTemplate extends TAttachedDoc implements StateTemplate {
declare rank: string
}
@Model(task.class.DoneStateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN)
export class TDoneStateTemplate extends TAttachedDoc implements DoneStateTemplate {
@Model(task.class.DoneStateTemplate, core.class.Doc, DOMAIN_KANBAN)
export class TDoneStateTemplate extends TDoc implements DoneStateTemplate {
// We attach to attribute, so we could distinguish between
ofAttribute!: Ref<Attribute<Status>>
attachedTo!: Ref<KanbanTemplate>
@Prop(TypeString(), task.string.StateTemplateTitle)
name!: string

View File

@ -76,25 +76,20 @@ export async function createKanbanTemplate (
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',
{
ofAttribute: task.attribute.DoneState,
rank: doneStateRanks[i],
name: st.name
}
)
client.createDoc(st.isWon ? task.class.WonStateTemplate : task.class.LostStateTemplate, data.space, {
rank: doneStateRanks[i],
ofAttribute: task.attribute.DoneState,
name: st.name,
attachedTo: data.kanbanId
})
)
)
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', {
client.createDoc(task.class.StateTemplate, data.space, {
attachedTo: data.kanbanId,
ofAttribute: task.attribute.State,
rank: stateRanks[i],
name: st.name,

View File

@ -30,7 +30,8 @@ export async function createBoard (
description,
private: false,
archived: false,
members: [getCurrentAccount()._id]
members: [getCurrentAccount()._id],
templateId
})
await Promise.all([createKanban(client, boardRef, templateId)])

View File

@ -184,7 +184,8 @@
archived: false,
number: (incResult as any).object.sequence,
company,
members: [getCurrentAccount()._id]
members: [getCurrentAccount()._id],
templateId
},
objectId
)

View File

@ -32,7 +32,6 @@
if (template === undefined) {
return
}
const hierarchy = client.getHierarchy()
showPopup(
MessageBox,
@ -43,15 +42,7 @@
undefined,
async (result) => {
if (result && template !== undefined) {
const collection = hierarchy.isDerived(state._class, task.class.DoneStateTemplate) ? 'doneStatesC' : 'statesC'
await client.removeCollection(
state._class,
template.space,
state._id,
template._id,
template._class,
collection
)
await client.removeDoc(state._class, template.space, state._id)
}
}
)

View File

@ -57,19 +57,13 @@
title: 'New Template'
})
await client.addCollection(
task.class.StateTemplate,
space as Ref<Doc> as Ref<Space>,
template,
task.class.KanbanTemplate,
'statesC',
{
ofAttribute: task.attribute.State,
name: 'New State',
color: 9,
rank: [...genRanks(1)][0]
}
)
await client.createDoc(task.class.StateTemplate, space as Ref<Doc> as Ref<Space>, {
attachedTo: template,
ofAttribute: task.attribute.State,
name: 'New State',
color: 9,
rank: [...genRanks(1)][0]
})
const ranks = [...genRanks(2)]
const doneStates = [
@ -87,18 +81,12 @@
await Promise.all(
doneStates.map(async (ds) => {
await client.addCollection(
ds.class,
space as Ref<Doc> as Ref<Space>,
template,
task.class.KanbanTemplate,
'doneStatesC',
{
ofAttribute: task.attribute.DoneState,
name: ds.name,
rank: ds.rank
}
)
await client.createDoc(ds.class, space as Ref<Doc> as Ref<Space>, {
attachedTo: template,
ofAttribute: task.attribute.DoneState,
name: ds.name,
rank: ds.rank
})
})
)
}

View File

@ -104,17 +104,19 @@
if (hierarchy.isDerived(_class, task.class.DoneState)) {
const targetClass = _class === task.class.WonState ? task.class.WonStateTemplate : task.class.LostStateTemplate
await client.addCollection(targetClass, kanban.space, kanban._id, kanban._class, 'doneStatesC', {
await client.createDoc(targetClass, kanban.space, {
ofAttribute: task.attribute.DoneState,
name: 'New Done State',
rank: calcRank(lastOne, undefined)
rank: calcRank(lastOne, undefined),
attachedTo: kanban._id
})
} else {
await client.addCollection(task.class.StateTemplate, kanban.space, kanban._id, kanban._class, 'statesC', {
await client.createDoc(task.class.StateTemplate, kanban.space, {
name: 'New State',
ofAttribute: task.attribute.DoneState,
color: 9,
rank: calcRank(lastOne, undefined)
rank: calcRank(lastOne, undefined),
attachedTo: kanban._id
})
}
}

View File

@ -45,7 +45,9 @@ export interface DocWithRank extends Doc {
/**
* @public
*/
export interface SpaceWithStates extends Space {}
export interface SpaceWithStates extends Space {
templateId?: Ref<KanbanTemplate>
}
// S T A T E
@ -123,12 +125,16 @@ export interface Sequence extends Doc {
/**
* @public
*/
export interface StateTemplate extends AttachedDoc, State {}
export interface StateTemplate extends Doc, State {
attachedTo: Ref<KanbanTemplate>
}
/**
* @public
*/
export interface DoneStateTemplate extends AttachedDoc, DoneState {}
export interface DoneStateTemplate extends Doc, DoneState {
attachedTo: Ref<KanbanTemplate>
}
/**
* @public
@ -159,6 +165,7 @@ export interface KanbanTemplateSpace extends Space {
description: IntlString
icon: AnyComponent
editor?: AnyComponent
attachedToClass: Ref<Class<Doc>>
}
/**

View File

@ -13,7 +13,95 @@
// limitations under the License.
//
import { Class, Doc, Ref, Space, Tx, TxCreateDoc, TxProcessor, TxUpdateDoc } from '@hcengineering/core'
import { TriggerControl } from '@hcengineering/server-core'
import core from '@hcengineering/core/lib/component'
import task, { KanbanTemplateSpace, KanbanTemplate, StateTemplate, State, DoneState } from '@hcengineering/task'
/**
* @public
*/
export async function OnTemplateStateUpdate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = tx as TxUpdateDoc<StateTemplate>
const txes = await control.findAll(core.class.TxCollectionCUD, {
'tx.objectId': actualTx.objectId
})
const createTxes = await control.findAll(core.class.TxCreateDoc, { objectId: actualTx.objectId })
const updateTxes = await control.findAll(core.class.TxUpdateDoc, { objectId: actualTx.objectId })
const prevDoc = TxProcessor.buildDoc2Doc(
[...createTxes, ...txes, ...updateTxes].filter((t) => t._id !== tx._id)
) as StateTemplate
if (prevDoc === undefined) return []
const templateSpace = (
await control.findAll(task.class.KanbanTemplateSpace, {
_id: actualTx.objectSpace as Ref<KanbanTemplateSpace>
})
)[0] as KanbanTemplateSpace
if (templateSpace === undefined) return []
const template = (await control.findAll(task.class.KanbanTemplate, { _id: prevDoc.attachedTo }))[0] as KanbanTemplate
const classToChange = getClassToChangeOrCreate(actualTx.objectClass)
const objectWithStatesToChange = await control.findAll(templateSpace.attachedToClass, { templateId: template._id })
const ids = Array.from(objectWithStatesToChange.map((x) => x._id)) as Array<Ref<Space>>
const newDoc = TxProcessor.buildDoc2Doc([...createTxes, ...txes, ...updateTxes]) as StateTemplate
const statesToChange = Array.from(
await control.findAll(classToChange, { space: { $in: ids }, name: prevDoc.name })
) as Array<State | DoneState>
return statesToChange.map((it) => {
const newAttributes = it._class === task.class.State ? { color: newDoc.color, rank: newDoc.rank } : {}
return control.txFactory.createTxUpdateDoc(it._class, it.space, it._id, {
name: newDoc.name,
...newAttributes
})
})
}
/**
* @public
*/
export async function OnTemplateStateCreate (tx: Tx, control: TriggerControl): Promise<Tx[]> {
const actualTx = tx as TxCreateDoc<StateTemplate>
const templateSpace = (
await control.findAll(task.class.KanbanTemplateSpace, {
_id: actualTx.objectSpace as Ref<KanbanTemplateSpace>
})
)[0] as KanbanTemplateSpace
if (templateSpace === undefined) return []
const template = (
await control.findAll(task.class.KanbanTemplate, { _id: actualTx.attributes.attachedTo })
)[0] as KanbanTemplate
const classToChange = getClassToChangeOrCreate(actualTx.objectClass)
const objectWithStatesToChange = await control.findAll(templateSpace.attachedToClass, { templateId: template._id })
const ids = Array.from(objectWithStatesToChange.map((x) => x._id)) as Array<Ref<Space>>
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 } : {}
return control.txFactory.createTxCreateDoc(classToChange, it, {
ofAttribute,
name: doc.name,
...newAttributes
})
})
}
function getClassToChangeOrCreate (check: Ref<Class<Doc>>): Ref<Class<Doc>> {
let classToChange = task.class.State
switch (check) {
case task.class.WonStateTemplate:
classToChange = task.class.WonState
break
case task.class.LostStateTemplate:
classToChange = task.class.LostState
break
}
return classToChange
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
function: {}
function: {},
trigger: {
OnTemplateStateUpdate,
OnTemplateStateCreate
}
})

View File

@ -16,6 +16,7 @@
import type { Plugin, Resource } from '@hcengineering/platform'
import { plugin } from '@hcengineering/platform'
import { Presenter } from '@hcengineering/server-notification'
import { TriggerFunc } from '@hcengineering/server-core'
/**
* @public
@ -29,5 +30,9 @@ export default plugin(serverTaskId, {
function: {
IssueHTMLPresenter: '' as Resource<Presenter>,
IssueTextPresenter: '' as Resource<Presenter>
},
trigger: {
OnTemplateStateUpdate: '' as Resource<TriggerFunc>,
OnTemplateStateCreate: '' as Resource<TriggerFunc>
}
})