From b0ffc0ed46550593e87b4a58fe7b93da0ffd455e Mon Sep 17 00:00:00 2001 From: Ilya Sumbatyants Date: Fri, 17 Dec 2021 16:08:37 +0700 Subject: [PATCH] Ranks (#653) Signed-off-by: Ilya Sumbatyants --- common/config/rush/pnpm-lock.yaml | 85 ++-------- dev/generator/src/kanban.ts | 35 ++-- dev/generator/src/recruit.ts | 14 +- dev/prod/package.json | 2 - dev/prod/src/platform-dev.ts | 2 - models/all/package.json | 1 - models/all/src/index.ts | 2 - models/core/src/security.ts | 7 +- models/server-task/.eslintrc.js | 7 - models/server-task/.npmignore | 4 - models/server-task/config/rig.json | 18 -- models/server-task/package.json | 34 ---- models/server-task/src/index.ts | 27 --- models/server-task/tsconfig.json | 8 - models/task/src/index.ts | 24 ++- models/task/src/migration.ts | 154 ++++++++++++++++-- packages/core/package.json | 3 +- packages/core/src/classes.ts | 7 + packages/core/src/component.ts | 5 +- packages/core/src/utils.ts | 30 ++++ .../src/components/CreateLead.svelte | 10 +- plugins/lead/src/index.ts | 35 ++-- .../src/components/CreateApplication.svelte | 14 +- .../src/components/statuses/Templates.svelte | 27 +-- .../src/components/CreateTask.svelte | 10 +- .../src/components/kanban/KanbanEditor.svelte | 64 ++++---- .../kanban/KanbanTemplateEditor.svelte | 67 ++++---- .../src/components/kanban/KanbanView.svelte | 116 ++++++------- plugins/task/src/index.ts | 111 ++++++------- rush.json | 15 -- server/server/package.json | 4 +- server/server/src/server.ts | 2 - server/task-resources/.eslintrc.js | 7 - server/task-resources/.npmignore | 4 - server/task-resources/config/rig.json | 18 -- server/task-resources/package.json | 34 ---- server/task-resources/src/index.ts | 67 -------- server/task-resources/tsconfig.json | 8 - server/task/.eslintrc.js | 7 - server/task/.npmignore | 4 - server/task/config/rig.json | 18 -- server/task/package.json | 34 ---- server/task/src/index.ts | 33 ---- server/task/tsconfig.json | 9 - 44 files changed, 482 insertions(+), 705 deletions(-) delete mode 100644 models/server-task/.eslintrc.js delete mode 100644 models/server-task/.npmignore delete mode 100644 models/server-task/config/rig.json delete mode 100644 models/server-task/package.json delete mode 100644 models/server-task/src/index.ts delete mode 100644 models/server-task/tsconfig.json delete mode 100644 server/task-resources/.eslintrc.js delete mode 100644 server/task-resources/.npmignore delete mode 100644 server/task-resources/config/rig.json delete mode 100644 server/task-resources/package.json delete mode 100644 server/task-resources/src/index.ts delete mode 100644 server/task-resources/tsconfig.json delete mode 100644 server/task/.eslintrc.js delete mode 100644 server/task/.npmignore delete mode 100644 server/task/config/rig.json delete mode 100644 server/task/package.json delete mode 100644 server/task/src/index.ts delete mode 100644 server/task/tsconfig.json diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index bcd0e30ae4..e3a2589a9b 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -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: diff --git a/dev/generator/src/kanban.ts b/dev/generator/src/kanban.ts index f75e237296..b39f3b1ffa 100644 --- a/dev/generator/src/kanban.ts +++ b/dev/generator/src/kanban.ts @@ -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, cl { color: '#F28469', name: 'Invalid' } ] const states: Array> = [] + 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 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> = [] - 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 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, { - attachedTo: spaceId, - states, - doneStates, - order: [] + attachedTo: spaceId } ) return states diff --git a/dev/generator/src/recruit.ts b/dev/generator/src/recruit.ts index 1c978b9a59..675a2ad4fe 100644 --- a/dev/generator/src/recruit.ts +++ b/dev/generator/src/recruit.ts @@ -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 - 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 const applicant: AttachedData = { number: faker.datatype.number(), assignee: faker.random.arrayElement(emoloyeeIds), state: faker.random.arrayElement(states), - doneState: null + doneState: null, + rank } // Update or create candidate diff --git a/dev/prod/package.json b/dev/prod/package.json index fb0b94066d..ee9379e33d 100644 --- a/dev/prod/package.json +++ b/dev/prod/package.json @@ -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", diff --git a/dev/prod/src/platform-dev.ts b/dev/prod/src/platform-dev.ts index 97db2db1d5..bb5e1671ce 100644 --- a/dev/prod/src/platform-dev.ts +++ b/dev/prod/src/platform-dev.ts @@ -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() diff --git a/models/all/package.json b/models/all/package.json index ecbcc3d01a..ed1bb5a3f7 100644 --- a/models/all/package.json +++ b/models/all/package.json @@ -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" diff --git a/models/all/src/index.ts b/models/all/src/index.ts index 77c3cebf1e..ebb6efa156 100644 --- a/models/all/src/index.ts +++ b/models/all/src/index.ts @@ -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) diff --git a/models/core/src/security.ts b/models/core/src/security.ts index 60519dbcfe..70871bcac1 100644 --- a/models/core/src/security.ts +++ b/models/core/src/security.ts @@ -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 +} diff --git a/models/server-task/.eslintrc.js b/models/server-task/.eslintrc.js deleted file mode 100644 index c3c2c49417..0000000000 --- a/models/server-task/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - extends: ['./node_modules/@anticrm/model-rig/profiles/default/config/eslint.config.json'], - parserOptions: { - tsconfigRootDir: __dirname, - project: './tsconfig.json' - } -} diff --git a/models/server-task/.npmignore b/models/server-task/.npmignore deleted file mode 100644 index e3ec093c38..0000000000 --- a/models/server-task/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -* -!/lib/** -!CHANGELOG.md -/lib/**/__tests__/ diff --git a/models/server-task/config/rig.json b/models/server-task/config/rig.json deleted file mode 100644 index e9a9ee9add..0000000000 --- a/models/server-task/config/rig.json +++ /dev/null @@ -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" -} diff --git a/models/server-task/package.json b/models/server-task/package.json deleted file mode 100644 index 4c8f3f30f0..0000000000 --- a/models/server-task/package.json +++ /dev/null @@ -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" - } -} diff --git a/models/server-task/src/index.ts b/models/server-task/src/index.ts deleted file mode 100644 index 60b8ed850b..0000000000 --- a/models/server-task/src/index.ts +++ /dev/null @@ -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 - }) -} diff --git a/models/server-task/tsconfig.json b/models/server-task/tsconfig.json deleted file mode 100644 index 1d60db76b4..0000000000 --- a/models/server-task/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "./node_modules/@anticrm/model-rig/profiles/default/tsconfig.json", - - "compilerOptions": { - "rootDir": "./src", - "outDir": "./lib", - } -} \ No newline at end of file diff --git a/models/task/src/index.ts b/models/task/src/index.ts index e7a06f5791..3095618c41 100644 --- a/models/task/src/index.ts +++ b/models/task/src/index.ts @@ -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 @@ -91,6 +95,8 @@ export class TTask extends TAttachedDoc implements Task { // @Prop(TypeRef(contact.class.Employee), 'Assignee' as IntlString) assignee!: Ref | null + + declare rank: string } @Model(task.class.SpaceWithStates, core.class.Space) @@ -136,7 +142,6 @@ export class TKanban extends TDoc implements Kanban { states!: Arr> doneStates!: Arr> attachedTo!: Ref - order!: Arr> } @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> - doneStates!: Arr> - @Prop(Collection(task.class.StateTemplate), 'States' as IntlString) statesC!: number diff --git a/models/task/src/migration.ts b/models/task/src/migration.ts index 28a8551860..3d5d0122ac 100644 --- a/models/task/src/migration.ts +++ b/models/task/src/migration.ts @@ -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 { + 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 ({ + client, + ops, + _class, + extractOrder +}: { + client: Client + ops: TxOperations + _class: Ref> + extractOrder: (kanban: any) => Ref[] +}): Promise { + const allItems = await client.findAll(_class, {}) + const unorderedItems = allItems + .filter((item) => item.rank === undefined) + const groupedUnsortedItems = new Map, 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>, item.space, item._id, { rank }) + } + } +} + +async function updateTemplateRankItems ({ + client, + ops, + _class, + extractOrder +}: { + client: Client + ops: TxOperations + _class: Ref> + extractOrder: (kanban: any) => Ref[] +}): Promise { + const allItems = await client.findAll(_class, {}) + const unorderedItems = allItems + .filter((state) => state.rank === undefined) + const groupedUnsortedItems = new Map, 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 }) + + 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>, item.space, item._id, { rank }) + } } } diff --git a/packages/core/package.json b/packages/core/package.json index b01ec98392..013f9edc38 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -29,6 +29,7 @@ }, "dependencies": { "@anticrm/platform": "~0.6.5", - "just-clone": "^3.2.1" + "just-clone": "^3.2.1", + "lexorank": "~1.0.4" } } diff --git a/packages/core/src/classes.ts b/packages/core/src/classes.ts index efb9566a74..14b9b85c92 100644 --- a/packages/core/src/classes.ts +++ b/packages/core/src/classes.ts @@ -218,3 +218,10 @@ export interface Space extends Doc { export interface Account extends Doc { email: string } + +/** + * @public + */ +export interface DocWithRank extends Doc { + rank: string +} diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 1dc695287b..1f89e53f82 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -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>>, Bag: '' as Ref>>> }, + interface: { + DocWithRank: '' as Ref> + }, space: { Tx: '' as Ref, Model: '' as Ref diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index eb6b3825eb..a4728b379b 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -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 => + (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() +} diff --git a/plugins/lead-resources/src/components/CreateLead.svelte b/plugins/lead-resources/src/components/CreateLead.svelte index b6b49edc06..3ea90a1676 100644 --- a/plugins/lead-resources/src/components/CreateLead.svelte +++ b/plugins/lead-resources/src/components/CreateLead.svelte @@ -15,7 +15,7 @@ --> diff --git a/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte b/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte index 347eb49af0..1d3633735c 100644 --- a/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte +++ b/plugins/task-resources/src/components/kanban/KanbanTemplateEditor.svelte @@ -15,7 +15,7 @@ -->