TSK-838: Created by (#2742)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2023-03-16 10:15:35 +07:00 committed by GitHub
parent 787e01378e
commit 0c1b13f5e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 199 additions and 206 deletions

View File

@ -29,7 +29,7 @@ import {
TxUpdateDoc
} from '@hcengineering/core'
import { createOrUpdate, MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import core from '@hcengineering/model-core'
import { DOMAIN_TAGS } from '@hcengineering/model-tags'
import { createKanbanTemplate, createSequence, DOMAIN_TASK } from '@hcengineering/model-task'
import tags, { TagElement, TagReference } from '@hcengineering/tags'
@ -191,43 +191,9 @@ async function migrateLabels (client: MigrationClient): Promise<void> {
}
}
}
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const objects = await client.find<Board>(DOMAIN_SPACE, {
_class: board.class.Board,
createdBy: { $exists: false }
})
const txes = await client.find<TxCreateDoc<Board>>(DOMAIN_TX, {
objectClass: board.class.Board,
_class: core.class.TxCreateDoc
})
const txMap = new Map(txes.map((p) => [p.objectId, p]))
for (const object of objects) {
const createTx = txMap.get(object._id)
if (createTx !== undefined && createTx.attributes.createdBy === undefined) {
await client.update(
DOMAIN_TX,
{ _id: createTx._id },
{
'attributes.createdBy': createTx.modifiedBy
}
)
}
await client.update(
DOMAIN_SPACE,
{ _id: object._id },
{
createdBy: createTx?.modifiedBy ?? object.modifiedBy
}
)
}
}
export const boardOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await Promise.all([migrateLabels(client)])
await fillCreatedBy(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const ops = new TxOperations(client, core.account.System)

View File

@ -13,10 +13,9 @@
// limitations under the License.
//
import { ChunterSpace, Comment, Message, ThreadMessage } from '@hcengineering/chunter'
import { Comment, Message, ThreadMessage } from '@hcengineering/chunter'
import core, { Doc, DOMAIN_TX, Ref, TxCreateDoc, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import { DOMAIN_CHUNTER, DOMAIN_COMMENT } from './index'
import chunter from './plugin'
@ -179,43 +178,10 @@ export async function migrateThreadMessages (client: MigrationClient): Promise<v
}
}
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const objects = await client.find<ChunterSpace>(DOMAIN_SPACE, {
_class: { $in: [chunter.class.DirectMessage, chunter.class.Channel] },
createdBy: { $exists: false }
})
const txes = await client.find<TxCreateDoc<ChunterSpace>>(DOMAIN_TX, {
objectClass: { $in: [chunter.class.DirectMessage, chunter.class.Channel] },
_class: core.class.TxCreateDoc
})
const txMap = new Map(txes.map((p) => [p.objectId, p]))
for (const object of objects) {
const createTx = txMap.get(object._id)
if (createTx !== undefined && createTx.attributes.createdBy === undefined) {
await client.update(
DOMAIN_TX,
{ _id: createTx._id },
{
'attributes.createdBy': createTx.modifiedBy
}
)
}
await client.update(
DOMAIN_SPACE,
{ _id: object._id },
{
createdBy: createTx?.modifiedBy ?? object.modifiedBy
}
)
}
}
export const chunterOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await migrateMessages(client)
await migrateThreadMessages(client)
await fillCreatedBy(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)

View File

@ -19,7 +19,6 @@ import { IntlString, mergeIds } from '@hcengineering/platform'
export default mergeIds(coreId, core, {
string: {
Archived: '' as IntlString,
CreatedBy: '' as IntlString,
ClassLabel: '' as IntlString,
ClassPropertyLabel: '' as IntlString,
Members: '' as IntlString

View File

@ -94,6 +94,9 @@ export class TDoc extends TObj implements Doc {
@Prop(TypeRef(core.class.Account), core.string.ModifiedBy)
modifiedBy!: Ref<Account>
@Prop(TypeRef(core.class.Account), core.string.CreatedBy)
createdBy!: Ref<Account>
}
@Model(core.class.AttachedDoc, core.class.Doc)

View File

@ -14,8 +14,80 @@
//
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import core, {
Doc,
DOMAIN_BLOB,
DOMAIN_DOC_INDEX_STATE,
DOMAIN_MODEL,
DOMAIN_TX,
TxCreateDoc,
TxCollectionCUD,
AttachedDoc
} from '@hcengineering/core'
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const h = client.hierarchy
const domains = h.domains()
for (const domain of domains) {
if (
domain === DOMAIN_TX ||
domain === DOMAIN_MODEL ||
domain === DOMAIN_BLOB ||
domain === DOMAIN_DOC_INDEX_STATE
) {
continue
}
try {
const objects = await client.find<Doc>(
domain,
{ createdBy: { $exists: false } },
{ projection: { _id: 1, modifiedBy: 1 } }
)
if (objects.length === 0) {
continue
}
const txes = await client.find<TxCreateDoc<Doc>>(
DOMAIN_TX,
{
_class: core.class.TxCreateDoc,
objectId: { $in: Array.from(objects.map((it) => it._id)) }
},
{ projection: { _id: 1, modifiedBy: 1, createdBy: 1, objectId: 1 } }
)
const txes2 = (
await client.find<TxCollectionCUD<Doc, AttachedDoc>>(
DOMAIN_TX,
{
_class: core.class.TxCollectionCUD,
'tx._class': core.class.TxCreateDoc,
'tx.objectId': { $in: Array.from(objects.map((it) => it._id)) }
},
{ projection: { _id: 1, modifiedBy: 1, createdBy: 1, tx: 1 } }
)
).map((it) => it.tx as unknown as TxCreateDoc<Doc>)
const txMap = new Map(txes.concat(txes2).map((p) => [p.objectId, p]))
console.log('migrateCreateBy', domain, objects.length)
await client.bulk(
domain,
objects.map((it) => {
const createTx = txMap.get(it._id)
return {
filter: { _id: it._id },
update: {
createdBy: createTx?.modifiedBy ?? it.modifiedBy
}
}
})
)
} catch (err) {}
}
}
export const coreOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {},
async migrate (client: MigrationClient): Promise<void> {
await fillCreatedBy(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {}
}

View File

@ -41,9 +41,6 @@ export class TSpace extends TDoc implements Space {
@Prop(ArrOf(TypeRef(core.class.Account)), core.string.Members)
members!: Arr<Ref<Account>>
@Prop(TypeRef(core.class.Account), core.string.CreatedBy)
createdBy?: Ref<Account>
}
@Model(core.class.Account, core.class.Doc, DOMAIN_MODEL)

View File

@ -13,10 +13,9 @@
// limitations under the License.
//
import { DOMAIN_TX, Ref, TxCreateDoc, TxOperations } from '@hcengineering/core'
import { Funnel } from '@hcengineering/lead'
import { Ref, TxOperations } from '@hcengineering/core'
import { MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import core, { DOMAIN_SPACE } from '@hcengineering/model-core'
import core from '@hcengineering/model-core'
import { createKanbanTemplate, createSequence } from '@hcengineering/model-task'
import task, { createKanban, KanbanTemplate } from '@hcengineering/task'
import lead from './plugin'
@ -100,42 +99,8 @@ async function createDefaults (tx: TxOperations): Promise<void> {
await createDefaultKanban(tx)
}
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const objects = await client.find<Funnel>(DOMAIN_SPACE, {
_class: lead.class.Funnel,
createdBy: { $exists: false }
})
const txes = await client.find<TxCreateDoc<Funnel>>(DOMAIN_TX, {
objectClass: lead.class.Funnel,
_class: core.class.TxCreateDoc
})
const txMap = new Map(txes.map((p) => [p.objectId, p]))
for (const object of objects) {
const createTx = txMap.get(object._id)
if (createTx !== undefined && createTx.attributes.createdBy === undefined) {
await client.update(
DOMAIN_TX,
{ _id: createTx._id },
{
'attributes.createdBy': createTx.modifiedBy
}
)
}
await client.update(
DOMAIN_SPACE,
{ _id: object._id },
{
createdBy: createTx?.modifiedBy ?? object.modifiedBy
}
)
}
}
export const leadOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await fillCreatedBy(client)
},
async migrate (client: MigrationClient): Promise<void> {},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const ops = new TxOperations(client, core.account.System)
await createDefaults(ops)

View File

@ -140,38 +140,6 @@ async function fillVacancyNumbers (client: MigrationClient): Promise<void> {
}
}
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const objects = await client.find<Vacancy>(DOMAIN_SPACE, {
_class: recruit.class.Vacancy,
createdBy: { $exists: false }
})
const txes = await client.find<TxCreateDoc<Vacancy>>(DOMAIN_TX, {
objectClass: recruit.class.Vacancy,
_class: core.class.TxCreateDoc
})
const txMap = new Map(txes.map((p) => [p.objectId, p]))
for (const object of objects) {
const createTx = txMap.get(object._id)
if (createTx !== undefined && createTx.attributes.createdBy === undefined) {
await client.update(
DOMAIN_TX,
{ _id: createTx._id },
{
'attributes.createdBy': createTx.modifiedBy
}
)
}
await client.update(
DOMAIN_SPACE,
{ _id: object._id },
{
createdBy: createTx?.modifiedBy ?? object.modifiedBy
}
)
}
}
export const recruitOperation: MigrateOperation = {
async migrate (client: MigrationClient): Promise<void> {
await setCreate(client)
@ -214,7 +182,6 @@ export const recruitOperation: MigrateOperation = {
)
}
}
await fillCreatedBy(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)

View File

@ -20,12 +20,10 @@ import core, {
generateId,
Ref,
SortingOrder,
TxCreateDoc,
TxOperations,
TxResult
} from '@hcengineering/core'
import { createOrUpdate, MigrateOperation, MigrationClient, MigrationUpgradeClient } from '@hcengineering/model'
import { DOMAIN_SPACE } from '@hcengineering/model-core'
import tags from '@hcengineering/tags'
import {
calcRank,
@ -410,38 +408,6 @@ async function upgradeIssues (tx: TxOperations): Promise<void> {
}
}
async function fillCreatedBy (client: MigrationClient): Promise<void> {
const objects = await client.find<Team>(DOMAIN_SPACE, {
_class: tracker.class.Team,
createdBy: { $exists: false }
})
const txes = await client.find<TxCreateDoc<Team>>(DOMAIN_TX, {
objectClass: tracker.class.Team,
_class: core.class.TxCreateDoc
})
const txMap = new Map(txes.map((p) => [p.objectId, p]))
for (const object of objects) {
const createTx = txMap.get(object._id)
if (createTx !== undefined && createTx.attributes.createdBy === undefined) {
await client.update(
DOMAIN_TX,
{ _id: createTx._id },
{
'attributes.createdBy': createTx.modifiedBy
}
)
}
await client.update(
DOMAIN_SPACE,
{ _id: object._id },
{
createdBy: createTx?.modifiedBy ?? object.modifiedBy
}
)
}
}
async function upgradeProjects (tx: TxOperations): Promise<void> {
await upgradeProjectIcons(tx)
}
@ -460,7 +426,6 @@ export const trackerOperation: MigrateOperation = {
await Promise.all([migrateIssueProjects(client), migrateParentIssues(client)])
await migrateIssueParentInfo(client)
await fillRank(client)
await fillCreatedBy(client)
},
async upgrade (client: MigrationUpgradeClient): Promise<void> {
const tx = new TxOperations(client, core.account.System)

View File

@ -56,6 +56,7 @@ export interface Doc extends Obj {
space: Ref<Space>
modifiedOn: Timestamp
modifiedBy: Ref<Account>
createdBy?: Ref<Account>
}
/**
@ -295,7 +296,6 @@ export interface Space extends Doc {
private: boolean
members: Arr<Ref<Account>>
archived: boolean
createdBy?: Ref<Account>
}
/**

View File

@ -154,6 +154,7 @@ export default plugin(coreId, {
Description: '' as IntlString,
Hyperlink: '' as IntlString,
Private: '' as IntlString,
Object: '' as IntlString
Object: '' as IntlString,
CreatedBy: '' as IntlString
}
})

View File

@ -312,7 +312,8 @@ export abstract class TxProcessor implements WithTx {
_class: tx.objectClass,
space: tx.objectSpace,
modifiedBy: tx.modifiedBy,
modifiedOn: tx.modifiedOn
modifiedOn: tx.modifiedOn,
createdBy: tx.createdBy ?? tx.modifiedBy
} as T
}
@ -444,6 +445,7 @@ export class TxFactory {
objectSpace: space,
modifiedOn: modifiedOn ?? Date.now(),
modifiedBy: modifiedBy ?? this.account,
createdBy: modifiedBy ?? this.account,
attributes
}
}

View File

@ -5,7 +5,9 @@ import {
DocumentQuery,
Domain,
FindOptions,
Hierarchy,
IncOptions,
ModelDb,
ObjQueryType,
OmitNever,
PushOptions,
@ -71,11 +73,19 @@ export interface MigrationClient {
operations: MigrateUpdate<T>
) => Promise<MigrationResult>
bulk: <T extends Doc>(
domain: Domain,
operations: { filter: MigrationDocumentQuery<T>, update: MigrateUpdate<T> }[]
) => Promise<MigrationResult>
// Move documents per domain
move: <T extends Doc>(sourceDomain: Domain, query: DocumentQuery<T>, targetDomain: Domain) => Promise<MigrationResult>
create: <T extends Doc>(domain: Domain, doc: T) => Promise<void>
delete: <T extends Doc>(domain: Domain, _id: Ref<T>) => Promise<void>
hierarchy: Hierarchy
model: ModelDb
}
/**

View File

@ -30,8 +30,7 @@ export async function createBoard (
description,
private: false,
archived: false,
members: [getCurrentAccount()._id],
createdBy: getCurrentAccount()._id
members: [getCurrentAccount()._id]
})
await Promise.all([createKanban(client, boardRef, templateId)])

View File

@ -37,8 +37,7 @@
description: '',
private: isPrivate,
archived: false,
members: [getCurrentAccount()._id],
createdBy: getCurrentAccount()._id
members: [getCurrentAccount()._id]
})
const navigate = await getResource(workbench.actionImpl.Navigate)

View File

@ -52,8 +52,7 @@
description: '',
private: true,
archived: false,
members: accIds,
createdBy: getCurrentAccount()._id
members: accIds
})
await navigate([], undefined as any, {

View File

@ -47,8 +47,7 @@
description,
private: isPrivate,
archived: false,
members: [getCurrentAccount()._id],
createdBy: getCurrentAccount()._id
members: [getCurrentAccount()._id]
})
await createKanban(client, id, templateId)

View File

@ -143,7 +143,6 @@
archived: false,
number: (incResult as any).object.sequence,
company,
createdBy: getCurrentAccount()._id,
members: [getCurrentAccount()._id]
},
objectId

View File

@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import core, { getCurrentAccount } from '@hcengineering/core'
import core from '@hcengineering/core'
import presentation, { getClient, SpaceCreateCard } from '@hcengineering/presentation'
import { EditBox, Grid, IconFolder, ToggleWithLabel } from '@hcengineering/ui'
import { createEventDispatcher } from 'svelte'
@ -37,8 +37,7 @@
description,
private: isPrivate,
archived: false,
members: [],
createdBy: getCurrentAccount()._id
members: []
})
}
</script>

View File

@ -13,8 +13,11 @@
// limitations under the License.
-->
<script lang="ts">
import { ClassifierKind, Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
import contact, { Employee, EmployeeAccount } from '@hcengineering/contact'
import { EmployeePresenter } from '@hcengineering/contact-resources'
import core, { ClassifierKind, Doc, Mixin, Ref, WithLookup } from '@hcengineering/core'
import { AttributeBarEditor, createQuery, getClient, KeyedAttribute } from '@hcengineering/presentation'
import tags from '@hcengineering/tags'
import type { Issue, IssueStatus } from '@hcengineering/tracker'
import { Component, Label } from '@hcengineering/ui'
@ -73,6 +76,31 @@
}
$: updateKeys(['title', 'description', 'priority', 'status', 'number', 'assignee', 'project', 'dueDate', 'sprint'])
const employeeAccountQuery = createQuery()
const employeeQuery = createQuery()
let account: EmployeeAccount | undefined
let employee: Employee | undefined
$: employeeAccountQuery.query(
contact.class.EmployeeAccount,
{ _id: issue.createdBy as Ref<EmployeeAccount> },
(res) => {
;[account] = res
},
{ limit: 1 }
)
$: account &&
employeeQuery.query(
contact.class.Employee,
{ _id: account.employee },
(res) => {
;[employee] = res
},
{ limit: 1 }
)
</script>
<div class="content">
@ -126,6 +154,13 @@
</span>
<PriorityEditor value={issue} shouldShowLabel />
<span class="label">
<Label label={core.string.CreatedBy} />
</span>
<div class="min-w-0 w-full employee-button overflow-label">
<EmployeePresenter value={employee} inline />
</div>
<span class="label">
<Label label={tracker.string.Assignee} />
</span>
@ -196,4 +231,17 @@
align-self: start;
margin-top: 0.385rem;
}
.employee-button {
padding: 0 0.875rem;
border: 1px solid transparent;
display: flex;
min-height: 2rem;
&:hover {
border: 1px solid var(--button-border-hover);
color: var(--accent-color);
transition-duration: 0;
border-radius: 0.25rem;
}
}
</style>

View File

@ -77,7 +77,6 @@
private: isPrivate,
members,
archived: false,
createdBy: getCurrentAccount()._id,
identifier,
sequence: 0,
issueStatuses: 0,

View File

@ -214,11 +214,9 @@ export async function backup (transactorUrl: string, workspaceId: WorkspaceId, s
const infoFile = 'backup.json.gz'
if (await storage.exists(infoFile)) {
const backupInfoE = JSON.parse(gunzipSync(await storage.loadFile(infoFile)).toString())
if (backupInfoE.version === backupInfo.version) {
backupInfo = backupInfoE
}
backupInfo = JSON.parse(gunzipSync(await storage.loadFile(infoFile)).toString())
}
backupInfo.version = '0.6.1'
backupInfo.workspace = workspaceId.name
backupInfo.productId = workspaceId.productId

View File

@ -22,7 +22,9 @@ import core, {
DOMAIN_MODEL,
DOMAIN_TX,
FieldIndex,
Hierarchy,
IndexKind,
ModelDb,
Tx,
WorkspaceId
} from '@hcengineering/core'
@ -172,7 +174,20 @@ export async function upgradeModel (
const insert = await db.collection(DOMAIN_TX).insertMany(model as Document[])
console.log(`${insert.insertedCount} model transactions inserted.`)
const migrateClient = new MigrateClientImpl(db)
const hierarchy = new Hierarchy()
const modelDb = new ModelDb(hierarchy)
for (const tx of txes) {
try {
hierarchy.tx(tx)
} catch (err: any) {}
}
for (const tx of txes) {
try {
await modelDb.tx(tx)
} catch (err: any) {}
}
const migrateClient = new MigrateClientImpl(db, hierarchy, modelDb)
for (const op of migrateOperations) {
console.log('migrate:', op[0])
await op[1].migrate(migrateClient)

View File

@ -1,12 +1,22 @@
import { Doc, DocumentQuery, Domain, FindOptions, isOperator, Ref, SortingOrder } from '@hcengineering/core'
import { MigrationClient, MigrateUpdate, MigrationResult } from '@hcengineering/model'
import {
Doc,
DocumentQuery,
Domain,
FindOptions,
Hierarchy,
isOperator,
ModelDb,
Ref,
SortingOrder
} from '@hcengineering/core'
import { MigrateUpdate, MigrationClient, MigrationResult } from '@hcengineering/model'
import { Db, Document, Filter, Sort, UpdateFilter } from 'mongodb'
/**
* Upgrade client implementation.
*/
export class MigrateClientImpl implements MigrationClient {
constructor (readonly db: Db) {}
constructor (readonly db: Db, readonly hierarchy: Hierarchy, readonly model: ModelDb) {}
private translateQuery<T extends Doc>(query: DocumentQuery<T>): Filter<Document> {
const translated: any = {}
@ -67,6 +77,22 @@ export class MigrateClientImpl implements MigrationClient {
}
}
async bulk<T extends Doc>(
domain: Domain,
operations: { filter: DocumentQuery<T>, update: MigrateUpdate<T> }[]
): Promise<MigrationResult> {
const result = await this.db.collection(domain).bulkWrite(
operations.map((it) => ({
updateOne: {
filter: this.translateQuery(it.filter),
update: { $set: it.update }
}
}))
)
return { matched: result.matchedCount, updated: result.modifiedCount }
}
async move<T extends Doc>(
sourceDomain: Domain,
query: DocumentQuery<T>,