From 639df5b352be8179c5c93bc608ee9a206391c29b Mon Sep 17 00:00:00 2001 From: Chunosov Date: Wed, 11 Dec 2024 00:29:16 +0700 Subject: [PATCH 1/6] Remove duplicated column, fix adding surveys to vacancies (#7422) * hide second company column in vacancies viewlet Signed-off-by: Nikolay Chunosov * fix adding surveys to vacancy Signed-off-by: Nikolay Chunosov --------- Signed-off-by: Nikolay Chunosov --- models/recruit/src/index.ts | 2 +- .../src/components/EditVacancy.svelte | 10 +++++++++- .../src/components/PollCollection.svelte | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/models/recruit/src/index.ts b/models/recruit/src/index.ts index 6b8e3f5dfd..a8e2857cd4 100644 --- a/models/recruit/src/index.ts +++ b/models/recruit/src/index.ts @@ -355,7 +355,7 @@ export function createModel (builder: Builder): void { } ], configOptions: { - hiddenKeys: ['name', 'space', 'modifiedOn'], + hiddenKeys: ['name', 'space', 'modifiedOn', 'company'], sortable: true }, viewOptions: { diff --git a/plugins/recruit-resources/src/components/EditVacancy.svelte b/plugins/recruit-resources/src/components/EditVacancy.svelte index b43f63d082..5dea25f030 100644 --- a/plugins/recruit-resources/src/components/EditVacancy.svelte +++ b/plugins/recruit-resources/src/components/EditVacancy.svelte @@ -208,7 +208,15 @@
- +
diff --git a/plugins/survey-resources/src/components/PollCollection.svelte b/plugins/survey-resources/src/components/PollCollection.svelte index 696624fc22..b81c391ca6 100644 --- a/plugins/survey-resources/src/components/PollCollection.svelte +++ b/plugins/survey-resources/src/components/PollCollection.svelte @@ -47,7 +47,7 @@ const client = getClient() const pollId = await client.addCollection(survey.class.Poll, space, objectId, _class, 'polls', makePollData(source)) - const poll = await client.findOne(survey.class.Survey, { _id: pollId }) + const poll = await client.findOne(survey.class.Poll, { _id: pollId }) if (poll === undefined) { console.error(`Could not find just created poll ${pollId}.`) return From 896c611ccacd8830223fe612ff0fdbb7b5628727 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 11 Dec 2024 11:16:01 +0700 Subject: [PATCH 2/6] tool: restore controlled docs content (#7423) Signed-off-by: Alexander Onnikov --- dev/tool/package.json | 2 + dev/tool/src/index.ts | 60 ++++++++++++++++- dev/tool/src/markup.ts | 124 ++++++++++++++++++++++++++++++++++- models/core/src/migration.ts | 12 +--- 4 files changed, 186 insertions(+), 12 deletions(-) diff --git a/dev/tool/package.json b/dev/tool/package.json index 47c913a296..120d8723d6 100644 --- a/dev/tool/package.json +++ b/dev/tool/package.json @@ -67,6 +67,7 @@ "@hcengineering/client-resources": "^0.6.27", "@hcengineering/contact": "^0.6.24", "@hcengineering/core": "^0.6.32", + "@hcengineering/controlled-documents": "^0.1.0", "@hcengineering/document": "^0.6.0", "@hcengineering/elastic": "^0.6.0", "@hcengineering/lead": "^0.6.0", @@ -75,6 +76,7 @@ "@hcengineering/model-all": "^0.6.0", "@hcengineering/model-attachment": "^0.6.0", "@hcengineering/model-contact": "^0.6.1", + "@hcengineering/model-controlled-documents": "^0.1.0", "@hcengineering/model-document": "^0.6.0", "@hcengineering/model-recruit": "^0.6.0", "@hcengineering/model-telegram": "^0.6.0", diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index cb53d67e7b..882a2a14a2 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -122,7 +122,7 @@ import { copyToDatalake, moveFiles, showLostFiles } from './storage' import { getModelVersion } from '@hcengineering/model-all' import { type DatalakeConfig, DatalakeService, createDatalakeClient } from '@hcengineering/datalake' import { S3Service, type S3Config } from '@hcengineering/s3' -import { restoreWikiContentMongo } from './markup' +import { restoreControlledDocContentMongo, restoreWikiContentMongo } from './markup' const colorConstants = { colorRed: '\u001b[31m', @@ -1263,6 +1263,8 @@ export function devTool ( .sort((a, b) => b.lastVisit - a.lastVisit) }) + console.log('found workspaces', workspaces.length) + await withStorage(async (storageAdapter) => { await withDatabase(dbUrl, async (db) => { const mongodbUri = getMongoDBUrl() @@ -1293,6 +1295,62 @@ export function devTool ( }) }) + program + .command('restore-controlled-content-mongo') + .description('restore controlled document contents') + .option('-w, --workspace ', 'Selected workspace only', '') + .option('-d, --dryrun', 'Dry run', false) + .option('-f, --force', 'Force update', false) + .action(async (cmd: { workspace: string, dryrun: boolean, force: 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) + }) + + console.log('found workspaces', workspaces.length) + + 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 (!cmd.force && (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 restoreControlledDocContentMongo(toolCtx, wsDb, workspaceId, storageAdapter, params) + } + } finally { + client.close() + } + }) + }) + }) + program .command('confirm-email ') .description('confirm user email') diff --git a/dev/tool/src/markup.ts b/dev/tool/src/markup.ts index 4712f2ad31..6aa9e1b1a6 100644 --- a/dev/tool/src/markup.ts +++ b/dev/tool/src/markup.ts @@ -14,9 +14,21 @@ // import { loadCollabYdoc, saveCollabYdoc, yDocCopyXmlField } from '@hcengineering/collaboration' -import { type MeasureContext, type WorkspaceId, makeCollabYdocId } from '@hcengineering/core' +import core, { + type Blob, + type Doc, + type MeasureContext, + type Ref, + type TxCreateDoc, + type WorkspaceId, + DOMAIN_TX, + makeCollabYdocId, + makeDocCollabId +} from '@hcengineering/core' import document, { type Document } from '@hcengineering/document' +import documents from '@hcengineering/controlled-documents' import { DOMAIN_DOCUMENT } from '@hcengineering/model-document' +import { DOMAIN_DOCUMENTS } from '@hcengineering/model-controlled-documents' import { type StorageAdapter } from '@hcengineering/server-core' import { type Db } from 'mongodb' @@ -43,7 +55,7 @@ export async function restoreWikiContentMongo ( try { while (true) { const doc = await iterator.next() - if (doc === null) return + if (doc === null) break processedCnt++ if (processedCnt % 100 === 0) { @@ -88,3 +100,111 @@ export async function restoreWikiContentMongo ( await iterator.close() } } + +export interface RestoreControlledDocContentParams { + dryRun: boolean +} + +export async function restoreControlledDocContentMongo ( + ctx: MeasureContext, + db: Db, + workspaceId: WorkspaceId, + storageAdapter: StorageAdapter, + params: RestoreWikiContentParams +): Promise { + const iterator = db.collection(DOMAIN_DOCUMENTS).find({ + _class: { + $in: [documents.class.ControlledDocument, documents.class.ControlledDocumentSnapshot] + } + }) + + 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) break + + const restored = await restoreControlledDocContentForDoc( + ctx, + db, + workspaceId, + storageAdapter, + params, + doc, + 'content' + ) + if (restored) { + restoredCnt++ + } + + processedCnt++ + if (processedCnt % 100 === 0) { + printStats() + } + } + } finally { + printStats() + await iterator.close() + } +} + +export async function restoreControlledDocContentForDoc ( + ctx: MeasureContext, + db: Db, + workspaceId: WorkspaceId, + storageAdapter: StorageAdapter, + params: RestoreWikiContentParams, + doc: Doc, + attribute: string +): Promise { + const tx = await db.collection>(DOMAIN_TX).findOne({ + _class: core.class.TxCreateDoc, + objectId: doc._id, + objectClass: doc._class + }) + + // It is expected that tx contains attribute with content in old collaborative doc format + // the original value here looks like '65b7f82f4d422b89d4cbdd6f:HEAD:0' + const attribures = tx?.attributes ?? {} + const value = (attribures as any)[attribute] as string + if (value == null || !value.includes(':')) { + console.log('no content to restore', doc._class, doc._id) + return false + } + + const currentYdocId = value.split(':')[0] as Ref + const ydocId = makeCollabYdocId(makeDocCollabId(doc, attribute)) + + // Ensure that we don't have new content in storage + const stat = await storageAdapter.stat(ctx, workspaceId, ydocId) + if (stat !== undefined) { + console.log('content already restored', doc._class, doc._id, ydocId) + return false + } + + console.log('restoring content', doc._id, currentYdocId, '-->', ydocId) + if (!params.dryRun) { + try { + const stat = await storageAdapter.stat(ctx, workspaceId, currentYdocId) + if (stat === undefined) { + console.log('no content to restore', doc._class, doc._id, ydocId) + return false + } + + const data = await storageAdapter.read(ctx, workspaceId, currentYdocId) + const buffer = Buffer.concat(data as any) + await storageAdapter.put(ctx, workspaceId, ydocId, buffer, 'application/ydoc', buffer.length) + } catch (err: any) { + console.error('failed to restore content for', doc._class, doc._id, err) + return false + } + } + + return true +} diff --git a/models/core/src/migration.ts b/models/core/src/migration.ts index f922129f28..5ece09f075 100644 --- a/models/core/src/migration.ts +++ b/models/core/src/migration.ts @@ -376,15 +376,9 @@ async function processMigrateJsonForDoc ( await retry(5, async () => { const stat = await storageAdapter.stat(ctx, workspaceId, currentYdocId) if (stat !== undefined) { - const buffer = await storageAdapter.read(ctx, workspaceId, currentYdocId) - await storageAdapter.put( - ctx, - workspaceId, - ydocId, - Buffer.concat(buffer as any), - 'application/ydoc', - buffer.length - ) + const data = await storageAdapter.read(ctx, workspaceId, currentYdocId) + const buffer = Buffer.concat(data as any) + await storageAdapter.put(ctx, workspaceId, ydocId, buffer, 'application/ydoc', buffer.length) } }) } From b4bf2dc0bf70a3d75a4374dda6a884acb0e6edea Mon Sep 17 00:00:00 2001 From: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:47:12 +0530 Subject: [PATCH 3/6] fix: content shift on select workspace page (#7424) Signed-off-by: Dakshesh Jain --- plugins/login-assets/lang/cs.json | 1 + plugins/login-assets/lang/en.json | 1 + plugins/login-assets/lang/es.json | 1 + plugins/login-assets/lang/fr.json | 1 + plugins/login-assets/lang/it.json | 1 + plugins/login-assets/lang/pt.json | 1 + plugins/login-assets/lang/ru.json | 1 + plugins/login-assets/lang/zh.json | 1 + .../src/components/SelectWorkspace.svelte | 20 +++++++++++++++++-- plugins/login-resources/src/plugin.ts | 1 + 10 files changed, 27 insertions(+), 2 deletions(-) diff --git a/plugins/login-assets/lang/cs.json b/plugins/login-assets/lang/cs.json index 5a688b19ba..b3f3ebf249 100644 --- a/plugins/login-assets/lang/cs.json +++ b/plugins/login-assets/lang/cs.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Nemáte účet?", "PasswordRepeat": "Zopakujte heslo", "HaveAccount": "Již máte účet?", + "LoadingAccount": "Načítání...", "SelectWorkspace": "Vyberte pracovní prostor", "Copy": "Kopírovat", "Copied": "Zkopírováno", diff --git a/plugins/login-assets/lang/en.json b/plugins/login-assets/lang/en.json index e627fc91d8..efd8b57ede 100644 --- a/plugins/login-assets/lang/en.json +++ b/plugins/login-assets/lang/en.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Do not have an account?", "PasswordRepeat": "Repeat password", "HaveAccount": "Already have an account?", + "LoadingAccount": "Loading...", "SelectWorkspace": "Select workspace", "Copy": "Copy", "Copied": "Copied", diff --git a/plugins/login-assets/lang/es.json b/plugins/login-assets/lang/es.json index aa22e6039d..8dd31dde43 100644 --- a/plugins/login-assets/lang/es.json +++ b/plugins/login-assets/lang/es.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "¿No tienes una cuenta?", "PasswordRepeat": "Repetir contraseña", "HaveAccount": "¿Ya tienes una cuenta?", + "LoadingAccount": "Cargando...", "SelectWorkspace": "Seleccionar espacio de trabajo", "Copy": "Copiar", "Copied": "Copiado", diff --git a/plugins/login-assets/lang/fr.json b/plugins/login-assets/lang/fr.json index f9831f3053..b442b854c1 100644 --- a/plugins/login-assets/lang/fr.json +++ b/plugins/login-assets/lang/fr.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Vous n'avez pas de compte ?", "PasswordRepeat": "Répétez le mot de passe", "HaveAccount": "Vous avez déjà un compte ?", + "LoadingAccount": "Chargement...", "SelectWorkspace": "Sélectionner un espace de travail", "Copy": "Copier", "Copied": "Copié", diff --git a/plugins/login-assets/lang/it.json b/plugins/login-assets/lang/it.json index 1ee337f375..9f59687b01 100644 --- a/plugins/login-assets/lang/it.json +++ b/plugins/login-assets/lang/it.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Non hai un account?", "PasswordRepeat": "Ripeti password", "HaveAccount": "Hai già un account?", + "LoadingAccount": "Caricamento...", "SelectWorkspace": "Seleziona spazio di lavoro", "Copy": "Copia", "Copied": "Copiato", diff --git a/plugins/login-assets/lang/pt.json b/plugins/login-assets/lang/pt.json index 244e29c3d1..754b9f81a4 100644 --- a/plugins/login-assets/lang/pt.json +++ b/plugins/login-assets/lang/pt.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Não tem uma conta?", "PasswordRepeat": "Repetir palavra-passe", "HaveAccount": "Já tem uma conta?", + "LoadingAccount": "Carregando...", "SelectWorkspace": "Selecionar espaço de trabalho", "Copy": "Copiar", "Copied": "Copiado", diff --git a/plugins/login-assets/lang/ru.json b/plugins/login-assets/lang/ru.json index 8112290770..0d49a61cbf 100644 --- a/plugins/login-assets/lang/ru.json +++ b/plugins/login-assets/lang/ru.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "Нет учетной записи?", "PasswordRepeat": "Повторите пароль", "HaveAccount": "Уже есть учетная запись?", + "LoadingAccount": "Загрузка...", "SelectWorkspace": "Выбрать рабочее пространство", "Copy": "Копировать", "Copied": "Скопировано", diff --git a/plugins/login-assets/lang/zh.json b/plugins/login-assets/lang/zh.json index 9c7a2ca8f0..e5c44c87d6 100644 --- a/plugins/login-assets/lang/zh.json +++ b/plugins/login-assets/lang/zh.json @@ -19,6 +19,7 @@ "DoNotHaveAnAccount": "还没有账户?", "PasswordRepeat": "重复密码", "HaveAccount": "已经有账户了?", + "LoadingAccount": "加载中...", "SelectWorkspace": "选择工作区", "Copy": "复制", "Copied": "已复制", diff --git a/plugins/login-resources/src/components/SelectWorkspace.svelte b/plugins/login-resources/src/components/SelectWorkspace.svelte index 7303da3c07..d8a47cf7ca 100644 --- a/plugins/login-resources/src/components/SelectWorkspace.svelte +++ b/plugins/login-resources/src/components/SelectWorkspace.svelte @@ -20,6 +20,7 @@ import { Button, Label, + Spinner, Scroller, SearchEdit, deviceOptionsStore as deviceInfo, @@ -95,7 +96,11 @@
- {account?.email} + {#if account?.email} + {account.email} + {:else} +
@@ -106,7 +111,11 @@
{/if} - {#await _getWorkspaces() then} + {#await _getWorkspaces()} +
+ +
+ {:then}
{#each workspaces @@ -207,6 +216,13 @@ flex-grow: 1; overflow: hidden; + .workspace-loader { + height: 100%; + display: flex; + justify-content: center; + align-items: center; + } + .title { font-weight: 600; font-size: 1.5rem; diff --git a/plugins/login-resources/src/plugin.ts b/plugins/login-resources/src/plugin.ts index d9b9c94ecf..9647fe18e1 100644 --- a/plugins/login-resources/src/plugin.ts +++ b/plugins/login-resources/src/plugin.ts @@ -32,6 +32,7 @@ export default mergeIds(loginId, login, { LastName: '' as IntlString, FirstName: '' as IntlString, HaveAccount: '' as IntlString, + LoadingAccount: '' as IntlString, Join: '' as IntlString, Email: '' as IntlString, Password: '' as IntlString, From e39094b3fe08a80303b1f82ed5bb5a48a39a69b6 Mon Sep 17 00:00:00 2001 From: Kristina Date: Wed, 11 Dec 2024 12:49:55 +0400 Subject: [PATCH 4/6] Migrate multiple ai employees (#7398) Signed-off-by: Kristina Fefelova --- models/ai-bot/package.json | 2 + models/ai-bot/src/index.ts | 1 + models/ai-bot/src/migration.ts | 86 ++++++++++++++++++++ models/all/src/migration.ts | 4 +- services/ai-bot/pod-ai-bot/src/controller.ts | 48 ++++++----- 5 files changed, 119 insertions(+), 22 deletions(-) create mode 100644 models/ai-bot/src/migration.ts diff --git a/models/ai-bot/package.json b/models/ai-bot/package.json index fe96dbca4c..578c28fed5 100644 --- a/models/ai-bot/package.json +++ b/models/ai-bot/package.json @@ -31,8 +31,10 @@ "@hcengineering/ai-bot": "^0.6.0", "@hcengineering/analytics-collector": "^0.6.0", "@hcengineering/chunter": "^0.6.20", + "@hcengineering/contact": "^0.6.24", "@hcengineering/core": "^0.6.32", "@hcengineering/model": "^0.6.11", + "@hcengineering/model-contact": "^0.6.1", "@hcengineering/model-core": "^0.6.0", "@hcengineering/model-view": "^0.6.0", "@hcengineering/platform": "^0.6.11", diff --git a/models/ai-bot/src/index.ts b/models/ai-bot/src/index.ts index 7393934da5..9d5d87ba5a 100644 --- a/models/ai-bot/src/index.ts +++ b/models/ai-bot/src/index.ts @@ -21,6 +21,7 @@ import analyticsCollector from '@hcengineering/analytics-collector' import aiBot from './plugin' export { aiBotId } from '@hcengineering/ai-bot' +export { aiBotOperation } from './migration' export default aiBot export const DOMAIN_AI_BOT = 'ai_bot' as Domain diff --git a/models/ai-bot/src/migration.ts b/models/ai-bot/src/migration.ts new file mode 100644 index 0000000000..e54aca4b2d --- /dev/null +++ b/models/ai-bot/src/migration.ts @@ -0,0 +1,86 @@ +// +// 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 contact, { type Channel, type Person, type PersonAccount } from '@hcengineering/contact' +import core, { + DOMAIN_MODEL_TX, + type TxCUD, + type TxCreateDoc, + type Ref, + type TxUpdateDoc, + TxProcessor, + type Domain +} from '@hcengineering/core' +import { DOMAIN_CHANNEL, DOMAIN_CONTACT } from '@hcengineering/model-contact' +import { + tryMigrate, + type MigrateOperation, + type MigrationClient, + type MigrationUpgradeClient +} from '@hcengineering/model' +import aiBot, { aiBotId } from '@hcengineering/ai-bot' + +const DOMAIN_ACTIVITY = 'activity' as Domain + +async function migrateAiExtraAccounts (client: MigrationClient): Promise { + const currentAccount = ( + await client.model.findAll(contact.class.PersonAccount, { _id: aiBot.account.AIBot as Ref }) + )[0] + if (currentAccount === undefined) return + + const txes = await client.find>(DOMAIN_MODEL_TX, { + _class: { $in: [core.class.TxCreateDoc, core.class.TxUpdateDoc] }, + objectClass: contact.class.PersonAccount, + objectId: aiBot.account.AIBot as Ref + }) + + const personsToDelete: Ref[] = [] + const txesToDelete: Ref>[] = [] + + for (const tx of txes) { + if (tx._class === core.class.TxCreateDoc) { + const acc = TxProcessor.createDoc2Doc(tx as TxCreateDoc) + if (acc.person !== currentAccount.person) { + personsToDelete.push(acc.person) + txesToDelete.push(tx._id) + } + } else if (tx._class === core.class.TxUpdateDoc) { + const person = (tx as TxUpdateDoc).operations.person + if (person !== undefined && person !== currentAccount.person) { + personsToDelete.push(person) + txesToDelete.push(tx._id) + } + } + } + + if (personsToDelete.length === 0) return + + await client.deleteMany(DOMAIN_MODEL_TX, { _id: { $in: txesToDelete } }) + await client.deleteMany(DOMAIN_ACTIVITY, { attachedTo: { $in: personsToDelete } }) + await client.deleteMany(DOMAIN_CHANNEL, { attachedTo: { $in: personsToDelete } }) + await client.deleteMany(DOMAIN_CONTACT, { _id: { $in: personsToDelete } }) +} + +export const aiBotOperation: MigrateOperation = { + async migrate (client: MigrationClient): Promise { + await tryMigrate(client, aiBotId, [ + { + state: 'remove-ai-bot-extra-accounts-v100', + func: migrateAiExtraAccounts + } + ]) + }, + async upgrade (state: Map>, client: () => Promise): Promise {} +} diff --git a/models/all/src/migration.ts b/models/all/src/migration.ts index e3f441eaf6..551c02407e 100644 --- a/models/all/src/migration.ts +++ b/models/all/src/migration.ts @@ -54,6 +54,7 @@ import { analyticsCollectorOperation } from '@hcengineering/model-analytics-coll import { workbenchOperation } from '@hcengineering/model-workbench' import { testManagementOperation } from '@hcengineering/model-test-management' import { surveyOperation } from '@hcengineering/model-survey' +import { aiBotId, aiBotOperation } from '@hcengineering/model-ai-bot' export const migrateOperations: [string, MigrateOperation][] = [ ['core', coreOperation], @@ -96,5 +97,6 @@ export const migrateOperations: [string, MigrateOperation][] = [ ['analyticsCollector', analyticsCollectorOperation], ['workbench', workbenchOperation], ['testManagement', testManagementOperation], - ['survey', surveyOperation] + ['survey', surveyOperation], + [aiBotId, aiBotOperation] ] diff --git a/services/ai-bot/pod-ai-bot/src/controller.ts b/services/ai-bot/pod-ai-bot/src/controller.ts index a8f4ecf310..6ddf93a004 100644 --- a/services/ai-bot/pod-ai-bot/src/controller.ts +++ b/services/ai-bot/pod-ai-bot/src/controller.ts @@ -51,7 +51,7 @@ const CLOSE_INTERVAL_MS = 10 * 60 * 1000 // 10 minutes export class AIControl { private readonly workspaces: Map = new Map() private readonly closeWorkspaceTimeouts: Map = new Map() - private readonly connectingWorkspaces: Set = new Set() + private readonly connectingWorkspaces = new Map>() readonly aiClient?: OpenAI readonly encoding = encodingForModel(config.OpenAIModel) @@ -69,7 +69,6 @@ export class AIControl { baseURL: config.OpenAIBaseUrl === '' ? undefined : config.OpenAIBaseUrl }) : undefined - void this.connectSupportWorkspace() } @@ -78,11 +77,9 @@ export class AIControl { } async connectSupportWorkspace (): Promise { - if (this.supportClient === undefined && !this.connectingWorkspaces.has(config.SupportWorkspace)) { - this.connectingWorkspaces.add(config.SupportWorkspace) + if (this.supportClient === undefined) { const record = await this.getWorkspaceRecord(config.SupportWorkspace) this.supportClient = (await this.createWorkspaceClient(config.SupportWorkspace, record)) as SupportWsClient - this.connectingWorkspaces.delete(config.SupportWorkspace) } } @@ -138,27 +135,36 @@ export class AIControl { if (workspace === config.SupportWorkspace) { return } - this.connectingWorkspaces.add(workspace) - if (!this.workspaces.has(workspace)) { - const record = await this.getWorkspaceRecord(workspace) - const client = await this.createWorkspaceClient(workspace, record) - if (client === undefined) { + if (this.connectingWorkspaces.has(workspace)) { + return await this.connectingWorkspaces.get(workspace) + } + + const initPromise = (async () => { + try { + if (!this.workspaces.has(workspace)) { + const record = await this.getWorkspaceRecord(workspace) + const client = await this.createWorkspaceClient(workspace, record) + if (client === undefined) { + return + } + this.workspaces.set(workspace, client) + } + + const timeoutId = this.closeWorkspaceTimeouts.get(workspace) + if (timeoutId !== undefined) { + clearTimeout(timeoutId) + } + + this.updateClearInterval(workspace) + } finally { this.connectingWorkspaces.delete(workspace) - return } + })() - this.workspaces.set(workspace, client) - } + this.connectingWorkspaces.set(workspace, initPromise) - const timeoutId = this.closeWorkspaceTimeouts.get(workspace) - - if (timeoutId !== undefined) { - clearTimeout(timeoutId) - } - - this.updateClearInterval(workspace) - this.connectingWorkspaces.delete(workspace) + await initPromise } allowAiReplies (workspace: string, email: string): boolean { From 224d003d64e69498bec3e50bbbce50f4d4a09e93 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 11 Dec 2024 19:42:39 +0700 Subject: [PATCH 5/6] UBERF-8884 Use direct connection to database (#7426) Signed-off-by: Alexander Onnikov --- workers/datalake/src/blob.ts | 16 +++++++++++----- workers/datalake/src/db.ts | 6 ++++-- workers/datalake/worker-configuration.d.ts | 1 + 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/workers/datalake/src/blob.ts b/workers/datalake/src/blob.ts index 1c97a1675b..98cbf414e2 100644 --- a/workers/datalake/src/blob.ts +++ b/workers/datalake/src/blob.ts @@ -44,9 +44,13 @@ export async function handleBlobGet ( const { workspace, name } = request const cache = new LoggedCache(caches.default, metrics) - const cached = await cache.match(request) - if (cached !== undefined) { - return cached + + const cacheControl = request.headers.get('Cache-Control') ?? '' + if (!cacheControl.includes('no-cache')) { + const cached = await cache.match(request) + if (cached !== undefined) { + return cached + } } const { bucket } = selectStorage(env, workspace) @@ -75,8 +79,10 @@ export async function handleBlobGet ( const response = new Response(object?.body, { headers, status }) if (response.status === 200) { - const clone = metrics.withSync('response.clone', () => response.clone()) - ctx.waitUntil(cache.put(request, clone)) + if (!cacheControl.includes('no-store')) { + const clone = metrics.withSync('response.clone', () => response.clone()) + ctx.waitUntil(cache.put(request, clone)) + } } return response diff --git a/workers/datalake/src/db.ts b/workers/datalake/src/db.ts index 8af838fdb9..91e9bfe282 100644 --- a/workers/datalake/src/db.ts +++ b/workers/datalake/src/db.ts @@ -50,12 +50,14 @@ export async function withPostgres ( fn: (db: BlobDB) => Promise ): Promise { const sql = metrics.withSync('db.connect', () => { - return postgres(env.HYPERDRIVE.connectionString, { + return postgres(env.DB_URL, { connection: { application_name: 'datalake' - } + }, + fetch_types: false }) }) + const db = new LoggedDB(new PostgresDB(sql), metrics) try { diff --git a/workers/datalake/worker-configuration.d.ts b/workers/datalake/worker-configuration.d.ts index dfc1e51e8e..532c6991ce 100644 --- a/workers/datalake/worker-configuration.d.ts +++ b/workers/datalake/worker-configuration.d.ts @@ -12,6 +12,7 @@ interface Env { STREAMS_ACCOUNT_ID: string; STREAMS_AUTH_KEY: string; R2_ACCOUNT_ID: string; + DB_URL: string; DATALAKE_APAC_ACCESS_KEY: string; DATALAKE_APAC_SECRET_KEY: string; DATALAKE_APAC_BUCKET_NAME: string; From 962f3816b9c9fd323bd2cb5e86256d1f11138d68 Mon Sep 17 00:00:00 2001 From: Alexander Onnikov Date: Wed, 11 Dec 2024 20:42:39 +0700 Subject: [PATCH 6/6] UBERF-8876 Clear account cache on contact transaction (#7431) Signed-off-by: Alexander Onnikov --- services/github/pod-github/src/worker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/services/github/pod-github/src/worker.ts b/services/github/pod-github/src/worker.ts index 3f36636908..45e168a622 100644 --- a/services/github/pod-github/src/worker.ts +++ b/services/github/pod-github/src/worker.ts @@ -24,6 +24,7 @@ import core, { TxApplyIf, TxCUD, TxOperations, + TxProcessor, TxWorkspaceEvent, WithLookup, WorkspaceEvent, @@ -824,12 +825,16 @@ export class GithubWorker implements IntegrationManager { // Handle tx const h = this._client.getHierarchy() for (const t of tx) { - if (h.isDerived(t._class, core.class.TxCUD)) { + if (TxProcessor.isExtendsCUD(t._class)) { const cud = t as TxCUD if (cud.objectClass === github.class.DocSyncInfo) { this.triggerSync() break } + if (cud.objectClass === contact.class.Person || cud.objectClass === contact.class.Channel) { + this.accountMap.clear() + break + } } if (h.isDerived(t._class, core.class.TxApplyIf)) { const applyop = t as TxApplyIf