QFIX: Fix REST API + few minors (#8108)

Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
Andrey Sobolev 2025-02-27 17:00:00 +07:00 committed by GitHub
parent 5bb36d6eee
commit e0ec8d4206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 41 additions and 31 deletions

View File

@ -5,5 +5,7 @@
{ "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" },
{ "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }, { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" },
{ "src": "/icon-1024.png", "type": "image/png", "sizes": "1024x1024" } { "src": "/icon-1024.png", "type": "image/png", "sizes": "1024x1024" }
] ],
"start_url": "/",
"scope": "/"
} }

View File

@ -3,7 +3,6 @@
// //
import { type AnalyticProvider, Analytics } from "@hcengineering/analytics" import { type AnalyticProvider, Analytics } from "@hcengineering/analytics"
import { AnalyticsCollectorProvider } from './analytics/analyticsCollector'
import { PosthogAnalyticProvider } from "./analytics/posthog" import { PosthogAnalyticProvider } from "./analytics/posthog"
import { SentryAnalyticProvider } from "./analytics/sentry" import { SentryAnalyticProvider } from "./analytics/sentry"
import { type Config } from "./platform" import { type Config } from "./platform"
@ -11,8 +10,7 @@ import { type Config } from "./platform"
export function configureAnalytics (config: Config) { export function configureAnalytics (config: Config) {
const providers: AnalyticProvider[] = [ const providers: AnalyticProvider[] = [
new SentryAnalyticProvider, new SentryAnalyticProvider,
new PosthogAnalyticProvider, new PosthogAnalyticProvider
new AnalyticsCollectorProvider
] ]
for (const provider of providers) { for (const provider of providers) {
Analytics.init(provider, config) Analytics.init(provider, config)

View File

@ -39,7 +39,7 @@ import { PlatformError, unknownError } from '@hcengineering/platform'
import type { RestClient } from './types' import type { RestClient } from './types'
import { extractJson, withRetry } from './utils' import { extractJson, withRetry } from './utils'
export async function createRestClient (endpoint: string, workspaceId: string, token: string): Promise<RestClient> { export function createRestClient (endpoint: string, workspaceId: string, token: string): RestClient {
return new RestClientImpl(endpoint, workspaceId, token) return new RestClientImpl(endpoint, workspaceId, token)
} }

View File

@ -70,6 +70,8 @@ import {
import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage' import { buildStorageFromConfig, storageConfigFromEnv } from '@hcengineering/server-storage'
import { createWorkspace, upgradeWorkspace } from './ws-operations' import { createWorkspace, upgradeWorkspace } from './ws-operations'
const dbCleanTreshold = 256 // Cleanup workspaces if less 256mb
export interface WorkspaceOptions { export interface WorkspaceOptions {
errorHandler: (workspace: BaseWorkspaceInfo, error: any) => Promise<void> errorHandler: (workspace: BaseWorkspaceInfo, error: any) => Promise<void>
force: boolean force: boolean
@ -372,15 +374,19 @@ export class WorkspaceWorker {
/** /**
* If onlyDrop is true, will drop workspace from database, overwize remove only indexes and do full reindex. * If onlyDrop is true, will drop workspace from database, overwize remove only indexes and do full reindex.
*/ */
async doCleanup (ctx: MeasureContext, workspace: BaseWorkspaceInfo, onlyDrop: boolean): Promise<void> { async doCleanup (ctx: MeasureContext, workspace: BaseWorkspaceInfo, cleanIndexes: boolean): Promise<void> {
const { dbUrl } = prepareTools([]) const { dbUrl } = prepareTools([])
const adapter = getWorkspaceDestroyAdapter(dbUrl) const adapter = getWorkspaceDestroyAdapter(dbUrl)
await adapter.deleteWorkspace(ctx, sharedPipelineContextVars, { name: workspace.workspace, uuid: workspace.uuid }) await adapter.deleteWorkspace(ctx, sharedPipelineContextVars, { name: workspace.workspace, uuid: workspace.uuid })
await this.doReindexFulltext(ctx, workspace, onlyDrop) await this.doReindexFulltext(ctx, workspace, cleanIndexes)
} }
private async doReindexFulltext (ctx: MeasureContext, workspace: BaseWorkspaceInfo, onlyDrop: boolean): Promise<void> { private async doReindexFulltext (
ctx: MeasureContext,
workspace: BaseWorkspaceInfo,
clearIndexes: boolean
): Promise<void> {
if (this.fulltextUrl !== undefined) { if (this.fulltextUrl !== undefined) {
const token = generateToken(systemAccountEmail, { name: workspace.workspace }, { service: 'workspace' }) const token = generateToken(systemAccountEmail, { name: workspace.workspace }, { service: 'workspace' })
@ -390,7 +396,7 @@ export class WorkspaceWorker {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ token, onlyDrop }) body: JSON.stringify({ token, onlyDrop: clearIndexes })
}) })
if (!res.ok) { if (!res.ok) {
throw new Error(`HTTP Error ${res.status} ${res.statusText}`) throw new Error(`HTTP Error ${res.status} ${res.statusText}`)
@ -491,12 +497,16 @@ export class WorkspaceWorker {
// We should remove DB, not storages. // We should remove DB, not storages.
await sendEvent('migrate-clean-started', 0) await sendEvent('migrate-clean-started', 0)
await this.sendTransactorMaitenance(token, { name: workspace.workspace }) await this.sendTransactorMaitenance(token, { name: workspace.workspace })
const sz = workspace.backupInfo?.backupSize ?? 0
if (sz <= dbCleanTreshold) {
try { try {
await this.doCleanup(ctx, workspace, false) await this.doCleanup(ctx, workspace, false)
} catch (err: any) { } catch (err: any) {
Analytics.handleError(err) Analytics.handleError(err)
return return
} }
}
await sendEvent('migrate-clean-done', 0) await sendEvent('migrate-clean-done', 0)
break break
} }

View File

@ -149,9 +149,9 @@ describe('rest-server', () => {
await shutdown() await shutdown()
}) })
async function connect (): Promise<RestClient> { function connect (): RestClient {
const token: string = generateToken('user1@site.com', getWorkspaceId('test-ws')) const token: string = generateToken('user1@site.com', getWorkspaceId('test-ws'))
return await createRestClient(`http://localhost:${port}`, 'test-ws', token) return createRestClient(`http://localhost:${port}`, 'test-ws', token)
} }
async function connectTx (): Promise<TxOperations> { async function connectTx (): Promise<TxOperations> {
@ -160,7 +160,7 @@ describe('rest-server', () => {
} }
it('get account', async () => { it('get account', async () => {
const conn = await connect() const conn = connect()
const account = await conn.getAccount() const account = await conn.getAccount()
expect(account.email).toBe('user1@site.com') expect(account.email).toBe('user1@site.com')
@ -175,7 +175,7 @@ describe('rest-server', () => {
}) })
it('find spaces', async () => { it('find spaces', async () => {
const conn = await connect() const conn = connect()
const spaces = await conn.findAll(core.class.Space, {}) const spaces = await conn.findAll(core.class.Space, {})
expect(spaces.length).toBe(2) expect(spaces.length).toBe(2)
expect(spaces[0].name).toBe('Sp1') expect(spaces[0].name).toBe('Sp1')
@ -183,7 +183,7 @@ describe('rest-server', () => {
}) })
it('find avg', async () => { it('find avg', async () => {
const conn = await connect() const conn = connect()
let ops = 0 let ops = 0
let total = 0 let total = 0
const attempts = 1000 const attempts = 1000
@ -204,7 +204,7 @@ describe('rest-server', () => {
}) })
it('add space', async () => { it('add space', async () => {
const conn = await connect() const conn = connect()
const account = await conn.getAccount() const account = await conn.getAccount()
const tx: TxCreateDoc<Space> = { const tx: TxCreateDoc<Space> = {
_class: core.class.TxCreateDoc, _class: core.class.TxCreateDoc,

View File

@ -44,9 +44,9 @@ describe('rest-api-server', () => {
}) })
}) })
async function connect (ws?: WorkspaceToken): Promise<RestClient> { function connect (ws?: WorkspaceToken): RestClient {
const tok = ws ?? apiWorkspace1 const tok = ws ?? apiWorkspace1
return await createRestClient(tok.endpoint, tok.workspaceId, tok.token) return createRestClient(tok.endpoint, tok.workspaceId, tok.token)
} }
async function connectTx (ws?: WorkspaceToken): Promise<TxOperations> { async function connectTx (ws?: WorkspaceToken): Promise<TxOperations> {
@ -55,7 +55,7 @@ describe('rest-api-server', () => {
} }
it('get account', async () => { it('get account', async () => {
const conn = await connect() const conn = connect()
const account = await conn.getAccount() const account = await conn.getAccount()
expect(account.email).toBe('user1') expect(account.email).toBe('user1')
@ -69,7 +69,7 @@ describe('rest-api-server', () => {
}) })
it('find spaces', async () => { it('find spaces', async () => {
const conn = await connect() const conn = connect()
const spaces = await conn.findAll(core.class.Space, {}) const spaces = await conn.findAll(core.class.Space, {})
expect(spaces.length).toBeGreaterThanOrEqual(20) expect(spaces.length).toBeGreaterThanOrEqual(20)
const personSpace = spaces.find((it) => it.name === 'Pesonal space' && it.private) const personSpace = spaces.find((it) => it.name === 'Pesonal space' && it.private)
@ -77,12 +77,12 @@ describe('rest-api-server', () => {
}) })
it('find spaces limit', async () => { it('find spaces limit', async () => {
const conn = await connect() const conn = connect()
const spaces = await conn.findAll(core.class.Space, {}, { limit: 5 }) const spaces = await conn.findAll(core.class.Space, {}, { limit: 5 })
expect(spaces.length).toBe(5) expect(spaces.length).toBe(5)
}) })
it('find spaces by-name', async () => { it('find spaces by-name', async () => {
const conn = await connect() const conn = connect()
const spaces = await conn.findAll( const spaces = await conn.findAll(
contact.class.PersonSpace, contact.class.PersonSpace,
{ name: 'Personal space' }, { name: 'Personal space' },
@ -98,24 +98,24 @@ describe('rest-api-server', () => {
}) })
it('find channels', async () => { it('find channels', async () => {
const conn = await connect() const conn = connect()
const spaces = await conn.findAll(chunter.class.Channel, {}) const spaces = await conn.findAll(chunter.class.Channel, {})
expect(spaces.length).toBeGreaterThanOrEqual(2) expect(spaces.length).toBeGreaterThanOrEqual(2)
expect(spaces.find((it) => it._id === 'chunter:space:General')).not.toBeNull() expect(spaces.find((it) => it._id === 'chunter:space:General')).not.toBeNull()
}) })
it('find avg', async () => { it('find avg', async () => {
const conn = await connect() const conn = connect()
await checkFindPerformance(conn) // 5ms max per operation await checkFindPerformance(conn) // 5ms max per operation
}) })
it('find avg-europe', async () => { it('find avg-europe', async () => {
const conn = await connect(apiWorkspace2) const conn = connect(apiWorkspace2)
await checkFindPerformance(conn) // 5ms max per operation await checkFindPerformance(conn) // 5ms max per operation
}) })
it('add space', async () => { it('add space', async () => {
const conn = await connect() const conn = connect()
const account = await conn.getAccount() const account = await conn.getAccount()
const spaceName = generateId() const spaceName = generateId()
const tx: TxCreateDoc<Space> = { const tx: TxCreateDoc<Space> = {
@ -142,7 +142,7 @@ describe('rest-api-server', () => {
}) })
it('get-model', async () => { it('get-model', async () => {
const conn = await connect() const conn = connect()
const { hierarchy, model } = await conn.getModel() const { hierarchy, model } = await conn.getModel()
const dsc = hierarchy.getDescendants(core.class.Space) const dsc = hierarchy.getDescendants(core.class.Space)