From 9dcec2381526e6cd329ba2962c9d0e191c828412 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Fri, 2 Feb 2024 16:27:27 +0700 Subject: [PATCH] UBERF-5140: Any workspace names (#4489) Signed-off-by: Andrey Sobolev --- README.md | 2 +- dev/client-resources/src/connection.ts | 2 +- dev/docker-compose.yaml | 3 +- dev/server/src/server.ts | 9 +- dev/tool/src/benchmark.ts | 19 +- dev/tool/src/index.ts | 34 +- packages/core/src/client.ts | 2 +- packages/core/src/utils.ts | 14 +- plugins/client-resources/src/index.ts | 61 +-- plugins/login-assets/lang/en.json | 8 +- plugins/login-assets/lang/ru.json | 6 - .../src/components/Confirmation.svelte | 5 +- .../src/components/CreateWorkspaceForm.svelte | 36 +- .../src/components/Form.svelte | 2 +- .../src/components/Join.svelte | 6 +- .../src/components/LoginApp.svelte | 1 + .../src/components/LoginForm.svelte | 120 ++++-- .../src/components/SelectWorkspace.svelte | 7 +- .../src/components/SignupForm.svelte | 3 +- plugins/login-resources/src/plugin.ts | 6 - plugins/login-resources/src/utils.ts | 22 +- plugins/login/src/index.ts | 4 +- .../src/components/Settings.svelte | 1 + .../src/components/Logo.svelte | 2 +- .../src/components/SelectWorkspaceMenu.svelte | 2 +- .../src/components/ServerManager.svelte | 12 +- .../src/components/Workbench.svelte | 14 +- .../components/statistics/MetricsInfo.svelte | 2 +- plugins/workbench-resources/src/connect.ts | 9 +- plugins/workbench-resources/src/utils.ts | 1 + pods/server/src/__start.ts | 9 +- pods/server/src/server.ts | 7 +- scripts/create-workspace.sh | 2 +- server-plugins/chunter-resources/src/index.ts | 2 +- server-plugins/contact-resources/src/index.ts | 4 +- .../inventory-resources/src/index.ts | 2 +- server-plugins/lead-resources/src/index.ts | 2 +- server-plugins/recruit-resources/src/index.ts | 4 +- server-plugins/tracker-resources/src/index.ts | 2 +- .../src/__tests__/account.test_skip.ts | 6 +- server/account/src/index.ts | 361 +++++++++++++----- server/core/src/storage.ts | 5 +- server/core/src/types.ts | 5 +- server/mongo/src/__tests__/storage.test.ts | 2 +- server/ws/src/__tests__/server.test.ts | 6 +- server/ws/src/server.ts | 141 +++++-- server/ws/src/server_http.ts | 46 ++- server/ws/src/types.ts | 16 +- tests/docker-compose.yaml | 3 +- tests/prepare.sh | 4 +- tests/restore-local.sh | 1 + tests/sanity/tests/login.spec.ts | 2 +- 52 files changed, 701 insertions(+), 346 deletions(-) diff --git a/README.md b/README.md index 1c81e2b48b..c09649c217 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Before you can begin, you need to create a workspace and an account and associat ```bash cd ./tool # dev/tool in the repository root -rushx run-local create-workspace ws1 -o DevWorkspace # Create workspace +rushx run-local create-workspace ws1 -w DevWorkspace # Create workspace rushx run-local create-account user1 -p 1234 -f John -l Appleseed # Create account rushx run-local configure ws1 --list --enable '*' # Enable all modules, even if they are not yet intended to be used by a wide audience. rushx run-local assign-workspace user1 ws1 # Assign workspace to user. diff --git a/dev/client-resources/src/connection.ts b/dev/client-resources/src/connection.ts index be4b8a6fa0..62c704348c 100644 --- a/dev/client-resources/src/connection.ts +++ b/dev/client-resources/src/connection.ts @@ -155,7 +155,7 @@ export async function connect (handler: (tx: Tx) => void): Promise { export async function start (port: number, host?: string): Promise { const ctx = new MeasureMetricsContext('server', {}) startJsonRpc(ctx, { - pipelineFactory: (ctx) => { + pipelineFactory: (ctx, workspaceId) => { const conf: DbConfiguration = { domains: { [DOMAIN_TX]: 'InMemoryTx' @@ -74,13 +74,14 @@ export async function start (port: number, host?: string): Promise { } }, defaultContentAdapter: 'default', - workspace: getWorkspaceId('') + workspace: workspaceId } return createPipeline(ctx, conf, [], false, () => {}) }, sessionFactory: (token, pipeline, broadcast) => new ClientSession(broadcast, token, pipeline), port, productId: '', - serverFactory: startHttpServer + serverFactory: startHttpServer, + accountsUrl: '' }) } diff --git a/dev/tool/src/benchmark.ts b/dev/tool/src/benchmark.ts index 04d0a96084..a6341b50e2 100644 --- a/dev/tool/src/benchmark.ts +++ b/dev/tool/src/benchmark.ts @@ -207,14 +207,19 @@ export async function benchmark ( operations = 0 requestTime = 0 transfer = 0 - for (const w of workspaceId) { - const r = extract(json.metrics as Metrics, w.name, 'client', 'handleRequest', 'process', 'find-all') - operations += r?.operations ?? 0 - requestTime += (r?.value ?? 0) / (((r?.operations as number) ?? 0) + 1) + const r = extract( + json.metrics as Metrics, + '🧲 session', + 'client', + 'handleRequest', + 'process', + 'find-all' + ) + operations += r?.operations ?? 0 + requestTime += (r?.value ?? 0) / (((r?.operations as number) ?? 0) + 1) - const tr = extract(json.metrics as Metrics, w.name, 'client', 'handleRequest', '#send-data') - transfer += tr?.value ?? 0 - } + const tr = extract(json.metrics as Metrics, '🧲 session', 'client', 'handleRequest', '#send-data') + transfer += tr?.value ?? 0 }) .catch((err) => { console.log(err) diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index 56fda5fdcc..9f15b4aa47 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -17,20 +17,20 @@ import { ACCOUNT_DB, assignWorkspace, + ClientWorkspaceInfo, confirmEmail, createAcc, createWorkspace, dropAccount, dropWorkspace, getAccount, - getWorkspace, + getWorkspaceById, listAccounts, listWorkspaces, replacePassword, setAccountAdmin, setRole, - upgradeWorkspace, - WorkspaceInfoOnly + upgradeWorkspace } from '@hcengineering/account' import { setMetadata } from '@hcengineering/platform' import { @@ -43,7 +43,7 @@ import { import serverToken, { decodeToken, generateToken } from '@hcengineering/server-token' import toolPlugin, { FileModelLogger } from '@hcengineering/server-tool' -import { program, Command } from 'commander' +import { Command, program } from 'commander' import { Db, MongoClient } from 'mongodb' import { clearTelegramHistory } from './telegram' import { diffWorkspace } from './workspace' @@ -155,7 +155,16 @@ export function devTool ( const { mongodbUri } = prepareTools() await withDatabase(mongodbUri, async (db, client) => { console.log(`assigning user ${email} to ${workspace}...`) - await assignWorkspace(db, productId, email, workspace) + const workspaceInfo = await getWorkspaceById(db, productId, workspace) + if (workspaceInfo === null) { + throw new Error(`workspace ${workspace} not found`) + } + console.log('assigning to workspace', workspaceInfo) + try { + await assignWorkspace(db, productId, email, workspaceInfo?.workspaceUrl ?? workspaceInfo.workspace) + } catch (err: any) { + console.error(err) + } }) }) @@ -197,11 +206,12 @@ export function devTool ( program .command('create-workspace ') .description('create workspace') - .requiredOption('-o, --organization ', 'organization name') + .requiredOption('-w, --workspaceName ', 'Workspace name') + .option('-e, --email ', 'Author email', 'platform@email.com') .action(async (workspace, cmd) => { const { mongodbUri, txes, version, migrateOperations } = prepareTools() await withDatabase(mongodbUri, async (db) => { - await createWorkspace(version, txes, migrateOperations, db, productId, workspace, cmd.organization) + await createWorkspace(version, txes, migrateOperations, db, productId, cmd.email, cmd.workspaceName, workspace) }) }) @@ -230,7 +240,11 @@ export function devTool ( .action(async (workspace, cmd) => { const { mongodbUri, version, txes, migrateOperations } = prepareTools() await withDatabase(mongodbUri, async (db) => { - await upgradeWorkspace(version, txes, migrateOperations, productId, db, workspace) + const info = await getWorkspaceById(db, productId, workspace) + if (info === null) { + throw new Error(`workspace ${workspace} not found`) + } + await upgradeWorkspace(version, txes, migrateOperations, productId, db, info.workspaceUrl ?? info.workspace) }) }) @@ -252,7 +266,7 @@ export function devTool ( const workspaces = await listWorkspaces(db, productId) const withError: string[] = [] - async function _upgradeWorkspace (ws: WorkspaceInfoOnly): Promise { + async function _upgradeWorkspace (ws: ClientWorkspaceInfo): Promise { const t = Date.now() const logger = cmd.console ? consoleModelLogger @@ -298,7 +312,7 @@ export function devTool ( .action(async (workspace, cmd) => { const { mongodbUri } = prepareTools() await withDatabase(mongodbUri, async (db) => { - const ws = await getWorkspace(db, productId, workspace) + const ws = await getWorkspaceById(db, productId, workspace) if (ws === null) { console.log('no workspace exists') return diff --git a/packages/core/src/client.ts b/packages/core/src/client.ts index eaed3821ee..7c5c4793b9 100644 --- a/packages/core/src/client.ts +++ b/packages/core/src/client.ts @@ -229,7 +229,7 @@ export async function createClient ( let lastTx: number function txHandler (tx: Tx): void { - if (tx === null) { + if (tx == null) { return } if (client === null) { diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index bf3e4982ea..c7800f3640 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -45,8 +45,8 @@ function count (): string { * @public * @returns */ -export function generateId (): Ref { - return (timestamp() + random + count()) as Ref +export function generateId (join: string = ''): Ref { + return (timestamp() + join + random + join + count()) as Ref } let currentAccount: Account @@ -89,6 +89,14 @@ export interface WorkspaceId { productId: string } +/** + * @public + */ +export interface WorkspaceIdWithUrl extends WorkspaceId { + workspaceUrl: string + workspaceName: string +} + /** * @public * @@ -507,7 +515,7 @@ export function cutObjectArray (obj: any): any { for (const key of Object.keys(obj)) { if (Array.isArray(obj[key])) { if (obj[key].length > 3) { - Object.assign(r, { [key]: `[${obj[key].slice(0, 3)}, ... and ${obj[key].length - 3} more]` }) + Object.assign(r, { [key]: [...obj[key].slice(0, 3), `... and ${obj[key].length - 3} more`] }) } else Object.assign(r, { [key]: obj[key] }) continue } diff --git a/plugins/client-resources/src/index.ts b/plugins/client-resources/src/index.ts index a4d0a24ed5..dfd592194a 100644 --- a/plugins/client-resources/src/index.ts +++ b/plugins/client-resources/src/index.ts @@ -18,6 +18,7 @@ import core, { AccountClient, ClientConnectEvent, TxHandler, + TxPersistenceStore, TxWorkspaceEvent, WorkspaceEvent, createClient @@ -68,34 +69,7 @@ export default async () => { return connect(url.href, upgradeHandler, onUpgrade, onUnauthorized, onConnect) }, filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined, - { - load: async () => { - if (typeof localStorage !== 'undefined') { - const storedValue = localStorage.getItem('platform.model') ?? null - try { - const model = storedValue != null ? JSON.parse(storedValue) : undefined - if (token !== model?.token) { - return { - full: false, - transactions: [], - hash: [] - } - } - return model.model - } catch {} - } - return { - full: true, - transactions: [], - hash: [] - } - }, - store: async (model) => { - if (typeof localStorage !== 'undefined') { - localStorage.setItem('platform.model', JSON.stringify({ token, model })) - } - } - } + createModelPersistence(token) ) // Check if we had dev hook for client. client = hookClient(client) @@ -104,6 +78,37 @@ export default async () => { } } } +function createModelPersistence (token: string): TxPersistenceStore | undefined { + return { + load: async () => { + if (typeof localStorage !== 'undefined') { + const storedValue = localStorage.getItem('platform.model') ?? null + try { + const model = storedValue != null ? JSON.parse(storedValue) : undefined + if (token !== model?.token) { + return { + full: false, + transactions: [], + hash: [] + } + } + return model.model + } catch {} + } + return { + full: true, + transactions: [], + hash: [] + } + }, + store: async (model) => { + if (typeof localStorage !== 'undefined') { + localStorage.setItem('platform.model', JSON.stringify({ token, model })) + } + } + } +} + async function hookClient (client: Promise): Promise { const hook = getMetadata(clientPlugin.metadata.ClientHook) if (hook !== undefined) { diff --git a/plugins/login-assets/lang/en.json b/plugins/login-assets/lang/en.json index 182a0aa9db..650da31d5c 100644 --- a/plugins/login-assets/lang/en.json +++ b/plugins/login-assets/lang/en.json @@ -15,7 +15,7 @@ "Join": "Join", "Email": "Email", "Password": "Password", - "Workspace": "Workspace", + "Workspace": "Workspace name", "DoNotHaveAnAccount": "Do not have an account?", "PasswordRepeat": "Repeat password", "HaveAccount": "Already have an account?", @@ -27,12 +27,6 @@ "WantAnotherWorkspace": "Want to create another workspace?", "ChangeAccount": "Change account", "NotSeeingWorkspace": "Not seeing your workspace?", - "WorkspaceNameRule": "Workspace name cannot contain special characters except -", - "WorkspaceNameRuleCapital": "The workspace name must contain only lowercase letters", - "WorkspaceNameRuleHyphen": "The workspace name cannot start with a dash (-)", - "WorkspaceNameRuleHyphenEnd": "The workspace name cannot end with a dash (-)", - "WorkspaceNameRuleLengthLow": "Workspace name must be at least 3 characters", - "WorkspaceNameRuleLengthHigh": "Workspace name must be no longer than 63 characters", "ForgotPassword": "Forgot your password?", "KnowPassword": "Know your password?", "Recover": "Recover", diff --git a/plugins/login-assets/lang/ru.json b/plugins/login-assets/lang/ru.json index 4055db9dd7..37bdf459f1 100644 --- a/plugins/login-assets/lang/ru.json +++ b/plugins/login-assets/lang/ru.json @@ -27,12 +27,6 @@ "WantAnotherWorkspace": "Хотите создать другое рабочее пространство?", "ChangeAccount": "Сменить пользователя", "NotSeeingWorkspace": "Не видите ваше рабочее пространство?", - "WorkspaceNameRule": "Название рабочего пространства не может содержать специальные символы кроме -", - "WorkspaceNameRuleCapital": "Название рабочего пространства должно содержать только строчные буквы", - "WorkspaceNameRuleHyphen": "Название рабочего пространства не может начинаться с символа 'тире' (-)", - "WorkspaceNameRuleHyphenEnd": "Название рабочего пространства не может заканчиваться символом 'тире' (-)", - "WorkspaceNameRuleLengthLow": "Название рабочего пространства должно быть не короче 3 символов", - "WorkspaceNameRuleLengthHigh": "Название рабочего пространства должно быть не длиннее 63 символов", "ForgotPassword": "Забыли пароль?", "KnowPassword": "Знаете пароль?", "Recover": "Восстановить", diff --git a/plugins/login-resources/src/components/Confirmation.svelte b/plugins/login-resources/src/components/Confirmation.svelte index 121e5610db..43a250ed94 100644 --- a/plugins/login-resources/src/components/Confirmation.svelte +++ b/plugins/login-resources/src/components/Confirmation.svelte @@ -31,7 +31,7 @@ navigate(loc) } - function goToLogin () { + function goToLogin (): void { const loc = getCurrentLocation() loc.query = undefined loc.path[1] = 'login' @@ -39,7 +39,7 @@ navigate(loc) } - async function check () { + async function check (): Promise { const location = getCurrentLocation() if (location.query?.id === undefined || location.query?.id === null) return status = new Status(Severity.INFO, login.status.ConnectingToServer, {}) @@ -50,6 +50,7 @@ if (result !== undefined) { setMetadata(presentation.metadata.Token, result.token) + setMetadataLocalStorage(login.metadata.LastToken, result.token) setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint) setMetadataLocalStorage(login.metadata.LoginEmail, result.email) goToWorkspaces() diff --git a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte index 3e21a71aa2..11c384111d 100644 --- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte +++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte @@ -28,38 +28,7 @@ { name: 'workspace', i18n: login.string.Workspace, - rules: [ - { - rule: /^-/, - notMatch: true, - ruleDescr: login.string.WorkspaceNameRuleHyphen - }, - { - rule: /-$/, - notMatch: true, - ruleDescr: login.string.WorkspaceNameRuleHyphenEnd - }, - { - rule: /[A-Z]/, - notMatch: true, - ruleDescr: login.string.WorkspaceNameRuleCapital - }, - { - rule: /^[0-9a-z-]+$/, - notMatch: false, - ruleDescr: login.string.WorkspaceNameRule - }, - { - rule: /^[0-9a-z-]{3,}$/, - notMatch: false, - ruleDescr: login.string.WorkspaceNameRuleLengthLow - }, - { - rule: /^[0-9a-z-]{3,63}$/, - notMatch: false, - ruleDescr: login.string.WorkspaceNameRuleLengthHigh - } - ] + rules: [] } ] @@ -89,12 +58,13 @@ if (result !== undefined) { setMetadata(presentation.metadata.Token, result.token) + setMetadataLocalStorage(login.metadata.LastToken, result.token) const tokens: Record = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {} tokens[object.workspace] = result.token setMetadataLocalStorage(login.metadata.LoginTokens, tokens) setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint) setMetadataLocalStorage(login.metadata.LoginEmail, result.email) - navigate({ path: [workbenchId, object.workspace] }) + navigate({ path: [workbenchId, result.workspace] }) } } } diff --git a/plugins/login-resources/src/components/Form.svelte b/plugins/login-resources/src/components/Form.svelte index d46a4f19d3..ca0fac03bb 100644 --- a/plugins/login-resources/src/components/Form.svelte +++ b/plugins/login-resources/src/components/Form.svelte @@ -72,7 +72,7 @@ for (const field of fields) { const v = object[field.name] const f = field - if (!f.optional && (!v || v === '')) { + if (!f.optional && (!v || v.trim() === '')) { status = new Status(Severity.INFO, login.status.RequiredField, { field: await translate(field.i18n, {}, language) }) diff --git a/plugins/login-resources/src/components/Join.svelte b/plugins/login-resources/src/components/Join.svelte index 51b7b67445..06ad18761a 100644 --- a/plugins/login-resources/src/components/Join.svelte +++ b/plugins/login-resources/src/components/Join.svelte @@ -75,6 +75,7 @@ if (result !== undefined) { setMetadata(presentation.metadata.Token, result.token) + setMetadataLocalStorage(login.metadata.LastToken, result.token) const tokens: Record = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {} tokens[result.workspace] = result.token setMetadataLocalStorage(login.metadata.LoginTokens, tokens) @@ -128,10 +129,10 @@ } onMount(() => { - check() + void check() }) - async function check () { + async function check (): Promise { if (location.query?.inviteId === undefined || location.query?.inviteId === null) return status = new Status(Severity.INFO, login.status.ConnectingToServer, {}) const [, result] = await checkJoined(location.query.inviteId) @@ -139,6 +140,7 @@ if (result !== undefined) { const tokens: Record = fetchMetadataLocalStorage(login.metadata.LoginTokens) ?? {} setMetadata(presentation.metadata.Token, result.token) + setMetadataLocalStorage(login.metadata.LastToken, result.token) tokens[result.workspace] = result.token setMetadataLocalStorage(login.metadata.LoginTokens, tokens) setMetadataLocalStorage(login.metadata.LoginEndpoint, result.endpoint) diff --git a/plugins/login-resources/src/components/LoginApp.svelte b/plugins/login-resources/src/components/LoginApp.svelte index 56228b0172..afaeb9546a 100644 --- a/plugins/login-resources/src/components/LoginApp.svelte +++ b/plugins/login-resources/src/components/LoginApp.svelte @@ -63,6 +63,7 @@ onDestroy( location.subscribe((loc) => { void (async (loc) => { + token = getMetadata(presentation.metadata.Token) page = loc.path[1] ?? (token ? 'selectWorkspace' : 'login') if (!pages.includes(page)) { page = 'login' diff --git a/plugins/login-resources/src/components/LoginForm.svelte b/plugins/login-resources/src/components/LoginForm.svelte index d3866d0092..3f06c662a2 100644 --- a/plugins/login-resources/src/components/LoginForm.svelte +++ b/plugins/login-resources/src/components/LoginForm.svelte @@ -14,13 +14,21 @@ // limitations under the License. -->
- +
+
+ {account?.email} +
@@ -126,7 +129,7 @@ class="workspace flex-center fs-title cursor-pointer focused-button bordered form-row" on:click={() => select(workspace.workspace)} > - {workspace.workspace} + {workspace.workspaceName ?? workspace.workspace}
{/each} {#if workspaces.length === 0 && account?.confirmed === true} diff --git a/plugins/login-resources/src/components/SignupForm.svelte b/plugins/login-resources/src/components/SignupForm.svelte index 102c4c5bc2..f91d0bd3a2 100644 --- a/plugins/login-resources/src/components/SignupForm.svelte +++ b/plugins/login-resources/src/components/SignupForm.svelte @@ -16,7 +16,7 @@ - +