>
}
diff --git a/plugins/workbench-assets/lang/en.json b/plugins/workbench-assets/lang/en.json
index d483a13f817..02abe025ccf 100644
--- a/plugins/workbench-assets/lang/en.json
+++ b/plugins/workbench-assets/lang/en.json
@@ -30,6 +30,7 @@
"PleaseUpdate": "Please update",
"ServerUnderMaintenance": "Server is under maintenance",
"MobileNotSupported": "Sorry, mobile devices support coming soon. In the meantime, please use Desktop",
- "LogInAnyway": "Log in anyway"
+ "LogInAnyway": "Log in anyway",
+ "WorkspaceCreating": "Creation in progress..."
}
}
diff --git a/plugins/workbench-assets/lang/es.json b/plugins/workbench-assets/lang/es.json
index cf9e4d068db..3a65e606453 100644
--- a/plugins/workbench-assets/lang/es.json
+++ b/plugins/workbench-assets/lang/es.json
@@ -30,6 +30,7 @@
"PleaseUpdate": "Por favor, actualice",
"ServerUnderMaintenance": "El servidor está en mantenimiento",
"MobileNotSupported": "Disculpa, el soporte para dispositivos móviles estará disponible próximamente. Mientras tanto, por favor usa el escritorio.",
- "LogInAnyway": "Iniciar sesión de todas formas"
+ "LogInAnyway": "Iniciar sesión de todas formas",
+ "WorkspaceCreating": "Creation in progress..."
}
}
\ No newline at end of file
diff --git a/plugins/workbench-assets/lang/pt.json b/plugins/workbench-assets/lang/pt.json
index 9a4aabc56d2..51d7f8d127a 100644
--- a/plugins/workbench-assets/lang/pt.json
+++ b/plugins/workbench-assets/lang/pt.json
@@ -30,6 +30,7 @@
"PleaseUpdate": "Atualize",
"ServerUnderMaintenance": "Servidor em manutenção",
"MobileNotSupported": "Desculpe, o suporte para dispositivos móveis estará disponível em breve. Enquanto isso, por favor, use o Desktop.",
- "LogInAnyway": "Entrar de qualquer maneira"
+ "LogInAnyway": "Entrar de qualquer maneira",
+ "WorkspaceCreating": "Creation in progress..."
}
}
\ No newline at end of file
diff --git a/plugins/workbench-assets/lang/ru.json b/plugins/workbench-assets/lang/ru.json
index 0d2d57046c4..fd9728755ce 100644
--- a/plugins/workbench-assets/lang/ru.json
+++ b/plugins/workbench-assets/lang/ru.json
@@ -30,6 +30,7 @@
"PleaseUpdate": "Пожалуйста, обновите приложение",
"ServerUnderMaintenance": "Обслуживание сервера",
"MobileNotSupported": "Простите, поддержка мобильных устройств скоро будет доступна. Пока воспользуйтесь компьютером.",
- "LogInAnyway": "Все равно войти"
+ "LogInAnyway": "Все равно войти",
+ "WorkspaceCreating": "Пространство создается..."
}
}
diff --git a/plugins/workbench-resources/src/components/WorkbenchApp.svelte b/plugins/workbench-resources/src/components/WorkbenchApp.svelte
index a4caa38202e..02ea6ab250d 100644
--- a/plugins/workbench-resources/src/components/WorkbenchApp.svelte
+++ b/plugins/workbench-resources/src/components/WorkbenchApp.svelte
@@ -28,8 +28,9 @@
import { connect, disconnect, versionError } from '../connect'
import { workbenchId } from '@hcengineering/workbench'
- import workbench from '../plugin'
import { onDestroy } from 'svelte'
+ import workbench from '../plugin'
+ import { workspaceCreating } from '../utils'
const isNeedUpgrade = window.location.host === ''
@@ -54,7 +55,14 @@
{:else}
{#key $location.path[1]}
{#await connect(getMetadata(workbench.metadata.PlatformTitle) ?? 'Platform')}
-
+
+ {#if ($workspaceCreating ?? -1) > 0}
+
+
+ {$workspaceCreating} %
+
+ {/if}
+
{:then client}
{#if !client && versionError}
diff --git a/plugins/workbench-resources/src/connect.ts b/plugins/workbench-resources/src/connect.ts
index 276a466190b..91bf0e44aa3 100644
--- a/plugins/workbench-resources/src/connect.ts
+++ b/plugins/workbench-resources/src/connect.ts
@@ -23,6 +23,7 @@ import {
setMetadataLocalStorage
} from '@hcengineering/ui'
import plugin from './plugin'
+import { workspaceCreating } from './utils'
export let versionError: string | undefined = ''
@@ -63,6 +64,7 @@ export async function connect (title: string): Promise {
}
const tokens: Record = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {}
let token = tokens[ws]
+
if (token === undefined && getMetadata(presentation.metadata.Token) !== undefined) {
const selectWorkspace = await getResource(login.function.SelectWorkspace)
const loginInfo = await ctx.with('select-workspace', {}, async () => (await selectWorkspace(ws))[1])
@@ -73,6 +75,22 @@ export async function connect (title: string): Promise {
}
}
setMetadata(presentation.metadata.Token, token)
+
+ const fetchWorkspace = await getResource(login.function.FetchWorkspace)
+ let loginInfo = await ctx.with('select-workspace', {}, async () => (await fetchWorkspace(ws))[1])
+ if (loginInfo?.creating === true) {
+ while (true) {
+ workspaceCreating.set(loginInfo?.createProgress ?? 0)
+ loginInfo = await ctx.with('select-workspace', {}, async () => (await fetchWorkspace(ws))[1])
+ workspaceCreating.set(loginInfo?.createProgress)
+ if (loginInfo?.creating === false) {
+ workspaceCreating.set(-1)
+ break
+ }
+ await new Promise((resolve) => setTimeout(resolve, 1000))
+ }
+ }
+
document.cookie =
encodeURIComponent(presentation.metadata.Token.replaceAll(':', '-')) + '=' + encodeURIComponent(token) + '; path=/'
diff --git a/plugins/workbench-resources/src/plugin.ts b/plugins/workbench-resources/src/plugin.ts
index f709c287ebc..92c2e1c0b9c 100644
--- a/plugins/workbench-resources/src/plugin.ts
+++ b/plugins/workbench-resources/src/plugin.ts
@@ -43,7 +43,8 @@ export default mergeIds(workbenchId, workbench, {
NewVersionAvailable: '' as IntlString,
PleaseUpdate: '' as IntlString,
MobileNotSupported: '' as IntlString,
- LogInAnyway: '' as IntlString
+ LogInAnyway: '' as IntlString,
+ WorkspaceCreating: '' as IntlString
},
metadata: {
MobileAllowed: '' as Metadata
diff --git a/plugins/workbench-resources/src/utils.ts b/plugins/workbench-resources/src/utils.ts
index dcf82f0da5e..2d132644351 100644
--- a/plugins/workbench-resources/src/utils.ts
+++ b/plugins/workbench-resources/src/utils.ts
@@ -33,6 +33,8 @@ import view from '@hcengineering/view'
import workbench, { type Application, type NavigatorModel } from '@hcengineering/workbench'
import { writable } from 'svelte/store'
+export const workspaceCreating = writable(undefined)
+
export function getSpecialSpaceClass (model: NavigatorModel): Array[>> {
const spaceResult = model.spaces.map((x) => x.spaceClass)
const result = (model.specials ?? [])
diff --git a/pods/account/src/index.ts b/pods/account/src/index.ts
index 699d7142560..1e938c96831 100644
--- a/pods/account/src/index.ts
+++ b/pods/account/src/index.ts
@@ -14,7 +14,7 @@
// limitations under the License.
//
-import account, { ACCOUNT_DB, type AccountMethod, accountId } from '@hcengineering/account'
+import account, { ACCOUNT_DB, type AccountMethod, accountId, cleanInProgressWorkspaces } from '@hcengineering/account'
import accountEn from '@hcengineering/account/lang/en.json'
import accountRu from '@hcengineering/account/lang/ru.json'
import { registerProviders } from '@hcengineering/auth-providers'
@@ -93,6 +93,9 @@ export function serveAccount (measureCtx: MeasureContext, methods: Record {
const db = p.db(ACCOUNT_DB)
registerProviders(measureCtx, app, router, db, productId, serverSecret, frontURL)
+
+ // We need to clean workspace with creating === true, since server is restarted.
+ void cleanInProgressWorkspaces(db, productId)
})
const extractToken = (header: IncomingHttpHeaders): string | undefined => {
diff --git a/server/account/src/index.ts b/server/account/src/index.ts
index eb4317f82a4..0e0b1e35c29 100644
--- a/server/account/src/index.ts
+++ b/server/account/src/index.ts
@@ -31,6 +31,7 @@ import core, {
generateId,
getWorkspaceId,
MeasureContext,
+ MeasureMetricsContext,
RateLimiter,
Ref,
systemAccountEmail,
@@ -112,6 +113,9 @@ export interface Workspace {
lastVisit: number
createdBy: string
+
+ creating?: boolean
+ createProgress?: number // Some progress
}
/**
@@ -129,6 +133,9 @@ export interface LoginInfo {
export interface WorkspaceLoginInfo extends LoginInfo {
workspace: string
productId: string
+
+ creating?: boolean
+ createProgress?: number
}
/**
@@ -344,12 +351,14 @@ export async function selectWorkspace (
email,
token: generateToken(email, getWorkspaceId(workspaceInfo.workspace, productId), getExtra(accountInfo)),
workspace: workspaceUrl,
- productId
+ productId,
+ creating: workspaceInfo.creating,
+ createProgress: workspaceInfo.createProgress
}
}
if (workspaceInfo !== null) {
- if (workspaceInfo.disabled === true) {
+ if (workspaceInfo.disabled === true && workspaceInfo.creating !== true) {
await ctx.error('workspace disabled', { workspaceUrl, email })
throw new PlatformError(
new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspace: workspaceUrl })
@@ -364,7 +373,9 @@ export async function selectWorkspace (
email,
token: generateToken(email, getWorkspaceId(workspaceInfo.workspace, productId), getExtra(accountInfo)),
workspace: workspaceUrl,
- productId
+ productId,
+ creating: workspaceInfo.creating,
+ createProgress: workspaceInfo.createProgress
}
return result
}
@@ -675,6 +686,19 @@ export async function setWorkspaceDisabled (db: Db, workspaceId: Workspace['_id'
await db.collection(WORKSPACE_COLLECTION).updateOne({ _id: workspaceId }, { $set: { disabled } })
}
+export async function cleanInProgressWorkspaces (db: Db, productId: string): Promise {
+ const toDelete = (
+ await db
+ .collection(WORKSPACE_COLLECTION)
+ .find(withProductId(productId, { creating: true }))
+ .toArray()
+ ).map((it) => ({ ...it, productId }))
+ const ctx = new MeasureMetricsContext('clean', {})
+ for (const d of toDelete) {
+ await dropWorkspace(ctx, db, productId, d.workspace)
+ }
+}
+
/**
* @public
*/
@@ -737,6 +761,8 @@ async function generateWorkspaceRecord (
workspaceName,
accounts: [],
disabled: true,
+ creating: true,
+ createProgress: 0,
createdOn: Date.now(),
lastVisit: Date.now(),
createdBy: email
@@ -767,6 +793,8 @@ async function generateWorkspaceRecord (
workspaceName,
accounts: [],
disabled: true,
+ creating: true,
+ createProgress: 0,
createdOn: Date.now(),
lastVisit: Date.now(),
createdBy: email
@@ -808,7 +836,8 @@ export async function createWorkspace (
productId: string,
email: string,
workspaceName: string,
- workspace?: string
+ workspace?: string,
+ notifyHandler?: (workspace: Workspace) => void
): Promise<{ workspaceInfo: Workspace, err?: any, client?: Client }> {
return await rateLimiter.exec(async () => {
// We need to search for duplicate workspaceUrl
@@ -818,6 +847,18 @@ export async function createWorkspace (
searchPromise = generateWorkspaceRecord(db, email, productId, version, workspaceName, workspace)
const workspaceInfo = await searchPromise
+
+ notifyHandler?.(workspaceInfo)
+
+ const wsColl = db.collection>(WORKSPACE_COLLECTION)
+
+ async function updateInfo (ops: Partial): Promise {
+ await wsColl.updateOne({ _id: workspaceInfo._id }, { $set: ops })
+ console.log('update', ops)
+ }
+
+ await updateInfo({ createProgress: 10 })
+
let client: Client | undefined
const childLogger = ctx.newChild(
'createWorkspace',
@@ -838,24 +879,54 @@ export async function createWorkspace (
const wsId = getWorkspaceId(workspaceInfo.workspace, productId)
if (initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null) {
// Just any valid model for transactor to be able to function
- await initModel(ctx, getTransactor(), wsId, txes, [], ctxModellogger, true)
+ await (
+ await initModel(ctx, getTransactor(), wsId, txes, [], ctxModellogger, async (value) => {
+ await updateInfo({ createProgress: Math.round((Math.min(value, 100) / 100) * 20) })
+ })
+ ).close()
+ await updateInfo({ createProgress: 20 })
// Clone init workspace.
await cloneWorkspace(
getTransactor(),
getWorkspaceId(initWS, productId),
- getWorkspaceId(workspaceInfo.workspace, productId)
+ getWorkspaceId(workspaceInfo.workspace, productId),
+ true,
+ async (value) => {
+ await updateInfo({ createProgress: 20 + Math.round((Math.min(value, 100) / 100) * 30) })
+ }
)
- client = await upgradeModel(ctx, getTransactor(), wsId, txes, migrationOperation, ctxModellogger)
+ await updateInfo({ createProgress: 50 })
+ client = await upgradeModel(
+ ctx,
+ getTransactor(),
+ wsId,
+ txes,
+ migrationOperation,
+ ctxModellogger,
+ true,
+ async (value) => {
+ await updateInfo({ createProgress: Math.round(50 + (Math.min(value, 100) / 100) * 40) })
+ }
+ )
+ await updateInfo({ createProgress: 90 })
} else {
- client = await initModel(ctx, getTransactor(), wsId, txes, migrationOperation, ctxModellogger)
+ client = await initModel(
+ ctx,
+ getTransactor(),
+ wsId,
+ txes,
+ migrationOperation,
+ ctxModellogger,
+ async (value) => {
+ await updateInfo({ createProgress: Math.round(Math.min(value, 100)) })
+ }
+ )
}
} catch (err: any) {
return { workspaceInfo, err, client: null as any }
}
// Workspace is created, we need to clear disabled flag.
- await db
- .collection>(WORKSPACE_COLLECTION)
- .updateOne({ _id: workspaceInfo._id }, { $set: { disabled: false } })
+ await updateInfo({ createProgress: 100, disabled: false, creating: false })
return { workspaceInfo, client }
})
}
@@ -901,7 +972,16 @@ export async function upgradeWorkspace (
}
)
await (
- await upgradeModel(ctx, getTransactor(), getWorkspaceId(ws.workspace, productId), txes, migrationOperation, logger)
+ await upgradeModel(
+ ctx,
+ getTransactor(),
+ getWorkspaceId(ws.workspace, productId),
+ txes,
+ migrationOperation,
+ logger,
+ false,
+ async (value) => {}
+ )
).close()
return versionStr
}
@@ -933,42 +1013,56 @@ export const createUserWorkspace =
}
}
- const { workspaceInfo, err, client } = await createWorkspace(
- ctx,
- version,
- txes,
- migrationOperation,
- db,
- productId,
- email,
- workspaceName
- )
+ async function doCreate (info: Account, notifyHandler: (workspace: Workspace) => void): Promise {
+ const { workspaceInfo, err, client } = await createWorkspace(
+ ctx,
+ version,
+ txes,
+ migrationOperation,
+ db,
+ productId,
+ email,
+ workspaceName,
+ undefined,
+ notifyHandler
+ )
- if (err != null) {
- await ctx.error('failed to create workspace', { err, workspaceName, email })
- // We need to drop workspace, to prevent wrong data usage.
+ if (err != null) {
+ await ctx.error('failed to create workspace', { err, workspaceName, email })
+ // We need to drop workspace, to prevent wrong data usage.
- await db.collection(WORKSPACE_COLLECTION).updateOne(
- {
- _id: workspaceInfo._id
- },
- { $set: { disabled: true, message: JSON.stringify(err?.message ?? ''), err: JSON.stringify(err) } }
- )
- throw err
- }
- try {
- info.lastWorkspace = Date.now()
-
- // Update last workspace time.
- await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: info._id }, { $set: { lastWorkspace: Date.now() } })
-
- const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
- const shouldUpdateAccount = initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null
- await assignWorkspace(ctx, db, productId, email, workspaceInfo.workspace, shouldUpdateAccount, client)
- await setRole(email, workspaceInfo.workspace, productId, AccountRole.Owner, client)
- } finally {
- await client?.close()
+ await db.collection(WORKSPACE_COLLECTION).updateOne(
+ {
+ _id: workspaceInfo._id
+ },
+ { $set: { disabled: true, message: JSON.stringify(err?.message ?? ''), err: JSON.stringify(err) } }
+ )
+ throw err
+ }
+ try {
+ info.lastWorkspace = Date.now()
+
+ // Update last workspace time.
+ await db.collection(ACCOUNT_COLLECTION).updateOne({ _id: info._id }, { $set: { lastWorkspace: Date.now() } })
+
+ const initWS = getMetadata(toolPlugin.metadata.InitWorkspace)
+ const shouldUpdateAccount = initWS !== undefined && (await getWorkspaceById(db, productId, initWS)) !== null
+ await assignWorkspace(ctx, db, productId, email, workspaceInfo.workspace, shouldUpdateAccount, client)
+ await setRole(email, workspaceInfo.workspace, productId, AccountRole.Owner, client)
+ await ctx.info('Creating server side done', { workspaceName, email })
+ } finally {
+ await client?.close()
+ }
}
+
+ const workspaceInfo = await new Promise((resolve) => {
+ void doCreate(info, (info: Workspace) => {
+ resolve(info)
+ })
+ })
+
+ await assignWorkspaceRaw(db, { account: info, workspace: workspaceInfo })
+
const result = {
endpoint: getEndpoint(),
email,
@@ -976,7 +1070,7 @@ export const createUserWorkspace =
productId,
workspace: workspaceInfo.workspaceUrl
}
- await ctx.info('Creating workspace done', { workspaceName, email })
+ await ctx.info('Creating user side done', { workspaceName, email })
return result
}
@@ -1051,7 +1145,7 @@ export async function getUserWorkspaces (
.find(withProductId(productId, account.admin === true ? {} : { _id: { $in: account.workspaces } }))
.toArray()
)
- .filter((it) => it.disabled !== true)
+ .filter((it) => it.disabled !== true || it.creating === true)
.map(mapToClientWorkspace)
}
@@ -1094,7 +1188,7 @@ export async function getWorkspaceInfo (
const [ws] = (
await db.collection(WORKSPACE_COLLECTION).find(withProductId(productId, query)).toArray()
- ).filter((it) => it.disabled !== true || account?.admin === true)
+ ).filter((it) => it.disabled !== true || account?.admin === true || it.creating === true)
if (ws == null) {
throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
}
@@ -1198,6 +1292,13 @@ export async function assignWorkspace (
}
// Add account into workspace.
+ await assignWorkspaceRaw(db, workspaceInfo)
+
+ await ctx.info('assign-workspace success', { email, workspaceId })
+ return workspaceInfo.workspace
+}
+
+async function assignWorkspaceRaw (db: Db, workspaceInfo: { account: Account, workspace: Workspace }): Promise {
await db
.collection(WORKSPACE_COLLECTION)
.updateOne({ _id: workspaceInfo.workspace._id }, { $addToSet: { accounts: workspaceInfo.account._id } })
@@ -1206,9 +1307,6 @@ export async function assignWorkspace (
await db
.collection(ACCOUNT_COLLECTION)
.updateOne({ _id: workspaceInfo.account._id }, { $addToSet: { workspaces: workspaceInfo.workspace._id } })
-
- await ctx.info('assign-workspace success', { email, workspaceId })
- return workspaceInfo.workspace
}
async function createEmployee (ops: TxOperations, name: string, _email: string): Promise][> {
diff --git a/server/backup/src/index.ts b/server/backup/src/index.ts
index a34d044b1da..28c03c5c2ec 100644
--- a/server/backup/src/index.ts
+++ b/server/backup/src/index.ts
@@ -206,7 +206,8 @@ export async function cloneWorkspace (
transactorUrl: string,
sourceWorkspaceId: WorkspaceId,
targetWorkspaceId: WorkspaceId,
- clearTime: boolean = true
+ clearTime: boolean = true,
+ progress: (value: number) => Promise
): Promise {
const sourceConnection = (await connect(transactorUrl, sourceWorkspaceId, undefined, {
mode: 'backup'
@@ -220,6 +221,7 @@ export async function cloneWorkspace (
.domains()
.filter((it) => it !== DOMAIN_TRANSIENT && it !== DOMAIN_MODEL)
+ let i = 0
for (const c of domains) {
console.log('clone domain...', c)
@@ -322,6 +324,9 @@ export async function cloneWorkspace (
continue
}
}
+
+ i++
+ await progress((100 / domains.length) * i)
}
} catch (err: any) {
console.error(err)
diff --git a/server/tool/src/index.ts b/server/tool/src/index.ts
index f6bbb81d63e..c95a22534b8 100644
--- a/server/tool/src/index.ts
+++ b/server/tool/src/index.ts
@@ -112,8 +112,8 @@ export async function initModel (
rawTxes: Tx[],
migrateOperations: [string, MigrateOperation][],
logger: ModelLogger = consoleModelLogger,
- skipOperations: boolean = false
-): Promise {
+ progress: (value: number) => Promise
+): Promise {
const { mongodbUri, storageAdapter: minio, txes } = prepareTools(rawTxes)
if (txes.some((tx) => tx.objectSpace !== core.space.Model)) {
throw Error('Model txes must target only core.space.Model')
@@ -129,39 +129,48 @@ export async function initModel (
const result = await db.collection(DOMAIN_TX).insertMany(txes as Document[])
logger.log('model transactions inserted.', { count: result.insertedCount })
+ await progress(10)
+
logger.log('creating data...', { transactorUrl })
const { model } = await fetchModelFromMongo(ctx, mongodbUri, workspaceId)
+ await progress(20)
+
logger.log('create minio bucket', { workspaceId })
if (!(await minio.exists(ctx, workspaceId))) {
await minio.make(ctx, workspaceId)
}
- if (!skipOperations) {
- connection = (await connect(
- transactorUrl,
- workspaceId,
- undefined,
- {
- model: 'upgrade',
- admin: 'true'
- },
- model
- )) as unknown as CoreClient & BackupClient
+ connection = (await connect(
+ transactorUrl,
+ workspaceId,
+ undefined,
+ {
+ model: 'upgrade',
+ admin: 'true'
+ },
+ model
+ )) as unknown as CoreClient & BackupClient
- try {
- for (const op of migrateOperations) {
- logger.log('Migrate', { name: op[0] })
- await op[1].upgrade(connection, logger)
- }
-
- // Create update indexes
- await createUpdateIndexes(ctx, connection, db, logger)
- } catch (e: any) {
- logger.error('error', { error: e })
- throw e
+ try {
+ let i = 0
+ for (const op of migrateOperations) {
+ logger.log('Migrate', { name: op[0] })
+ await op[1].upgrade(connection, logger)
+ i++
+ await progress(20 + (((100 / migrateOperations.length) * i) / 100) * 10)
}
- return connection
+ await progress(30)
+
+ // Create update indexes
+ await createUpdateIndexes(ctx, connection, db, logger, async (value) => {
+ await progress(30 + (Math.min(value, 100) / 100) * 70)
+ })
+ await progress(100)
+ } catch (e: any) {
+ logger.error('error', { error: e })
+ throw e
}
+ return connection
} finally {
_client.close()
}
@@ -177,7 +186,8 @@ export async function upgradeModel (
rawTxes: Tx[],
migrateOperations: [string, MigrateOperation][],
logger: ModelLogger = consoleModelLogger,
- skipTxUpdate: boolean = false
+ skipTxUpdate: boolean = false,
+ progress: (value: number) => Promise
): Promise {
const { mongodbUri, txes } = prepareTools(rawTxes)
@@ -193,6 +203,7 @@ export async function upgradeModel (
if (!skipTxUpdate) {
logger.log('removing model...', { workspaceId: workspaceId.name })
+ await progress(10)
// we're preserving accounts (created by core.account.System).
const result = await ctx.with(
'mongo-delete',
@@ -214,16 +225,20 @@ export async function upgradeModel (
)
logger.log('model transactions inserted.', { workspaceId: workspaceId.name, count: insert.insertedCount })
+ await progress(20)
}
const { hierarchy, modelDb, model } = await fetchModelFromMongo(ctx, mongodbUri, workspaceId)
await ctx.with('migrate', {}, async () => {
const migrateClient = new MigrateClientImpl(db, hierarchy, modelDb, logger)
+ let i = 0
for (const op of migrateOperations) {
const t = Date.now()
await op[1].migrate(migrateClient, logger)
logger.log('migrate:', { workspaceId: workspaceId.name, operation: op[0], time: Date.now() - t })
+ await progress(20 + ((100 / migrateOperations.length) * i * 20) / 100)
+ i++
}
})
logger.log('Apply upgrade operations', { workspaceId: workspaceId.name })
@@ -245,16 +260,23 @@ export async function upgradeModel (
)
)
- // Create update indexes
- await ctx.with('create-indexes', {}, async (ctx) => {
- await createUpdateIndexes(ctx, connection, db, logger)
- })
+ if (!skipTxUpdate) {
+ // Create update indexes
+ await ctx.with('create-indexes', {}, async (ctx) => {
+ await createUpdateIndexes(ctx, connection, db, logger, async (value) => {
+ await progress(40 + (Math.min(value, 100) / 100) * 20)
+ })
+ })
+ }
await ctx.with('upgrade', {}, async () => {
+ let i = 0
for (const op of migrateOperations) {
const t = Date.now()
await op[1].upgrade(connection, logger)
logger.log('upgrade:', { operation: op[0], time: Date.now() - t, workspaceId: workspaceId.name })
+ await progress(60 + ((100 / migrateOperations.length) * i * 40) / 100)
+ i++
}
})
return connection
@@ -295,7 +317,8 @@ async function createUpdateIndexes (
ctx: MeasureContext,
connection: CoreClient,
db: Db,
- logger: ModelLogger
+ logger: ModelLogger,
+ progress: (value: number) => Promise
): Promise {
const classes = await ctx.with('find-classes', {}, async () => await connection.findAll(core.class.Class, {}))
@@ -339,6 +362,8 @@ async function createUpdateIndexes (
{},
async () => await db.listCollections({}, { nameOnly: true }).toArray()
)
+ const promises: Promise[] = []
+ let completed = 0
for (const [d, v] of domains.entries()) {
const collInfo = collections.find((it) => it.name === d)
if (collInfo == null) {
@@ -352,13 +377,25 @@ async function createUpdateIndexes (
const name = typeof vv === 'string' ? `${key}_1` : `${key}_${vv[key]}`
const exists = await collection.indexExists(name)
if (!exists) {
- await collection.createIndex(vv)
bb.push(vv)
}
} catch (err: any) {
logger.error('error: failed to create index', { d, vv, err })
}
}
+ for (const vv of bb) {
+ promises.push(
+ collection
+ .createIndex(vv, {
+ background: true
+ })
+ .then(async () => {
+ completed++
+ await progress((100 / bb.length) * completed)
+ })
+ )
+ }
+ await Promise.all(promises)
if (bb.length > 0) {
logger.log('created indexes', { d, bb })
}
diff --git a/server/ws/src/server.ts b/server/ws/src/server.ts
index 8356674838f..b038f14146b 100644
--- a/server/ws/src/server.ts
+++ b/server/ws/src/server.ts
@@ -17,6 +17,7 @@ import core, {
TxFactory,
WorkspaceEvent,
generateId,
+ systemAccountEmail,
toWorkspaceString,
type MeasureContext,
type Ref,
@@ -183,6 +184,7 @@ class TSessionManager implements SessionManager {
workspace: string
workspaceUrl?: string | null
workspaceName?: string
+ creating?: boolean
}> {
const userInfo = await (
await fetch(accounts, {
@@ -219,6 +221,10 @@ class TSessionManager implements SessionManager {
let workspaceInfo = await ctx.with('check-token', {}, async (ctx) =>
accountsUrl !== '' ? await this.getWorkspaceInfo(accountsUrl, rawToken) : this.wsFromToken(token)
)
+ if (workspaceInfo?.creating === true && token.email !== systemAccountEmail) {
+ // No access to workspace for token.
+ return { error: new Error(`Workspace during creation phase ${token.email} ${token.workspace.name}`) }
+ }
if (workspaceInfo === undefined && token.extra?.admin !== 'true') {
// No access to workspace for token.
return { error: new Error(`No access to workspace for token ${token.email} ${token.workspace.name}`) }
@@ -307,6 +313,7 @@ class TSessionManager implements SessionManager {
workspace: string
workspaceUrl?: string | null
workspaceName?: string
+ creating?: boolean
} {
return {
workspace: token.workspace.name,
]