mirror of
https://github.com/hcengineering/platform.git
synced 2025-04-07 00:12:50 +00:00
Merge remote-tracking branch 'origin/develop' into staging
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
commit
ee88f22c0a
@ -75,6 +75,7 @@
|
|||||||
"@hcengineering/model-all": "^0.6.0",
|
"@hcengineering/model-all": "^0.6.0",
|
||||||
"@hcengineering/model-attachment": "^0.6.0",
|
"@hcengineering/model-attachment": "^0.6.0",
|
||||||
"@hcengineering/model-contact": "^0.6.1",
|
"@hcengineering/model-contact": "^0.6.1",
|
||||||
|
"@hcengineering/model-document": "^0.6.0",
|
||||||
"@hcengineering/model-recruit": "^0.6.0",
|
"@hcengineering/model-recruit": "^0.6.0",
|
||||||
"@hcengineering/model-telegram": "^0.6.0",
|
"@hcengineering/model-telegram": "^0.6.0",
|
||||||
"@hcengineering/model-tracker": "^0.6.0",
|
"@hcengineering/model-tracker": "^0.6.0",
|
||||||
|
@ -122,6 +122,7 @@ import { copyToDatalake, moveFiles, showLostFiles } from './storage'
|
|||||||
import { getModelVersion } from '@hcengineering/model-all'
|
import { getModelVersion } from '@hcengineering/model-all'
|
||||||
import { type DatalakeConfig, DatalakeService, createDatalakeClient } from '@hcengineering/datalake'
|
import { type DatalakeConfig, DatalakeService, createDatalakeClient } from '@hcengineering/datalake'
|
||||||
import { S3Service, type S3Config } from '@hcengineering/s3'
|
import { S3Service, type S3Config } from '@hcengineering/s3'
|
||||||
|
import { restoreWikiContentMongo } from './markup'
|
||||||
|
|
||||||
const colorConstants = {
|
const colorConstants = {
|
||||||
colorRed: '\u001b[31m',
|
colorRed: '\u001b[31m',
|
||||||
@ -1240,6 +1241,58 @@ export function devTool (
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
program
|
||||||
|
.command('restore-wiki-content-mongo')
|
||||||
|
.description('restore wiki document contents')
|
||||||
|
.option('-w, --workspace <workspace>', 'Selected workspace only', '')
|
||||||
|
.option('-d, --dryrun', 'Dry run', false)
|
||||||
|
.action(async (cmd: { workspace: string, dryrun: boolean }) => {
|
||||||
|
const params = {
|
||||||
|
dryRun: cmd.dryrun
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dbUrl, version } = prepareTools()
|
||||||
|
|
||||||
|
let workspaces: Workspace[] = []
|
||||||
|
const accountUrl = getAccountDBUrl()
|
||||||
|
await withDatabase(accountUrl, async (db) => {
|
||||||
|
workspaces = await listWorkspacesPure(db)
|
||||||
|
workspaces = workspaces
|
||||||
|
.filter((p) => p.mode !== 'archived')
|
||||||
|
.filter((p) => cmd.workspace === '' || p.workspace === cmd.workspace)
|
||||||
|
.sort((a, b) => b.lastVisit - a.lastVisit)
|
||||||
|
})
|
||||||
|
|
||||||
|
await withStorage(async (storageAdapter) => {
|
||||||
|
await withDatabase(dbUrl, async (db) => {
|
||||||
|
const mongodbUri = getMongoDBUrl()
|
||||||
|
const client = getMongoClient(mongodbUri)
|
||||||
|
const _client = await client.getClient()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const count = workspaces.length
|
||||||
|
let index = 0
|
||||||
|
for (const workspace of workspaces) {
|
||||||
|
index++
|
||||||
|
|
||||||
|
toolCtx.info('processing workspace', { workspace: workspace.workspace, index, count })
|
||||||
|
if (workspace.version === undefined || !deepEqual(workspace.version, version)) {
|
||||||
|
console.log(`upgrade to ${versionToString(version)} is required`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceId = getWorkspaceId(workspace.workspace)
|
||||||
|
const wsDb = getWorkspaceMongoDB(_client, { name: workspace.workspace })
|
||||||
|
|
||||||
|
await restoreWikiContentMongo(toolCtx, wsDb, workspaceId, storageAdapter, params)
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
program
|
program
|
||||||
.command('confirm-email <email>')
|
.command('confirm-email <email>')
|
||||||
.description('confirm user email')
|
.description('confirm user email')
|
||||||
|
90
dev/tool/src/markup.ts
Normal file
90
dev/tool/src/markup.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
//
|
||||||
|
// 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 { loadCollabYdoc, saveCollabYdoc, yDocCopyXmlField } from '@hcengineering/collaboration'
|
||||||
|
import { type MeasureContext, type WorkspaceId, makeCollabYdocId } from '@hcengineering/core'
|
||||||
|
import document, { type Document } from '@hcengineering/document'
|
||||||
|
import { DOMAIN_DOCUMENT } from '@hcengineering/model-document'
|
||||||
|
import { type StorageAdapter } from '@hcengineering/server-core'
|
||||||
|
import { type Db } from 'mongodb'
|
||||||
|
|
||||||
|
export interface RestoreWikiContentParams {
|
||||||
|
dryRun: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function restoreWikiContentMongo (
|
||||||
|
ctx: MeasureContext,
|
||||||
|
db: Db,
|
||||||
|
workspaceId: WorkspaceId,
|
||||||
|
storageAdapter: StorageAdapter,
|
||||||
|
params: RestoreWikiContentParams
|
||||||
|
): Promise<void> {
|
||||||
|
const iterator = db.collection<Document>(DOMAIN_DOCUMENT).find({ _class: document.class.Document })
|
||||||
|
|
||||||
|
let processedCnt = 0
|
||||||
|
let restoredCnt = 0
|
||||||
|
|
||||||
|
function printStats (): void {
|
||||||
|
console.log('...processed', processedCnt, 'restored', restoredCnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
const doc = await iterator.next()
|
||||||
|
if (doc === null) return
|
||||||
|
|
||||||
|
processedCnt++
|
||||||
|
if (processedCnt % 100 === 0) {
|
||||||
|
printStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
const correctCollabId = { objectClass: doc._class, objectId: doc._id, objectAttr: 'content' }
|
||||||
|
const wrongCollabId = { objectClass: doc._class, objectId: doc._id, objectAttr: 'description' }
|
||||||
|
|
||||||
|
const stat = storageAdapter.stat(ctx, workspaceId, makeCollabYdocId(wrongCollabId))
|
||||||
|
if (stat === undefined) continue
|
||||||
|
|
||||||
|
const ydoc1 = await loadCollabYdoc(ctx, storageAdapter, workspaceId, correctCollabId)
|
||||||
|
const ydoc2 = await loadCollabYdoc(ctx, storageAdapter, workspaceId, wrongCollabId)
|
||||||
|
|
||||||
|
if (ydoc1 !== undefined && ydoc1.share.has('content')) {
|
||||||
|
// There already is content, we should skip the document
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ydoc2 === undefined) {
|
||||||
|
// There are no content to restore
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log('restoring content for', doc._id)
|
||||||
|
if (!params.dryRun) {
|
||||||
|
if (ydoc2.share.has('description') && !ydoc2.share.has('content')) {
|
||||||
|
yDocCopyXmlField(ydoc2, 'description', 'content')
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveCollabYdoc(ctx, storageAdapter, workspaceId, correctCollabId, ydoc2)
|
||||||
|
}
|
||||||
|
restoredCnt++
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error('failed to restore content for', doc._id, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
printStats()
|
||||||
|
await iterator.close()
|
||||||
|
}
|
||||||
|
}
|
@ -15,13 +15,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getClient } from '@hcengineering/presentation'
|
import { getClient } from '@hcengineering/presentation'
|
||||||
import { EditBox, ModernButton } from '@hcengineering/ui'
|
import { EditBox, ModernButton } from '@hcengineering/ui'
|
||||||
import { Room, isOffice } from '@hcengineering/love'
|
import { Room, isOffice, type ParticipantInfo } from '@hcengineering/love'
|
||||||
import { createEventDispatcher, onMount } from 'svelte'
|
import { createEventDispatcher, onMount } from 'svelte'
|
||||||
import { personByIdStore } from '@hcengineering/contact-resources'
|
import { personByIdStore } from '@hcengineering/contact-resources'
|
||||||
import { IntlString } from '@hcengineering/platform'
|
import { IntlString } from '@hcengineering/platform'
|
||||||
|
|
||||||
import love from '../plugin'
|
import love from '../plugin'
|
||||||
import { getRoomName, tryConnect, isConnected } from '../utils'
|
import { getRoomName, tryConnect, isConnected, leaveRoom } from '../utils'
|
||||||
import { infos, invites, myInfo, myRequests, selectedRoomPlace, myOffice, currentRoom } from '../stores'
|
import { infos, invites, myInfo, myRequests, selectedRoomPlace, myOffice, currentRoom } from '../stores'
|
||||||
|
|
||||||
export let object: Room
|
export let object: Room
|
||||||
@ -79,11 +79,22 @@
|
|||||||
object: Room,
|
object: Room,
|
||||||
connecting: boolean,
|
connecting: boolean,
|
||||||
isConnected: boolean,
|
isConnected: boolean,
|
||||||
|
info: ParticipantInfo[],
|
||||||
myOffice?: Room,
|
myOffice?: Room,
|
||||||
currentRoom?: Room
|
currentRoom?: Room
|
||||||
): boolean {
|
): boolean {
|
||||||
// Do not show connect button in my office
|
if (isOffice(object)) {
|
||||||
if (object._id === myOffice?._id) return false
|
// Do not show connect button in own office
|
||||||
|
if (object._id === myOffice?._id) return false
|
||||||
|
// Do not show connect for empty office
|
||||||
|
if (object.person === null) return false
|
||||||
|
|
||||||
|
const owner = object.person
|
||||||
|
const ownerInfo = info.find((p) => p.person === owner)
|
||||||
|
// Do not show connect if owner is not in the office
|
||||||
|
if (ownerInfo?.room !== object._id) return false
|
||||||
|
}
|
||||||
|
|
||||||
// Show during connecting with spinner
|
// Show during connecting with spinner
|
||||||
if (connecting) return true
|
if (connecting) return true
|
||||||
// Do not show connect button if we are already connected to the room
|
// Do not show connect button if we are already connected to the room
|
||||||
@ -104,7 +115,7 @@
|
|||||||
focusIndex={1}
|
focusIndex={1}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if showConnectionButton(object, connecting, $isConnected, $myOffice, $currentRoom)}
|
{#if showConnectionButton(object, connecting, $isConnected, $infos, $myOffice, $currentRoom)}
|
||||||
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
|
<ModernButton label={connectLabel} size="large" kind={'primary'} on:click={connect} loading={connecting} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -316,6 +316,7 @@ export async function generateDocUpdateMessages (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (tx._class) {
|
switch (tx._class) {
|
||||||
|
@ -251,6 +251,15 @@ const docSyncInfo: Schema = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const githubLogin: Schema = {
|
||||||
|
...baseSchema,
|
||||||
|
login: {
|
||||||
|
type: 'text',
|
||||||
|
notNull: true,
|
||||||
|
index: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addSchema (domain: string, schema: Schema): void {
|
export function addSchema (domain: string, schema: Schema): void {
|
||||||
domainSchemas[translateDomain(domain)] = schema
|
domainSchemas[translateDomain(domain)] = schema
|
||||||
domainSchemaFields.set(domain, createSchemaFields(schema))
|
domainSchemaFields.set(domain, createSchemaFields(schema))
|
||||||
@ -271,7 +280,8 @@ export const domainSchemas: Record<string, Schema> = {
|
|||||||
notification: notificationSchema,
|
notification: notificationSchema,
|
||||||
[translateDomain('notification-dnc')]: dncSchema,
|
[translateDomain('notification-dnc')]: dncSchema,
|
||||||
[translateDomain('notification-user')]: userNotificationSchema,
|
[translateDomain('notification-user')]: userNotificationSchema,
|
||||||
github: docSyncInfo
|
[translateDomain('github_sync')]: docSyncInfo,
|
||||||
|
[translateDomain('github_user')]: githubLogin
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSchema (domain: string): Schema {
|
export function getSchema (domain: string): Schema {
|
||||||
|
@ -95,9 +95,11 @@ export { githubId } from '@hcengineering/github'
|
|||||||
export { githubOperation, githubOperationPreTime } from './migration'
|
export { githubOperation, githubOperationPreTime } from './migration'
|
||||||
export { default } from './plugin'
|
export { default } from './plugin'
|
||||||
export const DOMAIN_GITHUB = 'github' as Domain
|
export const DOMAIN_GITHUB = 'github' as Domain
|
||||||
|
export const DOMAIN_GITHUB_SYNC = 'github_sync' as Domain
|
||||||
|
export const DOMAIN_GITHUB_USER = 'github_user' as Domain
|
||||||
export const DOMAIN_GITHUB_COMMENTS = 'github_comments' as Domain
|
export const DOMAIN_GITHUB_COMMENTS = 'github_comments' as Domain
|
||||||
|
|
||||||
@Model(github.class.DocSyncInfo, core.class.Doc, DOMAIN_GITHUB)
|
@Model(github.class.DocSyncInfo, core.class.Doc, DOMAIN_GITHUB_SYNC)
|
||||||
export class TDocSyncInfo extends TDoc implements DocSyncInfo {
|
export class TDocSyncInfo extends TDoc implements DocSyncInfo {
|
||||||
// _id === objectId
|
// _id === objectId
|
||||||
@Prop(TypeNumber(), getEmbeddedLabel('Github number'))
|
@Prop(TypeNumber(), getEmbeddedLabel('Github number'))
|
||||||
@ -152,7 +154,7 @@ export class TDocSyncInfo extends TDoc implements DocSyncInfo {
|
|||||||
deleted!: boolean
|
deleted!: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@Model(github.class.GithubUserInfo, core.class.Doc, DOMAIN_GITHUB)
|
@Model(github.class.GithubUserInfo, core.class.Doc, DOMAIN_GITHUB_USER)
|
||||||
export class TGithubUserInfo extends TDoc implements GithubUserInfo {
|
export class TGithubUserInfo extends TDoc implements GithubUserInfo {
|
||||||
@Prop(TypeString(), getEmbeddedLabel('ID'))
|
@Prop(TypeString(), getEmbeddedLabel('ID'))
|
||||||
@ReadOnly()
|
@ReadOnly()
|
||||||
|
@ -32,7 +32,7 @@ import github from './plugin'
|
|||||||
import { DOMAIN_TIME } from '@hcengineering/model-time'
|
import { DOMAIN_TIME } from '@hcengineering/model-time'
|
||||||
import { DOMAIN_TRACKER } from '@hcengineering/model-tracker'
|
import { DOMAIN_TRACKER } from '@hcengineering/model-tracker'
|
||||||
import time from '@hcengineering/time'
|
import time from '@hcengineering/time'
|
||||||
import { DOMAIN_GITHUB } from '.'
|
import { DOMAIN_GITHUB, DOMAIN_GITHUB_SYNC, DOMAIN_GITHUB_USER } from '.'
|
||||||
|
|
||||||
export async function guessStatus (status: Status, statuses: Status[]): Promise<Status> {
|
export async function guessStatus (status: Status, statuses: Status[]): Promise<Status> {
|
||||||
const active = (): Status => statuses.find((it) => it.category === task.statusCategory.Active) as Status
|
const active = (): Status => statuses.find((it) => it.category === task.statusCategory.Active) as Status
|
||||||
@ -328,6 +328,13 @@ export const githubOperationPreTime: MigrateOperation = {
|
|||||||
func: async (client) => {
|
func: async (client) => {
|
||||||
await client.deleteMany(DOMAIN_TX, { objectClass: github.class.DocSyncInfo })
|
await client.deleteMany(DOMAIN_TX, { objectClass: github.class.DocSyncInfo })
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
state: 'migrate-github-sync-domain',
|
||||||
|
func: async (client) => {
|
||||||
|
await client.move(DOMAIN_GITHUB, { _class: github.class.DocSyncInfo }, DOMAIN_GITHUB_SYNC)
|
||||||
|
await client.move(DOMAIN_GITHUB, { _class: github.class.GithubUserInfo }, DOMAIN_GITHUB_USER)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
Loading…
Reference in New Issue
Block a user