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/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/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, 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 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 { 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 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;