mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-16 21:35:10 +00:00
UBER-734 Rework makeRank and migrate ranks (#7104)
Signed-off-by: Alexander Onnikov <Alexander.Onnikov@xored.com>
This commit is contained in:
parent
585b3f40e7
commit
e3353df572
@ -21,7 +21,8 @@ import {
|
||||
type Status,
|
||||
type StatusCategory,
|
||||
type Doc,
|
||||
type Class
|
||||
type Class,
|
||||
type Rank
|
||||
} from '@hcengineering/core'
|
||||
import { Model, Prop, TypeRef, TypeString, UX } from '@hcengineering/model'
|
||||
import { type Asset, type IntlString } from '@hcengineering/platform'
|
||||
@ -54,7 +55,7 @@ export class TStatus extends TDoc implements Status {
|
||||
@Prop(TypeString(), core.string.Description)
|
||||
description!: string
|
||||
|
||||
rank!: string
|
||||
rank!: Rank
|
||||
}
|
||||
|
||||
@Model(core.class.StatusCategory, core.class.Doc, DOMAIN_MODEL)
|
||||
|
@ -17,6 +17,7 @@ import { type CollaborativeDoc, DOMAIN_TX, MeasureMetricsContext, SortingOrder }
|
||||
import { type DocumentSnapshot, type Document, type Teamspace } from '@hcengineering/document'
|
||||
import {
|
||||
tryMigrate,
|
||||
migrateSpaceRanks,
|
||||
type MigrateOperation,
|
||||
type MigrateUpdate,
|
||||
type MigrationClient,
|
||||
@ -270,6 +271,16 @@ async function restoreContentField (client: MigrationClient): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateRanks (client: MigrationClient): Promise<void> {
|
||||
const classes = client.hierarchy.getDescendants(document.class.Teamspace)
|
||||
for (const _class of classes) {
|
||||
const spaces = await client.find<Teamspace>(DOMAIN_SPACE, { _class })
|
||||
for (const space of spaces) {
|
||||
await migrateSpaceRanks(client, DOMAIN_DOCUMENT, space)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const documentOperation: MigrateOperation = {
|
||||
async migrate (client: MigrationClient): Promise<void> {
|
||||
await tryMigrate(client, documentId, [
|
||||
@ -306,6 +317,10 @@ export const documentOperation: MigrateOperation = {
|
||||
{
|
||||
state: 'restoreContentField',
|
||||
func: restoreContentField
|
||||
},
|
||||
{
|
||||
state: 'migrateRanks',
|
||||
func: migrateRanks
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
import {
|
||||
createOrUpdate,
|
||||
migrateSpace,
|
||||
migrateSpaceRanks,
|
||||
tryMigrate,
|
||||
tryUpgrade,
|
||||
type MigrateOperation,
|
||||
@ -44,6 +45,7 @@ import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
|
||||
import tags from '@hcengineering/model-tags'
|
||||
import {
|
||||
taskId,
|
||||
type Project,
|
||||
type ProjectStatus,
|
||||
type ProjectType,
|
||||
type ProjectTypeDescriptor,
|
||||
@ -548,6 +550,16 @@ export async function migrateDefaultStatusesBase<T extends Task> (
|
||||
logger.log('Statuses updated: ', statusIdsBeingMigrated.length)
|
||||
}
|
||||
|
||||
async function migrateRanks (client: MigrationClient): Promise<void> {
|
||||
const classes = client.hierarchy.getDescendants(task.class.Project)
|
||||
for (const _class of classes) {
|
||||
const spaces = await client.find<Project>(DOMAIN_SPACE, { _class })
|
||||
for (const space of spaces) {
|
||||
await migrateSpaceRanks(client, DOMAIN_TASK, space)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function areSameArrays (arr1: any[] | undefined, arr2: any[] | undefined): boolean {
|
||||
if (arr1 === arr2) {
|
||||
return true
|
||||
@ -590,6 +602,10 @@ export const taskOperation: MigrateOperation = {
|
||||
func: async (client: MigrationClient): Promise<void> => {
|
||||
await client.update(DOMAIN_TASK, { '%hash%': { $exists: true } }, { $set: { '%hash%': null } })
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'migrateRanks',
|
||||
func: migrateRanks
|
||||
}
|
||||
])
|
||||
},
|
||||
|
@ -42,6 +42,7 @@
|
||||
"@hcengineering/platform": "^0.6.11",
|
||||
"@hcengineering/storage": "^0.6.0",
|
||||
"@hcengineering/analytics": "^0.6.0",
|
||||
"@hcengineering/rank": "^0.6.4",
|
||||
"toposort": "^2.0.2",
|
||||
"fast-equals": "^5.0.1"
|
||||
},
|
||||
|
@ -15,13 +15,16 @@ import core, {
|
||||
ModelDb,
|
||||
ObjQueryType,
|
||||
PushOptions,
|
||||
Rank,
|
||||
Ref,
|
||||
SortingOrder,
|
||||
Space,
|
||||
TxOperations,
|
||||
UnsetOptions,
|
||||
WorkspaceId,
|
||||
generateId
|
||||
} from '@hcengineering/core'
|
||||
import { makeRank } from '@hcengineering/rank'
|
||||
import { StorageAdapter } from '@hcengineering/storage'
|
||||
import { ModelLogger } from './utils'
|
||||
|
||||
@ -252,3 +255,34 @@ export async function migrateSpace (
|
||||
}
|
||||
await client.update(DOMAIN_TX, { objectSpace: from }, { objectSpace: to })
|
||||
}
|
||||
|
||||
export async function migrateSpaceRanks (client: MigrationClient, domain: Domain, space: Space): Promise<void> {
|
||||
type WithRank = Doc & { rank: Rank }
|
||||
|
||||
const iterator = await client.traverse<WithRank>(
|
||||
domain,
|
||||
{ space: space._id, rank: { $exists: true } },
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
|
||||
try {
|
||||
let rank = '0|100000:'
|
||||
|
||||
while (true) {
|
||||
const docs = await iterator.next(1000)
|
||||
if (docs === null || docs.length === 0) {
|
||||
break
|
||||
}
|
||||
|
||||
const updates: { filter: MigrationDocumentQuery<Doc<Space>>, update: MigrateUpdate<Doc<Space>> }[] = []
|
||||
for (const doc of docs) {
|
||||
rank = makeRank(rank, undefined)
|
||||
updates.push({ filter: { _id: doc._id }, update: { rank } })
|
||||
}
|
||||
|
||||
await client.bulk(domain, updates)
|
||||
}
|
||||
} finally {
|
||||
await iterator.close()
|
||||
}
|
||||
}
|
||||
|
91
packages/rank/src/__tests__/utils.test.ts
Normal file
91
packages/rank/src/__tests__/utils.test.ts
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// Copyright © 2024 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 { makeRank } from '..'
|
||||
|
||||
describe('makeRank', () => {
|
||||
it('calculates rank when no prev and next', () => {
|
||||
expect(makeRank(undefined, undefined)).toBe('0|hzzzzz:')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['0|hzzzzz:', '0|i00007:'],
|
||||
['0|i00007:', '0|i0000f:'],
|
||||
['0|i0000f:', '0|i0000n:'],
|
||||
['0|zzzzzz:', '0|zzzzzz:']
|
||||
])('calculates rank value when prev is %p', (prev, expected) => {
|
||||
expect(makeRank(prev, undefined)).toBe(expected)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['0|hzzzzz:', '0|hzzzzr:'],
|
||||
['0|hzzzzr:', '0|hzzzzj:'],
|
||||
['0|hzzzzj:', '0|hzzzzb:'],
|
||||
['0|000000:', '0|000000:']
|
||||
])('calculates rank value when next is %p', (next, expected) => {
|
||||
expect(makeRank(undefined, next)).toBe(expected)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['0|000000:', '0|000001:', '0|000000:i'],
|
||||
['0|hzzzzz:', '0|i0000f:', '0|i00007:'],
|
||||
['0|hzzzzz:', '0|hzzzzz:', '0|i00007:'],
|
||||
['0|i00007:', '0|i00007:', '0|i0000f:'],
|
||||
['0|i00007:', '0|i00008:', '0|i00007:i']
|
||||
])('calculates rank value when prev is %p and next is %p', (prev, next, expected) => {
|
||||
expect(makeRank(prev, next)).toBe(expected)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[10, '0|hzzzxr:'],
|
||||
[100, '0|hzzzdr:'],
|
||||
[1000, '0|hzzttr:'],
|
||||
[10000, '0|hzya9r:']
|
||||
])('produces prev rank of reasonable length for %p generations', (count, expected) => {
|
||||
let rank = '0|hzzzzz:'
|
||||
for (let i = 0; i < count; i++) {
|
||||
rank = makeRank(undefined, rank)
|
||||
}
|
||||
expect(rank).toBe(expected)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[5, '0|zfqzzz:'],
|
||||
[10, '0|zzd7vh:'],
|
||||
[50, '0|zzzzzy:zzzi'],
|
||||
[100, '0|zzzzzy:zzzzzzzzzzzv']
|
||||
])('produces middle rank of reasonable length for %p generations', (count, expected) => {
|
||||
let rank = '0|hzzzzz:'
|
||||
for (let i = 0; i < count; i++) {
|
||||
rank = makeRank(rank, '0|zzzzzz')
|
||||
}
|
||||
expect(rank).toBe(expected)
|
||||
})
|
||||
|
||||
it.each([
|
||||
[10, '0|i00027:'],
|
||||
[100, '0|i000m7:'],
|
||||
[1000, '0|i00667:'],
|
||||
[10000, '0|i01pq7:'],
|
||||
[100000, '0|i0h5a7:'],
|
||||
[1000000, '0|i4rgu7:']
|
||||
])('produces next rank of reasonable length for %p generations', (count, expected) => {
|
||||
let rank = '0|hzzzzz:'
|
||||
for (let i = 0; i < count; i++) {
|
||||
rank = makeRank(rank, undefined)
|
||||
}
|
||||
expect(rank).toBe(expected)
|
||||
})
|
||||
})
|
@ -34,9 +34,19 @@ export function genRanks (count: number): Rank[] {
|
||||
|
||||
/** @public */
|
||||
export function makeRank (prev: Rank | undefined, next: Rank | undefined): Rank {
|
||||
const prevLexoRank = prev === undefined ? LexoRank.min() : LexoRank.parse(prev)
|
||||
const nextLexoRank = next === undefined ? LexoRank.max() : LexoRank.parse(next)
|
||||
return prevLexoRank.equals(nextLexoRank)
|
||||
? prevLexoRank.genNext().toString()
|
||||
: prevLexoRank.between(nextLexoRank).toString()
|
||||
if (prev !== undefined && next !== undefined) {
|
||||
const prevLexoRank = LexoRank.parse(prev)
|
||||
const nextLexoRank = LexoRank.parse(next)
|
||||
return prevLexoRank.equals(nextLexoRank)
|
||||
? prevLexoRank.genNext().toString()
|
||||
: prevLexoRank.between(nextLexoRank).toString()
|
||||
} else if (prev !== undefined) {
|
||||
const prevLexoRank = LexoRank.parse(prev)
|
||||
return prevLexoRank.genNext().toString()
|
||||
} else if (next !== undefined) {
|
||||
const nextLexoRank = LexoRank.parse(next)
|
||||
return nextLexoRank.genPrev().toString()
|
||||
} else {
|
||||
return LexoRank.middle().toString()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user