Signed-off-by: Ilya Sumbatyants <ilya.sumb@gmail.com>
This commit is contained in:
Ilya Sumbatyants 2021-12-17 16:08:37 +07:00 committed by GitHub
parent 34b9495631
commit b0ffc0ed46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 482 additions and 705 deletions

View File

@ -51,7 +51,6 @@ specifiers:
'@rush-temp/model-server-chunter': file:./projects/model-server-chunter.tgz
'@rush-temp/model-server-core': file:./projects/model-server-core.tgz
'@rush-temp/model-server-recruit': file:./projects/model-server-recruit.tgz
'@rush-temp/model-server-task': file:./projects/model-server-task.tgz
'@rush-temp/model-setting': file:./projects/model-setting.tgz
'@rush-temp/model-task': file:./projects/model-task.tgz
'@rush-temp/model-telegram': file:./projects/model-telegram.tgz
@ -74,8 +73,6 @@ specifiers:
'@rush-temp/server-core': file:./projects/server-core.tgz
'@rush-temp/server-recruit': file:./projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:./projects/server-recruit-resources.tgz
'@rush-temp/server-task': file:./projects/server-task.tgz
'@rush-temp/server-task-resources': file:./projects/server-task-resources.tgz
'@rush-temp/server-ws': file:./projects/server-ws.tgz
'@rush-temp/setting': file:./projects/setting.tgz
'@rush-temp/setting-assets': file:./projects/setting-assets.tgz
@ -144,6 +141,7 @@ specifiers:
koa: ^2.13.1
koa-bodyparser: ^4.3.0
koa-router: ^10.1.1
lexorank: ~1.0.4
mini-css-extract-plugin: ^2.2.0
minio: ^7.0.19
node-html-parser: ^4.1.3
@ -220,7 +218,6 @@ dependencies:
'@rush-temp/model-server-chunter': file:projects/model-server-chunter.tgz_typescript@4.4.3
'@rush-temp/model-server-core': file:projects/model-server-core.tgz_typescript@4.4.3
'@rush-temp/model-server-recruit': file:projects/model-server-recruit.tgz_typescript@4.4.3
'@rush-temp/model-server-task': file:projects/model-server-task.tgz_typescript@4.4.3
'@rush-temp/model-setting': file:projects/model-setting.tgz_typescript@4.4.3
'@rush-temp/model-task': file:projects/model-task.tgz_typescript@4.4.3
'@rush-temp/model-telegram': file:projects/model-telegram.tgz_typescript@4.4.3
@ -243,8 +240,6 @@ dependencies:
'@rush-temp/server-core': file:projects/server-core.tgz
'@rush-temp/server-recruit': file:projects/server-recruit.tgz
'@rush-temp/server-recruit-resources': file:projects/server-recruit-resources.tgz
'@rush-temp/server-task': file:projects/server-task.tgz
'@rush-temp/server-task-resources': file:projects/server-task-resources.tgz
'@rush-temp/server-ws': file:projects/server-ws.tgz
'@rush-temp/setting': file:projects/setting.tgz
'@rush-temp/setting-assets': file:projects/setting-assets.tgz
@ -313,6 +308,7 @@ dependencies:
koa: 2.13.3
koa-bodyparser: 4.3.0
koa-router: 10.1.1
lexorank: 1.0.4
mini-css-extract-plugin: 2.4.1_webpack@5.57.1
minio: 7.0.19
node-html-parser: 4.1.5
@ -6409,6 +6405,10 @@ packages:
type-check: 0.4.0
dev: false
/lexorank/1.0.4:
resolution: {integrity: sha512-CMgA8AMJIX/QfoYHKyjg0hv9W1SGL2xRkt0uLyhT9xKKRj73fHi+IhsrB3W36wwk4I0iz8YlKHfdW14QDwerMA==}
dev: false
/lilconfig/2.0.3:
resolution: {integrity: sha512-EHKqr/+ZvdKCifpNrJCKxBTgk5XupZA3y/aCPY9mxfgBzmgh93Mt/WqjjQ38oMxXuvDokaKiM3lAgvSH2sjtHg==}
engines: {node: '>=10'}
@ -10345,7 +10345,7 @@ packages:
dev: false
file:projects/core.tgz:
resolution: {integrity: sha512-1RXdycw5v+KGu+kmqXvnrKAZpNYbc0U/rInLVwKYJ17U/hED4ONAIPEaVDxp+fxgyPHpEo7AQjZe2aD87ogwtg==, tarball: file:projects/core.tgz}
resolution: {integrity: sha512-Xjk/aGZgRnNWYrQpTy8f2H8oQVVIvbkX6MdesJnRH7AjJTI2RtloF+8sUQMSjSahPh7thHwaYyDVlJ37vMXXNQ==, tarball: file:projects/core.tgz}
name: '@rush-temp/core'
version: 0.0.0
dependencies:
@ -10359,6 +10359,7 @@ packages:
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.1.1_eslint@7.32.0
just-clone: 3.2.1
lexorank: 1.0.4
prettier: 2.4.1
simplytyped: 3.3.0_typescript@4.4.3
typescript: 4.4.3
@ -10770,7 +10771,7 @@ packages:
dev: false
file:projects/model-all.tgz_typescript@4.4.3:
resolution: {integrity: sha512-XyhGIWfnSxUKXSXQ/yfvCx+S5xqBMJ4eko1pDFv0v048sQJy6fefxg3LOLyCDodNzsa5KxGdqvk7Y8H7Izop0g==, tarball: file:projects/model-all.tgz}
resolution: {integrity: sha512-iCugm1e/KTBXZ0FNnog/eP07l49sFIDU2orN0vAMUJ5pgZC7T38LSjnOL2y80OmoTbQK/3G2eH7Or+bxOYk2Cg==, tarball: file:projects/model-all.tgz}
id: file:projects/model-all.tgz
name: '@rush-temp/model-all'
version: 0.0.0
@ -10837,7 +10838,7 @@ packages:
dev: false
file:projects/model-contact.tgz_typescript@4.4.3:
resolution: {integrity: sha512-qZPBOQC2Oi/Qxpjt91OM1tGBPwShe2SLIHKmDNGSsk79zzdmW3+ZDrge4VVrK/ngc5P2fJQwpQRXgOkNgz2vnA==, tarball: file:projects/model-contact.tgz}
resolution: {integrity: sha512-ffJA0hXhTeEjKGbCFKTgYk9ch8JsAnZKmDOtyTSzE+tmIklA0Q1gWQGwqttPSgrGVQuy2UXkYXv1m7cnhBICxg==, tarball: file:projects/model-contact.tgz}
id: file:projects/model-contact.tgz
name: '@rush-temp/model-contact'
version: 0.0.0
@ -11023,27 +11024,6 @@ packages:
- typescript
dev: false
file:projects/model-server-task.tgz_typescript@4.4.3:
resolution: {integrity: sha512-FZRz0sa1wBabVBBZietKGBDDF6vrJzSH6bcWlm8K3OkLl9jHoZlXRjBHIx4f0Vo1DSIPSH++/gULbgNAsd2p1A==, tarball: file:projects/model-server-task.tgz}
id: file:projects/model-server-task.tgz
name: '@rush-temp/model-server-task'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.4.0_87dbf04088b125598d0271706532eaf3
'@typescript-eslint/parser': 5.4.0_eslint@7.32.0+typescript@4.4.3
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_05a8ea1454e6ca4c9f98b94b8f3abf9c
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.1.1_eslint@7.32.0
prettier: 2.4.1
transitivePeerDependencies:
- supports-color
- typescript
dev: false
file:projects/model-setting.tgz_typescript@4.4.3:
resolution: {integrity: sha512-U6P3iLbt33/iJI21fs7trAHtpwWzmI/zJn+3qyxjjnFP88i11vmbd1zfb6+GYu5muRCutqNi1pTX/5cDo+NtrA==, tarball: file:projects/model-setting.tgz}
id: file:projects/model-setting.tgz
@ -11339,7 +11319,7 @@ packages:
dev: false
file:projects/prod.tgz_sass@1.42.1+typescript@4.4.3:
resolution: {integrity: sha512-jgSulZmT4Kq28boQo8BJQrdkcN660EtY7XUH0QWcgGYHDBXFiGfP4eoIxAJL4wtb/JKwKLEkwG+SwTUIuIb7Xw==, tarball: file:projects/prod.tgz}
resolution: {integrity: sha512-cWieTfrEsukKvJvSGr/ZOX6Vo4aZQHGDpOrE2smLt21Rk+/lSNpUcpAKKGRoFmW08sWkQoYrU6BdPMgw4+/UNQ==, tarball: file:projects/prod.tgz}
id: file:projects/prod.tgz
name: '@rush-temp/prod'
version: 0.0.0
@ -11573,47 +11553,6 @@ packages:
- supports-color
dev: false
file:projects/server-task-resources.tgz:
resolution: {integrity: sha512-Y2B9G8w4IpTnfIoDV+LtiARLb568CfTKG28VyMc2faS4QUz62f3PLU+ODB8NxwG5f1LsJf8DLrZM8fPRzYdc6w==, tarball: file:projects/server-task-resources.tgz}
name: '@rush-temp/server-task-resources'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
'@types/heft-jest': 1.0.2
'@typescript-eslint/eslint-plugin': 5.4.0_87dbf04088b125598d0271706532eaf3
'@typescript-eslint/parser': 5.4.0_eslint@7.32.0+typescript@4.4.3
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_05a8ea1454e6ca4c9f98b94b8f3abf9c
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.1.1_eslint@7.32.0
prettier: 2.4.1
typescript: 4.4.3
transitivePeerDependencies:
- supports-color
dev: false
file:projects/server-task.tgz:
resolution: {integrity: sha512-qaB/cZSAPKuvTUfBv5wsqD0r/YoQO/hy7rD7WBC0lx2TOWIp0/aacYKVkEltinbYjJzP4gg9cQQNful4Mvpp5Q==, tarball: file:projects/server-task.tgz}
name: '@rush-temp/server-task'
version: 0.0.0
dependencies:
'@rushstack/heft': 0.41.1
'@types/heft-jest': 1.0.2
'@types/node': 16.11.12
'@typescript-eslint/eslint-plugin': 5.4.0_87dbf04088b125598d0271706532eaf3
'@typescript-eslint/parser': 5.4.0_eslint@7.32.0+typescript@4.4.3
eslint: 7.32.0
eslint-config-standard-with-typescript: 21.0.1_05a8ea1454e6ca4c9f98b94b8f3abf9c
eslint-plugin-import: 2.25.3_eslint@7.32.0
eslint-plugin-node: 11.1.0_eslint@7.32.0
eslint-plugin-promise: 5.1.1_eslint@7.32.0
prettier: 2.4.1
typescript: 4.4.3
transitivePeerDependencies:
- supports-color
dev: false
file:projects/server-ws.tgz:
resolution: {integrity: sha512-MSFFpLjIMFt0oyH4+8JUkNOkCNtdEtMDoxcyN7+kDdz44wSZjSOmheJHYkXO6JTEffcaaRhQ9vO/e7MBNMeoxQ==, tarball: file:projects/server-ws.tgz}
name: '@rush-temp/server-ws'
@ -11641,7 +11580,7 @@ packages:
dev: false
file:projects/server.tgz:
resolution: {integrity: sha512-fFYknnDfAOz+iq8INHKSgCjYUkbxqLiHIBM55W8QtmZq0R8hMLEwCcLiyNqL1iCgXKs8sGHf+aMbZcciBNi1Ow==, tarball: file:projects/server.tgz}
resolution: {integrity: sha512-UBFOAisptjpTNIZoU6hWJVBKTk7mftZU0Dz+pJiVhMBJSD+iW393yv4v9GedEpor5rIYDGGcps7IVCXZlr76tw==, tarball: file:projects/server.tgz}
name: '@rush-temp/server'
version: 0.0.0
dependencies:

View File

@ -1,5 +1,5 @@
import { Ref, TxOperations } from '@anticrm/core'
import { genRanks, Ref, TxOperations } from '@anticrm/core'
import task, { DoneState, Kanban, SpaceWithStates, State } from '@anticrm/task'
import { findOrUpdate } from './utils'
@ -12,42 +12,55 @@ export async function createUpdateSpaceKanban (spaceId: Ref<SpaceWithStates>, cl
{ color: '#F28469', name: 'Invalid' }
]
const states: Array<Ref<State>> = []
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<State>
await findOrUpdate(client, spaceId, task.class.State,
sid,
{
title: st.name,
color: st.color
color: st.color,
rank
}
)
states.push(sid)
}
const rawDoneStates = [
const doneStates = [
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
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.title.toLowerCase().replace(' ', '_')) as Ref<DoneState>
await findOrUpdate(client, spaceId, st.class,
sid,
{
title: st.title
title: st.title,
rank
}
)
doneStates.push(sid)
}
await findOrUpdate(client, spaceId,
task.class.Kanban,
('generated-' + spaceId + '.kanban') as Ref<Kanban>,
{
attachedTo: spaceId,
states,
doneStates,
order: []
attachedTo: spaceId
}
)
return states

View File

@ -1,5 +1,5 @@
import contact from '@anticrm/contact'
import core, { AttachedData, Data, generateId, Ref, TxOperations } from '@anticrm/core'
import core, { AttachedData, Data, generateId, genRanks, Ref, TxOperations } from '@anticrm/core'
import recruit from '@anticrm/model-recruit'
import { Applicant, Candidate, Vacancy } from '@anticrm/recruit'
import faker from 'faker'
@ -79,7 +79,7 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
}
const vacancyId = (options.random ? `vacancy-${generateId()}-${i}` : `vacancy-genid-${i}`) as Ref<Vacancy>
console.log('Creating vacandy', vacancy.name)
console.log('Creating vacancy', vacancy.name)
// Update or create candidate
await findOrUpdate(client, core.space.Model, recruit.class.Vacancy, vacancyId, vacancy)
@ -93,14 +93,22 @@ export async function generateContacts (transactorUrl: string, dbName: string, o
console.log('States generated', vacancy.name)
const rankGen = genRanks(candidates.length)
for (const candidateId of candidates) {
const rank = rankGen.next().value
if (rank === undefined) {
throw Error('Failed to generate rank')
}
const applicantId = `vacancy-${vacancyId}-${candidateId}` as Ref<Applicant>
const applicant: AttachedData<Applicant> = {
number: faker.datatype.number(),
assignee: faker.random.arrayElement(emoloyeeIds),
state: faker.random.arrayElement(states),
doneState: null
doneState: null,
rank
}
// Update or create candidate

View File

@ -71,8 +71,6 @@
"@anticrm/contact-assets": "~0.6.0",
"@anticrm/server-recruit": "~0.6.1",
"@anticrm/server-recruit-resources": "~0.6.0",
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-task-resources": "~0.6.0",
"@anticrm/activity": "~0.6.0",
"@anticrm/activity-assets": "~0.6.0",
"@anticrm/activity-resources": "~0.6.0",

View File

@ -19,7 +19,6 @@ import login from '@anticrm/login'
import { clientId } from '@anticrm/client'
import { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit'
import { serverViewId } from '@anticrm/server-task'
import { setMetadata } from '@anticrm/platform'
@ -33,7 +32,6 @@ export function configurePlatformDev() {
addLocation(clientId, () => import(/* webpackChunkName: "client-dev" */ '@anticrm/dev-client-resources'))
addLocation(serverChunterId, () => import(/* webpackChunkName: "server-chunter" */ '@anticrm/dev-server-chunter-resources'))
addLocation(serverRecruitId, () => import(/* webpackChunkName: "server-recruit" */ '@anticrm/server-recruit-resources'))
addLocation(serverViewId, () => import(/* webpackChunkName: "server-task" */ '@anticrm/server-task-resources'))
// Set devmodel to hook client to be able to present all activity
enableDevModel()

View File

@ -43,7 +43,6 @@
"@anticrm/model-server-core": "~0.6.0",
"@anticrm/model-server-chunter": "~0.6.0",
"@anticrm/model-server-recruit": "~0.6.0",
"@anticrm/model-server-task": "~0.6.0",
"@anticrm/model-activity": "~0.6.0",
"@anticrm/model-attachment": "~0.6.0",
"@anticrm/core": "~0.6.13"

View File

@ -30,7 +30,6 @@ import { createModel as leadModel } from '@anticrm/model-lead'
import { createModel as serverCoreModel } from '@anticrm/model-server-core'
import { createModel as serverChunterModel } from '@anticrm/model-server-chunter'
import { createModel as serverRecruitModel } from '@anticrm/model-server-recruit'
import { createModel as serverViewModel } from '@anticrm/model-server-task'
import { createModel as activityModel } from '@anticrm/model-activity'
import { createDemo } from '@anticrm/model-demo'
@ -53,7 +52,6 @@ leadModel(builder)
serverCoreModel(builder)
serverChunterModel(builder)
serverRecruitModel(builder)
serverViewModel(builder)
createDemo(builder)

View File

@ -15,7 +15,7 @@
import type { Account, Arr, Ref, Space } from '@anticrm/core'
import { DOMAIN_MODEL } from '@anticrm/core'
import { Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
import { Implements, Model, Prop, TypeBoolean, TypeString } from '@anticrm/model'
import type { IntlString } from '@anticrm/platform'
import core from './component'
import { TDoc } from './core'
@ -40,3 +40,8 @@ export class TSpace extends TDoc implements Space {
export class TAccount extends TDoc implements Account {
email!: string
}
@Implements(core.interface.DocWithRank)
export class TDocWithRank extends TDoc {
@Prop(TypeString(), 'Rank' as IntlString)
rank!: string
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,18 +0,0 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/model-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -1,34 +0,0 @@
{
"name": "@anticrm/model-server-task",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/model-rig": "~0.6.0",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/model": "~0.6.0",
"@anticrm/platform": "~0.6.5",
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-core": "~0.6.0"
}
}

View File

@ -1,27 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// 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 { Builder } from '@anticrm/model'
import serverCore from '@anticrm/server-core'
import core from '@anticrm/core'
import serverTask from '@anticrm/server-task'
export function createModel (builder: Builder): void {
builder.createDoc(serverCore.class.Trigger, core.space.Model, {
trigger: serverTask.trigger.OnTask
})
}

View File

@ -1,8 +0,0 @@
{
"extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
}
}

View File

@ -53,18 +53,22 @@ export { default } from './plugin'
export const DOMAIN_TASK = 'task' as Domain
export const DOMAIN_STATE = 'state' as Domain
export const DOMAIN_KANBAN = 'kanban' as Domain
@Model(task.class.State, core.class.Doc, DOMAIN_STATE)
@Model(task.class.State, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank])
export class TState extends TDoc implements State {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
color!: string
declare rank: string
}
@Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE)
@Model(task.class.DoneState, core.class.Doc, DOMAIN_STATE, [core.interface.DocWithRank])
export class TDoneState extends TDoc implements DoneState {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
declare rank: string
}
@Model(task.class.WonState, task.class.DoneState, DOMAIN_STATE)
@ -78,7 +82,7 @@ export class TLostState extends TDoneState implements LostState {}
*
* No domain is specified, since pure Tasks could not exists
*/
@Model(task.class.Task, core.class.AttachedDoc, DOMAIN_TASK)
@Model(task.class.Task, core.class.AttachedDoc, DOMAIN_TASK, [core.interface.DocWithRank])
export class TTask extends TAttachedDoc implements Task {
@Prop(TypeRef(task.class.State), 'State' as IntlString)
state!: Ref<State>
@ -91,6 +95,8 @@ export class TTask extends TAttachedDoc implements Task {
// @Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString)
assignee!: Ref<Employee> | null
declare rank: string
}
@Model(task.class.SpaceWithStates, core.class.Space)
@ -136,7 +142,6 @@ export class TKanban extends TDoc implements Kanban {
states!: Arr<Ref<State>>
doneStates!: Arr<Ref<DoneState>>
attachedTo!: Ref<Space>
order!: Arr<Ref<Doc>>
}
@Model(task.class.KanbanTemplateSpace, core.class.Space, DOMAIN_MODEL)
@ -144,19 +149,23 @@ export class TKanbanTemplateSpace extends TSpace implements KanbanTemplateSpace
icon!: AnyComponent
}
@Model(task.class.StateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN)
@Model(task.class.StateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN, [core.interface.DocWithRank])
export class TStateTemplate extends TAttachedDoc implements StateTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
@Prop(TypeString(), 'Color' as IntlString)
color!: string
declare rank: string
}
@Model(task.class.DoneStateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN)
@Model(task.class.DoneStateTemplate, core.class.AttachedDoc, DOMAIN_KANBAN, [core.interface.DocWithRank])
export class TDoneStateTemplate extends TAttachedDoc implements DoneStateTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
declare rank: string
}
@Model(task.class.WonStateTemplate, task.class.DoneStateTemplate, DOMAIN_KANBAN)
@ -170,9 +179,6 @@ export class TKanbanTemplate extends TDoc implements KanbanTemplate {
@Prop(TypeString(), 'Title' as IntlString)
title!: string
states!: Arr<Ref<StateTemplate>>
doneStates!: Arr<Ref<DoneStateTemplate>>
@Prop(Collection(task.class.StateTemplate), 'States' as IntlString)
statesC!: number

View File

@ -13,7 +13,7 @@
// limitations under the License.
//
import { Class, Doc, Domain, DOMAIN_TX, Ref, TxCUD, TxOperations } from '@anticrm/core'
import { AttachedDoc, Class, Client, Doc, DocWithRank, Domain, DOMAIN_TX, genRanks, Ref, Space, TxCUD, TxOperations } from '@anticrm/core'
import {
MigrateOperation,
MigrateUpdate,
@ -22,7 +22,7 @@ import {
MigrationUpgradeClient
} from '@anticrm/model'
import core from '@anticrm/model-core'
import { createProjectKanban } from '@anticrm/task'
import { createProjectKanban, KanbanTemplate } from '@anticrm/task'
import { DOMAIN_TASK, DOMAIN_STATE, DOMAIN_KANBAN } from '.'
import task from './plugin'
@ -134,28 +134,148 @@ export const taskOperation: MigrateOperation = {
console.log('View: Performing model upgrades')
const kanbans = (await client.findAll(task.class.Kanban, {})).filter((kanban) => kanban.doneStates == null)
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 })
}
}
await Promise.all(
kanbans.map(async (kanban) => {
console.log(`Updating kanban: ${kanban._id}`)
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 {
const doneStates = await Promise.all([
ops.createDoc(task.class.WonState, kanban.space, {
title: 'Won'
await Promise.all([
ops.createDoc(task.class.WonState, space._id, {
title: 'Won',
rank: pairRanks[0]
}),
ops.createDoc(task.class.LostState, kanban.space, {
title: 'Lost'
ops.createDoc(task.class.LostState, space._id, {
title: 'Lost',
rank: pairRanks[1]
})
])
await ops.updateDoc(kanban._class, kanban.space, kanban._id, {
doneStates
})
} 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 })
}
}
}

View File

@ -29,6 +29,7 @@
},
"dependencies": {
"@anticrm/platform": "~0.6.5",
"just-clone": "^3.2.1"
"just-clone": "^3.2.1",
"lexorank": "~1.0.4"
}
}

View File

@ -218,3 +218,10 @@ export interface Space extends Doc {
export interface Account extends Doc {
email: string
}
/**
* @public
*/
export interface DocWithRank extends Doc {
rank: string
}

View File

@ -14,7 +14,7 @@
//
import type { Plugin, StatusCode } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { Account, AnyAttribute, AttachedDoc, Class, Doc, Interface, Obj, PropertyType, Ref, Space, Timestamp, Type, Collection, RefTo } from './classes'
import type { Account, AnyAttribute, AttachedDoc, DocWithRank, Class, Doc, Interface, Obj, PropertyType, Ref, Space, Timestamp, Type, Collection, RefTo } from './classes'
import type { Tx, TxBulkWrite, TxCollectionCUD, TxCreateDoc, TxCUD, TxMixin, TxPutBag, TxRemoveDoc, TxUpdateDoc } from './tx'
/**
@ -49,6 +49,9 @@ export default plugin(coreId, {
Collection: '' as Ref<Class<Collection<AttachedDoc>>>,
Bag: '' as Ref<Class<Type<Record<string, PropertyType>>>>
},
interface: {
DocWithRank: '' as Ref<Interface<DocWithRank>>
},
space: {
Tx: '' as Ref<Space>,
Model: '' as Ref<Space>

View File

@ -13,6 +13,9 @@
// limitations under the License.
//
import { LexoRank, LexoDecimal, LexoNumeralSystem36 } from 'lexorank'
import LexoRankBucket from 'lexorank/lib/lexoRank/lexoRankBucket'
import type { Doc, Ref, Account } from './classes'
function toHex (value: number, chars: number): string {
@ -59,3 +62,30 @@ export function getCurrentAccount (): Account { return currentAccount }
export function setCurrentAccount (account: Account): void {
currentAccount = account
}
/**
* @public
*/
export const genRanks = (count: number): Generator<string, void, unknown> =>
(function * () {
const sys = new LexoNumeralSystem36()
const base = 36
const max = base ** 6
const gap = LexoDecimal.parse(Math.trunc(max / (count + 2)).toString(base), sys)
let cur = LexoDecimal.parse('0', sys)
for (let i = 0; i < count; i++) {
cur = cur.add(gap)
yield new LexoRank(LexoRankBucket.BUCKET_0, cur).toString()
}
})()
/**
* @public
*/
export const calcRank = (prev?: { rank: string }, next?: { rank: string }): string => {
const a = prev?.rank !== undefined ? LexoRank.parse(prev.rank) : LexoRank.min()
const b = next?.rank !== undefined ? LexoRank.parse(next.rank) : LexoRank.max()
return a.between(b).toString()
}

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import contact, { Contact } from '@anticrm/contact'
import { Data, Ref, Space } from '@anticrm/core'
import { calcRank, Data, Ref, SortingOrder, Space } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
@ -52,6 +52,11 @@
throw new Error('sequence object not found')
}
const lastOne = await client.findOne(
lead.class.Lead,
{ state: state._id },
{ sort: { rank: SortingOrder.Descending } }
)
const incResult = await client.updateDoc(
task.class.Sequence,
task.space.Sequence,
@ -70,7 +75,8 @@
customer: customer!,
attachedTo: customer!,
attachedToClass: contact.class.Contact,
collection: 'leads'
collection: 'leads',
rank: calcRank(lastOne, undefined)
}
await client.createDoc(lead.class.Lead, _space, value, leadId)

View File

@ -15,7 +15,7 @@
//
import type { Contact } from '@anticrm/contact'
import type { Class, Data, Doc, Ref, Space } from '@anticrm/core'
import { Class, Data, Doc, genRanks, Ref, Space } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import task, { DoneState, Kanban, KanbanTemplateSpace, SpaceWithStates, State, Task } from '@anticrm/task'
@ -71,46 +71,55 @@ export async function createKanban (
{ color: '#F28469', name: 'Contract conclusion' },
{ color: '#7C6FCD', name: 'Done' }
]
const ids: Array<Ref<State>> = []
const stateRank = genRanks(states.length)
for (const st of states) {
const sid = (funnelId + '.state.' + st.name.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.name,
color: st.color
color: st.color,
rank
},
sid
)
ids.push(sid)
}
const rawDoneStates = [
const doneStates = [
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
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.class,
funnelId,
{
title: st.title
title: st.title,
rank
},
sid
)
doneStates.push(sid)
}
await factory(
task.class.Kanban,
funnelId,
{
attachedTo: funnelId,
states: ids,
doneStates,
order: []
attachedTo: funnelId
},
(funnelId + '.kanban') as Ref<Kanban>
)

View File

@ -15,7 +15,8 @@
<script lang="ts">
import type { Employee } from '@anticrm/contact'
import contact from '@anticrm/contact'
import type { Ref, Space } from '@anticrm/core'
import { Ref, SortingOrder, Space } from '@anticrm/core'
import { calcRank } from '@anticrm/core'
import { OK, Severity, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
import type { Candidate } from '@anticrm/recruit'
@ -50,6 +51,12 @@
if (sequence === undefined) {
throw new Error('sequence object not found')
}
const lastOne = await client.findOne(
recruit.class.Applicant,
{ state: state._id },
{ sort: { rank: SortingOrder.Descending } }
)
const incResult = await client.updateDoc(
task.class.Sequence,
task.space.Sequence,
@ -59,7 +66,7 @@
},
true
)
const id = await client.addCollection(
await client.addCollection(
recruit.class.Applicant,
_space,
candidate,
@ -69,7 +76,8 @@
state: state._id,
doneState: null,
number: incResult.object.sequence,
assignee: assignee
assignee: assignee,
rank: calcRank(lastOne, undefined)
}
)
}

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import core, { generateId } from '@anticrm/core'
import core, { generateId, genRanks } from '@anticrm/core'
import type { Ref } from '@anticrm/core'
import { AttributeEditor, createQuery, getClient } from '@anticrm/presentation'
import { CircleButton, IconAdd, IconMoreH, Label, showPopup } from '@anticrm/ui'
@ -52,23 +52,22 @@
const space = folder._id
const template = await client.createDoc(task.class.KanbanTemplate, space, {
states: [],
doneStates: [],
doneStatesC: 0,
statesC: 0,
title: 'New Template'
})
const ranks = [...genRanks(2)]
const doneStates = [
{
id: generateId<WonStateTemplate>(),
class: task.class.WonStateTemplate,
title: 'Won'
title: 'Won',
rank: ranks[0]
},
{
id: generateId<LostStateTemplate>(),
class: task.class.LostStateTemplate,
title: 'Lost'
title: 'Lost',
rank: ranks[1]
}
]
@ -80,19 +79,11 @@
task.class.KanbanTemplate,
'doneStatesC',
{
title: ds.title
},
ds.id
title: ds.title,
rank: ds.rank
}
)
}))
for (const ds of doneStates) {
await client.updateDoc(task.class.KanbanTemplate, space, template, {
$push: {
doneStates: ds.id
}
})
}
}
function select (item: KanbanTemplate) {

View File

@ -14,7 +14,7 @@
-->
<script lang="ts">
import contact, { Employee } from '@anticrm/contact'
import type { AttachedData, Data, Doc, Ref, Space } from '@anticrm/core'
import { AttachedData, calcRank, Data, Doc, Ref, Space } from '@anticrm/core'
import { generateId } from '@anticrm/core'
import { OK, Status } from '@anticrm/platform'
import { Card, getClient, UserBox } from '@anticrm/presentation'
@ -63,6 +63,11 @@
throw new Error('sequence object not found')
}
const lastOne = await client.findOne(
task.class.Task,
{ state: state._id },
{ sort: { rank: SortingOrder.Descending } }
)
const incResult = await client.updateDoc(
task.class.Sequence,
task.space.Sequence,
@ -79,7 +84,8 @@
assignee,
number: (incResult as any).object.sequence,
doneState: null,
state: state._id
state: state._id,
rank: calcRank(lastOne, undefined)
}
await client.addCollection(task.class.Issue, _space, parent?._id ?? task.global.Task, parent?._class ?? task.class.Issue, 'tasks', value, taskId)

View File

@ -14,7 +14,7 @@
// limitations under the License.
-->
<script lang="ts">
import type { Ref, Doc } from '@anticrm/core'
import { Ref, Doc, SortingOrder, calcRank } from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import type { Kanban, State, DoneState } from '@anticrm/task'
import task from '@anticrm/task'
@ -33,45 +33,49 @@
const client = getClient()
function sort <T extends Doc> (order: Ref<T>[], items: T[]): T[] {
if (items.length === 0) {
return []
}
const itemMap = new Map(items.map(x => [x._id, x]))
const x = order
.map(id => itemMap.get(id))
.filter((x): x is T => x !== undefined)
return x
}
const statesQ = createQuery()
$: statesQ.query(task.class.State, { _id: { $in: kanban.states ?? [] } }, result => { states = sort(kanban.states, result) })
$: statesQ.query(task.class.State, { space: kanban.space }, result => { states = result}, {
sort: {
rank: SortingOrder.Ascending
}
})
const doneStatesQ = createQuery()
$: doneStatesQ.query(task.class.DoneState, { _id: { $in: kanban.doneStates } }, (result) => { doneStates = sort(kanban.doneStates, result) })
$: doneStatesQ.query(task.class.DoneState, { space: kanban.space }, (result) => { doneStates = result }, {
sort: {
rank: SortingOrder.Ascending
}
})
async function onMove ({ detail: { stateID, position } }: { detail: { stateID: Ref<State>, position: number } }) {
client.updateDoc(kanban._class, kanban.space, kanban._id, {
$move: {
states: {
$value: stateID,
$position: position
}
const [prev, next] = [states[position - 1], states[position + 1]]
const state = states.find((x) => x._id === stateID)
if (state === undefined) {
return
}
await client.updateDoc(
state._class,
state.space,
state._id,
{
rank: calcRank(prev, next)
}
})
)
}
async function onAdd () {
const state = await client.createDoc(task.class.State, kanban.space, {
const lastOne = await client.findOne(
task.class.State,
{ space: kanban.space },
{ sort: { rank: SortingOrder.Descending } }
)
await client.createDoc(task.class.State, kanban.space, {
title: 'New State',
color: '#7C6FCD'
})
await client.updateDoc(kanban._class, kanban.space, kanban._id, {
$push: {
states: state
}
color: '#7C6FCD',
rank: calcRank(lastOne, undefined)
})
}
</script>

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Ref, Space, Doc, generateId } from '@anticrm/core'
import { Ref, Space, SortingOrder, calcRank } from '@anticrm/core'
import core from '@anticrm/core'
import { createQuery, getClient } from '@anticrm/presentation'
import type { State, DoneStateTemplate, KanbanTemplate, StateTemplate } from '@anticrm/task'
@ -35,42 +35,49 @@
const dispatch = createEventDispatcher()
const client = getClient()
function sort <T extends Doc>(order: Ref<T>[], items: T[]): T[] {
if (items.length === 0) {
return []
}
const itemMap = new Map(items.map(x => [x._id, x]))
const x = order
.map(id => itemMap.get(id))
.filter((x): x is T => x !== undefined)
return x
}
const statesQ = createQuery()
$: statesQ.query(task.class.StateTemplate, { attachedTo: kanban._id }, result => { states = sort(kanban.states, result) })
$: statesQ.query(task.class.StateTemplate, { attachedTo: kanban._id }, result => { states = result }, {
sort: {
rank: SortingOrder.Ascending
}
})
const doneStatesQ = createQuery()
$: doneStatesQ.query(task.class.DoneStateTemplate, { attachedTo: kanban._id }, (result) => { doneStates = sort(kanban.doneStates, result) })
$: doneStatesQ.query(task.class.DoneStateTemplate, { attachedTo: kanban._id }, (result) => { doneStates = result }, {
sort: {
rank: SortingOrder.Ascending
}
})
let space: Space | undefined
const spaceQ = createQuery()
$: spaceQ.query(core.class.Space, { _id: kanban.space }, (result) => { space = result[0] })
async function onMove ({ detail: { stateID, position } }: { detail: { stateID: Ref<StateTemplate>, position: number } }) {
client.updateDoc(kanban._class, kanban.space, kanban._id, {
$move: {
states: {
$value: stateID,
$position: position
}
const [prev, next] = [states[position - 1], states[position + 1]]
const state = states.find((x) => x._id === stateID)
if (state === undefined) {
return
}
await client.updateDoc(
state._class,
state.space,
state._id,
{
rank: calcRank(prev, next)
}
})
)
}
async function onAdd () {
const stateID = generateId<StateTemplate>()
const lastOne = await client.findOne(
task.class.StateTemplate,
{ attachedTo: kanban._id },
{ sort: { rank: SortingOrder.Descending } }
)
await client.addCollection(
task.class.StateTemplate,
kanban.space,
@ -79,16 +86,10 @@
'statesC',
{
title: 'New State',
color: '#7C6FCD'
},
stateID
)
await client.updateDoc(kanban._class, kanban.space, kanban._id, {
$push: {
states: stateID
color: '#7C6FCD',
rank: calcRank(lastOne, undefined)
}
})
)
}
function onDelete ({ detail: { state } }: { detail: { state: State }}) {

View File

@ -15,7 +15,7 @@
-->
<script lang="ts">
import type { AttachedDoc, Class, Doc, FindOptions, Ref } from '@anticrm/core'
import { AttachedDoc, calcRank, Class, Doc, DocumentUpdate, DocWithRank, FindOptions, Ref, SortingOrder } from '@anticrm/core'
import core from '@anticrm/core'
import { getResource } from '@anticrm/platform'
import { createQuery, getClient } from '@anticrm/presentation'
@ -26,7 +26,7 @@
import KanbanPanel from './KanbanPanel.svelte'
// import KanbanPanelEmpty from './KanbanPanelEmpty.svelte'
type Item = Doc & { state: Ref<State>, doneState: Ref<DoneState> | null }
type Item = DocWithRank & { state: Ref<State>, doneState: Ref<DoneState> | null }
export let _class: Ref<Class<Item>>
export let space: Ref<SpaceWithStates>
@ -36,50 +36,38 @@
let kanban: Kanban
let states: State[] = []
let rawStates: State[] = []
let objects: (Item | undefined)[] = []
let rawObjects: Item[] = []
let kanbanStates: Ref<State>[] = []
let kanbanDoneStates: Ref<DoneState>[] = []
let objects: Item[] = []
let wonState: WonState | undefined
let lostState: LostState | undefined
const kanbanQuery = createQuery()
$: kanbanQuery.query(task.class.Kanban, { attachedTo: space }, result => { kanban = result[0] })
$: kanbanStates = kanban?.states ?? []
$: kanbanDoneStates = kanban?.doneStates ?? []
function sort (kanban: Kanban, states: State[]): State[] {
if (kanban === undefined || states.length === 0) { return [] }
const map = states.reduce((map, state) => { map.set(state._id, state); return map }, new Map<Ref<State>, State>())
return kanban.states.map(id => map.get(id) as State)
}
function sortObjects<T extends Doc> (kanban: Kanban, objects: T[]): (T | undefined)[] {
if (kanban === undefined || objects.length === 0) { return [] }
const map = objects.reduce((map, doc) => { map.set(doc._id, doc); return map }, new Map<Ref<T>, T>())
return kanban.order
.map(id => map.get(id as Ref<T>))
}
const statesQuery = createQuery()
$: if (kanbanStates.length > 0) statesQuery.query(task.class.State, { _id: { $in: kanbanStates } }, result => { rawStates = result })
$: states = sort(kanban, rawStates)
$: if (kanban !== undefined) {
statesQuery.query(task.class.State, { space: kanban.space }, result => { states = result }, {
sort: {
rank: SortingOrder.Ascending
}
})
}
const doneStatesQ = createQuery()
$: if (kanbanDoneStates.length > 0) {
doneStatesQ.query(task.class.DoneState, { _id: { $in: kanbanDoneStates } }, (result) => {
$: if (kanban !== undefined) {
doneStatesQ.query(task.class.DoneState, { space: kanban.space }, (result) => {
wonState = result.find((x) => x._class === task.class.WonState)
lostState = result.find((x) => x._class === task.class.LostState)
})
}
const query = createQuery()
$: query.query(_class, { space, doneState: null }, result => { rawObjects = result }, options)
$: objects = sortObjects(kanban, rawObjects)
$: query.query(_class, { space, doneState: null }, result => { objects = result }, {
...options,
sort: {
rank: SortingOrder.Ascending
},
})
function dragover (ev: MouseEvent, object: Item) {
if (dragCard !== object) {
@ -89,28 +77,44 @@
}
}
async function updateItem (item: Item, update: DocumentUpdate<Item>) {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
const adoc: AttachedDoc = item as Doc as AttachedDoc
await client.updateCollection(
_class,
space,
adoc._id as Ref<Doc> as Ref<AttachedDoc>,
adoc.attachedTo,
adoc.attachedToClass,
adoc.collection,
update
)
} else {
await client.updateDoc(item._class, item.space, item._id, update)
}
}
async function move (state: Ref<State>) {
const id = dragCard._id
let updates: DocumentUpdate<Item> = {}
if (dragCardInitialState !== state) {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
const adoc: AttachedDoc = dragCard as Doc as AttachedDoc
// We need to update using updateCollection
client.updateCollection(_class, space, id as Ref<Doc> as Ref<AttachedDoc>, adoc.attachedTo, adoc.attachedToClass, adoc.collection, { state })
} else {
client.updateDoc<Task>(_class, space, id as Ref<Task>, { state })
updates = {
...updates,
state
}
}
if (dragCardInitialPosition !== dragCardEndPosition) {
client.updateDoc(task.class.Kanban, space, kanban._id, {
$move: {
order: {
$value: id,
$position: dragCardEndPosition
}
}
})
const [prev, next] = [objects[dragCardEndPosition - 1], objects[dragCardEndPosition + 1]]
updates = {
...updates,
rank: calcRank(prev, next)
}
}
if (Object.keys(updates).length > 0) {
await updateItem(dragCard, updates)
}
}
@ -128,25 +132,9 @@
}
const onDone = (state: DoneState) => async () => {
if (client.getHierarchy().isDerived(_class, core.class.AttachedDoc)) {
const adoc: AttachedDoc = dragCard as Doc as AttachedDoc
await client.updateCollection<Doc, Task>(
_class,
space,
adoc._id as Ref<Task>,
adoc.attachedTo,
adoc.attachedToClass,
adoc.collection,
{ doneState: state._id }
)
} else {
await client.updateDoc(dragCard._class, dragCard.space, dragCard._id, {
doneState: state._id
})
}
isDragging = false
hoveredDoneState = undefined
await updateItem(dragCard, { doneState: state._id })
}
let isDragging = false
@ -174,7 +162,7 @@
}}>
<!-- <KanbanCardEmpty label={'Create new application'} /> -->
{#each objects as object, j (object)}
{#if object !== undefined && object.state === state._id}
{#if object.state === state._id}
<div
class="step-tb75"
on:dragover|preventDefault={(ev) => {
@ -226,11 +214,9 @@
class="flex-grow flex-center done-item"
class:hovered={hoveredDoneState === lostState._id}
on:dragenter={() => {
console.log('enter')
hoveredDoneState = lostState?._id
}}
on:dragleave={() => {
console.log('leave')
hoveredDoneState = undefined
}}
on:dragover|preventDefault={() => {}}

View File

@ -14,8 +14,7 @@
//
import type { Employee } from '@anticrm/contact'
import type { AttachedDoc, Class, Client, Data, Doc, Mixin, Ref, Space, TxOperations } from '@anticrm/core'
import { Arr } from '@anticrm/core'
import { AttachedDoc, Class, Client, Data, Doc, DocWithRank, genRanks, Mixin, Ref, Space, TxOperations } from '@anticrm/core'
import type { Asset, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { AnyComponent } from '@anticrm/ui'
@ -26,7 +25,7 @@ import { ViewletDescriptor } from '@anticrm/view'
/**
* @public
*/
export interface State extends Doc {
export interface State extends DocWithRank {
title: string
color: string
}
@ -34,7 +33,7 @@ export interface State extends Doc {
/**
* @public
*/
export interface DoneState extends Doc {
export interface DoneState extends DocWithRank {
title: string
}
@ -51,7 +50,7 @@ export interface LostState extends DoneState {}
/**
* @public
*/
export interface Task extends AttachedDoc {
export interface Task extends AttachedDoc, DocWithRank {
state: Ref<State>
doneState: Ref<DoneState> | null
number: number
@ -95,9 +94,6 @@ export interface KanbanCard extends Class<Doc> {
*/
export interface Kanban extends Doc {
attachedTo: Ref<Space>
states: Arr<Ref<State>>
doneStates: Arr<Ref<DoneState>>
order: Arr<Ref<Doc>>
}
/**
@ -133,8 +129,6 @@ export interface LostStateTemplate extends DoneStateTemplate, LostState {}
*/
export interface KanbanTemplate extends Doc {
title: string
states: Arr<Ref<StateTemplate>>
doneStates: Arr<Ref<DoneStateTemplate>>
statesC: number
doneStatesC: number
}
@ -209,47 +203,56 @@ export async function createProjectKanban (
{ color: '#A5D179', name: 'Done' },
{ color: '#F28469', name: 'Invalid' }
]
const ids: Array<Ref<State>> = []
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<State>
await factory(
task.class.State,
projectId,
{
title: st.name,
color: st.color
color: st.color,
rank
},
sid
)
ids.push(sid)
}
const rawDoneStates = [
const doneStates = [
{ class: task.class.WonState, title: 'Won' },
{ class: task.class.LostState, title: 'Lost' }
]
const doneStates: Array<Ref<DoneState>> = []
for (const st of rawDoneStates) {
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.class,
projectId,
{
title: st.title
title: st.title,
rank
},
sid
)
doneStates.push(sid)
}
await factory(
task.class.Kanban,
projectId,
{
attachedTo: projectId,
states: ids,
doneStates,
order: []
attachedTo: projectId
},
(projectId + '.kanban') as Ref<Kanban>
)
@ -260,18 +263,19 @@ export async function createProjectKanban (
*/
export async function createKanban (client: Client & TxOperations, attachedTo: Ref<Space>, templateId?: Ref<KanbanTemplate>): Promise<Ref<Kanban>> {
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,
states: [],
doneStates: await Promise.all([
client.createDoc(task.class.WonState, attachedTo, {
title: 'Won'
}),
client.createDoc(task.class.LostState, attachedTo, {
title: 'Lost'
})
]),
order: []
attachedTo
})
}
@ -282,37 +286,34 @@ export async function createKanban (client: Client & TxOperations, attachedTo: R
}
const tmplStates = await client.findAll(task.class.StateTemplate, { attachedTo: template._id })
const states = await Promise.all(
template.states
.map((id) => tmplStates.find((x) => x._id === id))
.filter((tstate): tstate is StateTemplate => tstate !== undefined)
.map(async (state) => await client.createDoc(task.class.State, attachedTo, { color: state.color, title: state.title }))
)
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<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 })
const doneStates = (await Promise.all(
template.doneStates
.map((id) => tmplDoneStates.find((x) => x._id === id))
.filter((tstate): tstate is DoneStateTemplate => tstate !== undefined)
.map(async (state) => {
const cl = doneClassMap.get(state._class)
await Promise.all(
tmplDoneStates.map(async (state) => {
const cl = doneClassMap.get(state._class)
if (cl === undefined) {
return
}
if (cl === undefined) {
return
}
return await client.createDoc(cl, attachedTo, { title: state.title })
})
)).filter((x): x is Ref<DoneState> => x !== undefined)
return await client.createDoc(cl, attachedTo, { title: state.title, rank: state.rank })
})
)
return await client.createDoc(task.class.Kanban, attachedTo, {
attachedTo,
states,
doneStates,
order: []
attachedTo
})
}

View File

@ -736,21 +736,6 @@
"projectFolder": "server/recruit-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-task-resources",
"projectFolder": "server/task-resources",
"shouldPublish": true
},
{
"packageName": "@anticrm/server-task",
"projectFolder": "server/task",
"shouldPublish": true
},
{
"packageName": "@anticrm/model-server-task",
"projectFolder": "models/server-task",
"shouldPublish": true
},
{
"packageName": "@anticrm/mongo",
"projectFolder": "server/mongo",

View File

@ -43,8 +43,6 @@
"@anticrm/server-recruit": "~0.6.0",
"@anticrm/server-recruit-resources": "~0.6.0",
"@anticrm/mongo": "~0.6.1",
"@anticrm/elastic": "~0.6.0",
"@anticrm/server-task": "~0.6.0",
"@anticrm/server-task-resources": "~0.6.0"
"@anticrm/elastic": "~0.6.0"
}
}

View File

@ -24,7 +24,6 @@ import type { DbConfiguration, DbAdapter } from '@anticrm/server-core'
import { addLocation } from '@anticrm/platform'
import { serverChunterId } from '@anticrm/server-chunter'
import { serverRecruitId } from '@anticrm/server-recruit'
import { serverViewId } from '@anticrm/server-task'
class NullDbAdapter implements DbAdapter {
async init (model: Tx[]): Promise<void> {}
@ -42,7 +41,6 @@ async function createNullAdapter (hierarchy: Hierarchy, url: string, db: string,
export function start (dbUrl: string, fullTextUrl: string, port: number, host?: string): () => void {
addLocation(serverChunterId, () => import('@anticrm/server-chunter-resources'))
addLocation(serverRecruitId, () => import('@anticrm/server-recruit-resources'))
addLocation(serverViewId, () => import('@anticrm/server-task-resources'))
return startJsonRpc((workspace: string) => {
const conf: DbConfiguration = {

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,18 +0,0 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -1,34 +0,0 @@
{
"name": "@anticrm/server-task-resources",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.12",
"@anticrm/platform": "~0.6.5",
"@anticrm/server-core": "~0.6.1",
"@anticrm/task": "~0.6.0"
}
}

View File

@ -1,67 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021 Hardcore Engineering Inc.
//
// 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 { Tx, TxFactory, Doc, TxCreateDoc, TxRemoveDoc, TxCollectionCUD, AttachedDoc } from '@anticrm/core'
import type { FindAll } from '@anticrm/server-core'
import core, { Hierarchy } from '@anticrm/core'
import task, { Kanban, Task, State } from '@anticrm/task'
/**
* @public
*/
export async function OnTask (tx: Tx, txFactory: TxFactory, findAll: FindAll<Doc>, hierarchy: Hierarchy): Promise<Tx[]> {
if (tx._class === core.class.TxCollectionCUD) {
tx = (tx as TxCollectionCUD<Doc, AttachedDoc>).tx
}
if (hierarchy.isDerived(tx._class, core.class.TxCreateDoc)) {
const createTx = tx as TxCreateDoc<Task>
if (hierarchy.isDerived(createTx.objectClass, task.class.Task)) {
const state = (await (findAll as FindAll<State>)(task.class.State, { space: createTx.objectSpace }))[0] // TODO: make FindAll generic
if (state === undefined) {
throw new Error('OnTask: state not found')
}
const kanban = (await (findAll as FindAll<Kanban>)(task.class.Kanban, { attachedTo: createTx.objectSpace }))[0]
if (kanban === undefined) {
throw new Error('OnTask: kanban not found')
}
return [
txFactory.createTxUpdateDoc(createTx.objectClass, createTx.objectSpace, createTx.objectId, { state: state._id }),
txFactory.createTxUpdateDoc(task.class.Kanban, createTx.objectSpace, kanban._id, { $push: { order: createTx.objectId } })
]
}
} else if (tx._class === core.class.TxRemoveDoc) {
const removeTx = tx as TxRemoveDoc<Task>
if (hierarchy.isDerived(removeTx.objectClass, task.class.Task)) {
const kanban = (await (findAll as FindAll<Kanban>)(task.class.Kanban, { attachedTo: removeTx.objectSpace }))[0]
if (kanban === undefined) {
throw new Error('OnTask: kanban not found')
}
return [
txFactory.createTxUpdateDoc(task.class.Kanban, removeTx.objectSpace, kanban._id, { $pull: { order: removeTx.objectId } })
]
}
}
return []
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export default async () => ({
trigger: {
OnTask
}
})

View File

@ -1,8 +0,0 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib"
}
}

View File

@ -1,7 +0,0 @@
module.exports = {
extends: ['./node_modules/@anticrm/platform-rig/profiles/default/config/eslint.config.json'],
parserOptions: {
tsconfigRootDir: __dirname,
project: './tsconfig.json'
}
}

View File

@ -1,4 +0,0 @@
*
!/lib/**
!CHANGELOG.md
/lib/**/__tests__/

View File

@ -1,18 +0,0 @@
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
{
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
/**
* (Required) The name of the rig package to inherit from.
* It should be an NPM package name with the "-rig" suffix.
*/
"rigPackageName": "@anticrm/platform-rig"
/**
* (Optional) Selects a config profile from the rig package. The name must consist of
* lowercase alphanumeric words separated by hyphens, for example "sample-profile".
* If omitted, then the "default" profile will be used."
*/
// "rigProfile": "your-profile-name"
}

View File

@ -1,34 +0,0 @@
{
"name": "@anticrm/server-task",
"version": "0.6.0",
"main": "lib/index.js",
"author": "Anticrm Platform Contributors",
"license": "EPL-2.0",
"scripts": {
"build": "heft build",
"build:watch": "tsc",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
"format": "prettier --write src && eslint --fix src"
},
"devDependencies": {
"@anticrm/platform-rig": "~0.6.0",
"@types/heft-jest": "^1.0.2",
"@types/node": "^16.4.10",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-promise": "^5.1.1",
"eslint-plugin-node": "^11.1.0",
"eslint": "^7.32.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint-config-standard-with-typescript": "^21.0.1",
"prettier": "^2.4.1",
"@rushstack/heft": "^0.41.1",
"typescript": "^4.3.5"
},
"dependencies": {
"@anticrm/core": "~0.6.11",
"@anticrm/platform": "~0.6.5",
"@anticrm/server-core": "~0.6.0"
}
}

View File

@ -1,33 +0,0 @@
//
// Copyright © 2020, 2021 Anticrm Platform Contributors.
// Copyright © 2021, 2022 Hardcore Engineering Inc.
//
// 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 { Resource, Plugin } from '@anticrm/platform'
import { plugin } from '@anticrm/platform'
import type { TriggerFunc } from '@anticrm/server-core'
/**
* @public
*/
export const serverViewId = 'server-task' as Plugin
/**
* @public
*/
export default plugin(serverViewId, {
trigger: {
OnTask: '' as Resource<TriggerFunc>
}
})

View File

@ -1,9 +0,0 @@
{
"extends": "./node_modules/@anticrm/platform-rig/profiles/default/tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./lib",
"esModuleInterop": true
}
}