2021-08-07 06:34:28 +00:00
|
|
|
//
|
|
|
|
// 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 { Employee } from '@anticrm/contact'
|
2021-12-20 10:18:29 +00:00
|
|
|
import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, Timestamp, TxOperations } from '@anticrm/core'
|
2021-11-30 15:39:08 +00:00
|
|
|
import type { Asset, Plugin } from '@anticrm/platform'
|
|
|
|
import { plugin } from '@anticrm/platform'
|
2021-12-15 09:04:43 +00:00
|
|
|
import type { AnyComponent } from '@anticrm/ui'
|
|
|
|
import { ViewletDescriptor } from '@anticrm/view'
|
|
|
|
|
|
|
|
// S T A T E
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-17 09:08:37 +00:00
|
|
|
export interface State extends DocWithRank {
|
2021-12-15 09:04:43 +00:00
|
|
|
title: string
|
|
|
|
color: string
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-17 09:08:37 +00:00
|
|
|
export interface DoneState extends DocWithRank {
|
2021-12-15 09:04:43 +00:00
|
|
|
title: string
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface WonState extends DoneState {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface LostState extends DoneState {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-17 09:08:37 +00:00
|
|
|
export interface Task extends AttachedDoc, DocWithRank {
|
2021-12-15 09:04:43 +00:00
|
|
|
state: Ref<State>
|
|
|
|
doneState: Ref<DoneState> | null
|
|
|
|
number: number
|
|
|
|
assignee: Ref<Employee> | null
|
2021-12-20 10:18:29 +00:00
|
|
|
|
|
|
|
todoItems?: number
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface TodoItem extends AttachedDoc {
|
|
|
|
name: string
|
|
|
|
done: boolean
|
|
|
|
dueTo?: Timestamp
|
2021-12-15 09:04:43 +00:00
|
|
|
}
|
2021-08-07 06:34:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-15 09:04:43 +00:00
|
|
|
export interface SpaceWithStates extends Space {
|
|
|
|
}
|
2021-08-07 06:34:28 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
2021-12-15 09:04:43 +00:00
|
|
|
export interface Project extends SpaceWithStates {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface Issue extends Task {
|
2021-11-30 15:39:08 +00:00
|
|
|
number: number // Sequence number
|
|
|
|
|
|
|
|
name: string
|
2021-08-07 06:34:28 +00:00
|
|
|
description: string
|
2021-11-30 15:39:08 +00:00
|
|
|
|
|
|
|
comments?: number
|
|
|
|
attachments?: number
|
|
|
|
labels?: string
|
2021-08-07 06:34:28 +00:00
|
|
|
}
|
|
|
|
|
2021-12-15 09:04:43 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface KanbanCard extends Class<Doc> {
|
|
|
|
card: AnyComponent
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface Kanban extends Doc {
|
|
|
|
attachedTo: Ref<Space>
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface Sequence extends Doc {
|
|
|
|
attachedTo: Ref<Class<Doc>>
|
|
|
|
sequence: number
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface StateTemplate extends AttachedDoc, State {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface DoneStateTemplate extends AttachedDoc, DoneState {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface WonStateTemplate extends DoneStateTemplate, WonState {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface LostStateTemplate extends DoneStateTemplate, LostState {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface KanbanTemplate extends Doc {
|
|
|
|
title: string
|
|
|
|
statesC: number
|
|
|
|
doneStatesC: number
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export interface KanbanTemplateSpace extends Space {
|
|
|
|
icon: AnyComponent
|
|
|
|
}
|
|
|
|
|
2021-08-07 06:34:28 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export const taskId = 'task' as Plugin
|
|
|
|
|
2021-12-15 09:04:43 +00:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
const task = plugin(taskId, {
|
|
|
|
mixin: {
|
|
|
|
KanbanCard: '' as Ref<Mixin<KanbanCard>>
|
|
|
|
},
|
2021-08-07 06:34:28 +00:00
|
|
|
class: {
|
2021-12-15 09:04:43 +00:00
|
|
|
Issue: '' as Ref<Class<Issue>>,
|
|
|
|
Project: '' as Ref<Class<Project>>,
|
|
|
|
State: '' as Ref<Class<State>>,
|
|
|
|
DoneState: '' as Ref<Class<DoneState>>,
|
|
|
|
WonState: '' as Ref<Class<WonState>>,
|
|
|
|
LostState: '' as Ref<Class<LostState>>,
|
|
|
|
SpaceWithStates: '' as Ref<Class<SpaceWithStates>>,
|
2021-11-30 15:39:08 +00:00
|
|
|
Task: '' as Ref<Class<Task>>,
|
2021-12-15 09:04:43 +00:00
|
|
|
Kanban: '' as Ref<Class<Kanban>>,
|
|
|
|
Sequence: '' as Ref<Class<Sequence>>,
|
|
|
|
StateTemplate: '' as Ref<Class<StateTemplate>>,
|
|
|
|
DoneStateTemplate: '' as Ref<Class<DoneStateTemplate>>,
|
|
|
|
WonStateTemplate: '' as Ref<Class<WonStateTemplate>>,
|
|
|
|
LostStateTemplate: '' as Ref<Class<LostStateTemplate>>,
|
|
|
|
KanbanTemplate: '' as Ref<Class<KanbanTemplate>>,
|
2021-12-20 10:18:29 +00:00
|
|
|
KanbanTemplateSpace: '' as Ref<Class<KanbanTemplateSpace>>,
|
|
|
|
TodoItem: '' as Ref<Class<TodoItem>>
|
2021-12-15 09:04:43 +00:00
|
|
|
},
|
|
|
|
viewlet: {
|
|
|
|
Kanban: '' as Ref<ViewletDescriptor>
|
2021-08-07 06:34:28 +00:00
|
|
|
},
|
|
|
|
icon: {
|
2021-12-15 09:04:43 +00:00
|
|
|
Task: '' as Asset,
|
|
|
|
Kanban: '' as Asset,
|
2021-12-20 10:18:29 +00:00
|
|
|
Status: '' as Asset,
|
|
|
|
TodoCheck: '' as Asset,
|
|
|
|
TodoUnCheck: '' as Asset
|
2021-12-15 09:04:43 +00:00
|
|
|
},
|
|
|
|
global: {
|
|
|
|
// Global task root, if not attached to some other object.
|
|
|
|
Task: '' as Ref<Issue>
|
2021-12-14 09:24:14 +00:00
|
|
|
},
|
|
|
|
space: {
|
2021-12-15 09:04:43 +00:00
|
|
|
ProjectTemplates: '' as Ref<KanbanTemplateSpace>,
|
|
|
|
Sequence: '' as Ref<Space>
|
2021-08-07 06:34:28 +00:00
|
|
|
}
|
|
|
|
})
|
2021-12-15 09:04:43 +00:00
|
|
|
export default task
|
2021-12-03 05:49:00 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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 = [
|
|
|
|
{ color: '#7C6FCD', name: 'Open' },
|
|
|
|
{ color: '#6F7BC5', name: 'In Progress' },
|
|
|
|
{ color: '#77C07B', name: 'Under review' },
|
|
|
|
{ color: '#A5D179', name: 'Done' },
|
|
|
|
{ color: '#F28469', name: 'Invalid' }
|
|
|
|
]
|
2021-12-17 09:08:37 +00:00
|
|
|
const stateRank = genRanks(states.length)
|
2021-12-03 05:49:00 +00:00
|
|
|
for (const st of states) {
|
2021-12-17 09:08:37 +00:00
|
|
|
const rank = stateRank.next().value
|
|
|
|
|
|
|
|
if (rank === undefined) {
|
|
|
|
throw Error('Failed to generate rank')
|
|
|
|
}
|
|
|
|
|
2021-12-03 05:49:00 +00:00
|
|
|
const sid = (projectId + '.state.' + st.name.toLowerCase().replace(' ', '_')) as Ref<State>
|
|
|
|
await factory(
|
2021-12-15 09:04:43 +00:00
|
|
|
task.class.State,
|
2021-12-03 05:49:00 +00:00
|
|
|
projectId,
|
|
|
|
{
|
|
|
|
title: st.name,
|
2021-12-17 09:08:37 +00:00
|
|
|
color: st.color,
|
|
|
|
rank
|
2021-12-03 05:49:00 +00:00
|
|
|
},
|
|
|
|
sid
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-17 09:08:37 +00:00
|
|
|
const doneStates = [
|
2021-12-15 09:04:43 +00:00
|
|
|
{ class: task.class.WonState, title: 'Won' },
|
|
|
|
{ class: task.class.LostState, title: 'Lost' }
|
2021-12-14 09:24:14 +00:00
|
|
|
]
|
2021-12-17 09:08:37 +00:00
|
|
|
const doneStateRank = genRanks(doneStates.length)
|
|
|
|
for (const st of doneStates) {
|
|
|
|
const rank = doneStateRank.next().value
|
|
|
|
|
|
|
|
if (rank === undefined) {
|
|
|
|
throw Error('Failed to generate rank')
|
|
|
|
}
|
|
|
|
|
2021-12-14 09:24:14 +00:00
|
|
|
const sid = (projectId + '.done-state.' + st.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
|
|
|
|
await factory(
|
|
|
|
st.class,
|
|
|
|
projectId,
|
|
|
|
{
|
2021-12-17 09:08:37 +00:00
|
|
|
title: st.title,
|
|
|
|
rank
|
2021-12-14 09:24:14 +00:00
|
|
|
},
|
|
|
|
sid
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2021-12-03 05:49:00 +00:00
|
|
|
await factory(
|
2021-12-15 09:04:43 +00:00
|
|
|
task.class.Kanban,
|
2021-12-03 05:49:00 +00:00
|
|
|
projectId,
|
|
|
|
{
|
2021-12-17 09:08:37 +00:00
|
|
|
attachedTo: projectId
|
2021-12-03 05:49:00 +00:00
|
|
|
},
|
2021-12-15 09:04:43 +00:00
|
|
|
(projectId + '.kanban') as Ref<Kanban>
|
2021-12-03 05:49:00 +00:00
|
|
|
)
|
|
|
|
}
|
2021-12-15 09:04:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
export async function createKanban (client: Client & TxOperations, attachedTo: Ref<Space>, templateId?: Ref<KanbanTemplate>): Promise<Ref<Kanban>> {
|
|
|
|
if (templateId === undefined) {
|
2021-12-17 09:08:37 +00:00
|
|
|
const ranks = [...genRanks(2)]
|
|
|
|
await Promise.all([
|
|
|
|
client.createDoc(task.class.WonState, attachedTo, {
|
|
|
|
title: 'Won',
|
|
|
|
rank: ranks[0]
|
|
|
|
}),
|
|
|
|
client.createDoc(task.class.LostState, attachedTo, {
|
|
|
|
title: 'Lost',
|
|
|
|
rank: ranks[1]
|
|
|
|
})
|
|
|
|
])
|
2021-12-15 09:04:43 +00:00
|
|
|
return await client.createDoc(task.class.Kanban, attachedTo, {
|
2021-12-17 09:08:37 +00:00
|
|
|
attachedTo
|
2021-12-15 09:04:43 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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 })
|
2021-12-17 09:08:37 +00:00
|
|
|
await Promise.all(
|
|
|
|
tmplStates.map(async (state) => await client.createDoc(
|
|
|
|
task.class.State,
|
|
|
|
attachedTo,
|
|
|
|
{
|
|
|
|
color: state.color,
|
|
|
|
title: state.title,
|
|
|
|
rank: state.rank
|
|
|
|
})))
|
2021-12-15 09:04:43 +00:00
|
|
|
|
|
|
|
const doneClassMap = new Map<Ref<Class<DoneStateTemplate>>, Ref<Class<DoneState>>>([
|
|
|
|
[task.class.WonStateTemplate, task.class.WonState],
|
|
|
|
[task.class.LostStateTemplate, task.class.LostState]
|
|
|
|
])
|
|
|
|
const tmplDoneStates = await client.findAll(task.class.DoneStateTemplate, { attachedTo: template._id })
|
2021-12-17 09:08:37 +00:00
|
|
|
await Promise.all(
|
|
|
|
tmplDoneStates.map(async (state) => {
|
|
|
|
const cl = doneClassMap.get(state._class)
|
|
|
|
|
|
|
|
if (cl === undefined) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
return await client.createDoc(cl, attachedTo, { title: state.title, rank: state.rank })
|
|
|
|
})
|
|
|
|
)
|
2021-12-15 09:04:43 +00:00
|
|
|
|
|
|
|
return await client.createDoc(task.class.Kanban, attachedTo, {
|
2021-12-17 09:08:37 +00:00
|
|
|
attachedTo
|
2021-12-15 09:04:43 +00:00
|
|
|
})
|
|
|
|
}
|