// // 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' import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, Timestamp, TxOperations } from '@anticrm/core' import type { Asset, Plugin } from '@anticrm/platform' import { plugin } from '@anticrm/platform' import type { AnyComponent } from '@anticrm/ui' import { ViewletDescriptor } from '@anticrm/view' // S T A T E /** * @public */ export interface State extends DocWithRank { title: string color: string } /** * @public */ export interface DoneState extends DocWithRank { title: string } /** * @public */ export interface WonState extends DoneState {} /** * @public */ export interface LostState extends DoneState {} /** * @public */ export interface Task extends AttachedDoc, DocWithRank { state: Ref doneState: Ref | null number: number assignee: Ref | null todoItems?: number } /** * @public */ export interface TodoItem extends AttachedDoc { name: string done: boolean dueTo?: Timestamp } /** * @public */ export interface SpaceWithStates extends Space { } /** * @public */ export interface Project extends SpaceWithStates {} /** * @public */ export interface Issue extends Task { number: number // Sequence number name: string description: string comments?: number attachments?: number labels?: string } /** * @public */ export interface KanbanCard extends Class { card: AnyComponent } /** * @public */ export interface Kanban extends Doc { attachedTo: Ref } /** * @public */ export interface Sequence extends Doc { attachedTo: Ref> 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 } /** * @public */ export const taskId = 'task' as Plugin /** * @public */ const task = plugin(taskId, { mixin: { KanbanCard: '' as Ref> }, class: { Issue: '' as Ref>, Project: '' as Ref>, State: '' as Ref>, DoneState: '' as Ref>, WonState: '' as Ref>, LostState: '' as Ref>, SpaceWithStates: '' as Ref>, Task: '' as Ref>, Kanban: '' as Ref>, Sequence: '' as Ref>, StateTemplate: '' as Ref>, DoneStateTemplate: '' as Ref>, WonStateTemplate: '' as Ref>, LostStateTemplate: '' as Ref>, KanbanTemplate: '' as Ref>, KanbanTemplateSpace: '' as Ref>, TodoItem: '' as Ref> }, viewlet: { Kanban: '' as Ref }, icon: { Task: '' as Asset, Kanban: '' as Asset, Status: '' as Asset, TodoCheck: '' as Asset, TodoUnCheck: '' as Asset }, global: { // Global task root, if not attached to some other object. Task: '' as Ref }, space: { ProjectTemplates: '' as Ref, Sequence: '' as Ref } }) export default task /** * @public */ export async function createProjectKanban ( projectId: Ref, factory: (_class: Ref>, space: Ref, data: Data, id: Ref) => Promise ): Promise { const states = [ { color: '#7C6FCD', name: 'Open' }, { color: '#6F7BC5', name: 'In Progress' }, { color: '#77C07B', name: 'Under review' }, { color: '#A5D179', name: 'Done' }, { color: '#F28469', name: 'Invalid' } ] 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.name.toLowerCase().replace(' ', '_')) as Ref await factory( task.class.State, projectId, { title: st.name, color: st.color, rank }, sid ) } const doneStates = [ { class: task.class.WonState, title: 'Won' }, { class: task.class.LostState, title: 'Lost' } ] 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 await factory( st.class, projectId, { title: st.title, rank }, sid ) } await factory( task.class.Kanban, projectId, { attachedTo: projectId }, (projectId + '.kanban') as Ref ) } /** * @public */ export async function createKanban (client: Client & TxOperations, attachedTo: Ref, templateId?: Ref): Promise> { if (templateId === undefined) { 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] }) ]) 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, { color: state.color, title: state.title, 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, { title: state.title, rank: state.rank }) }) ) return await client.createDoc(task.class.Kanban, attachedTo, { attachedTo }) }