mirror of
https://github.com/hcengineering/platform.git
synced 2025-01-03 00:43:59 +03:00
UBERF-8518: Optimize client model (#7000)
Signed-off-by: Andrey Sobolev <haiodo@gmail.com>
This commit is contained in:
parent
001b2dd0d6
commit
0b1af0da90
@ -305,7 +305,7 @@ export async function configurePlatform (): Promise<void> {
|
|||||||
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
|
addLocation(printId, () => import(/* webpackChunkName: "print" */ '@hcengineering/print-resources'))
|
||||||
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
|
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
|
||||||
|
|
||||||
setMetadata(client.metadata.FilterModel, true)
|
setMetadata(client.metadata.FilterModel, 'ui')
|
||||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||||
|
|
||||||
// Use binary response transfer for faster performance and small transfer sizes.
|
// Use binary response transfer for faster performance and small transfer sizes.
|
||||||
|
@ -397,7 +397,7 @@ export async function configurePlatform() {
|
|||||||
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
|
addLocation(textEditorId, () => import(/* webpackChunkName: "text-editor" */ '@hcengineering/text-editor-resources'))
|
||||||
addLocation(uploaderId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/uploader-resources'))
|
addLocation(uploaderId, () => import(/* webpackChunkName: "uploader" */ '@hcengineering/uploader-resources'))
|
||||||
|
|
||||||
setMetadata(client.metadata.FilterModel, true)
|
setMetadata(client.metadata.FilterModel, 'ui')
|
||||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
||||||
|
|
||||||
// Use binary response transfer for faster performance and small transfer sizes.
|
// Use binary response transfer for faster performance and small transfer sizes.
|
||||||
|
@ -13,19 +13,30 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
import { Plugin, IntlString } from '@hcengineering/platform'
|
import { IntlString, Plugin } from '@hcengineering/platform'
|
||||||
import type { Account, Class, Data, Doc, Domain, PluginConfiguration, Ref, Timestamp } from '../classes'
|
import type { Account, Class, Data, Doc, Domain, PluginConfiguration, Ref, Timestamp } from '../classes'
|
||||||
import { Space, ClassifierKind, DOMAIN_MODEL } from '../classes'
|
import { ClassifierKind, DOMAIN_MODEL, Space } from '../classes'
|
||||||
import { createClient, ClientConnection } from '../client'
|
import { ClientConnection, createClient } from '../client'
|
||||||
|
import { clone } from '../clone'
|
||||||
import core from '../component'
|
import core from '../component'
|
||||||
import { Hierarchy } from '../hierarchy'
|
import { Hierarchy } from '../hierarchy'
|
||||||
import { ModelDb, TxDb } from '../memdb'
|
import { ModelDb, TxDb } from '../memdb'
|
||||||
import { TxOperations } from '../operations'
|
import { TxOperations } from '../operations'
|
||||||
import type { DocumentQuery, FindResult, TxResult, SearchQuery, SearchOptions, SearchResult } from '../storage'
|
import type { DocumentQuery, FindResult, SearchOptions, SearchQuery, SearchResult, TxResult } from '../storage'
|
||||||
import { Tx, TxFactory, TxProcessor } from '../tx'
|
import { Tx, TxFactory, TxProcessor } from '../tx'
|
||||||
|
import { fillConfiguration, pluginFilterTx } from '../utils'
|
||||||
import { connect } from './connection'
|
import { connect } from './connection'
|
||||||
import { genMinModel } from './minmodel'
|
import { genMinModel } from './minmodel'
|
||||||
import { clone } from '../clone'
|
|
||||||
|
function filterPlugin (plugin: Plugin): (txes: Tx[]) => Promise<Tx[]> {
|
||||||
|
return async (txes) => {
|
||||||
|
const configs = new Map<Ref<PluginConfiguration>, PluginConfiguration>()
|
||||||
|
fillConfiguration(txes, configs)
|
||||||
|
|
||||||
|
const excludedPlugins = Array.from(configs.values()).filter((it) => !it.enabled || it.pluginId !== plugin)
|
||||||
|
return pluginFilterTx(excludedPlugins, configs, txes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
describe('client', () => {
|
describe('client', () => {
|
||||||
it('should create client and spaces', async () => {
|
it('should create client and spaces', async () => {
|
||||||
@ -136,7 +147,10 @@ describe('client', () => {
|
|||||||
}
|
}
|
||||||
const txCreateDoc1 = txFactory.createTxCreateDoc(core.class.PluginConfiguration, core.space.Model, pluginData1)
|
const txCreateDoc1 = txFactory.createTxCreateDoc(core.class.PluginConfiguration, core.space.Model, pluginData1)
|
||||||
txes.push(txCreateDoc1)
|
txes.push(txCreateDoc1)
|
||||||
const client1 = new TxOperations(await createClient(connectPlugin, ['testPlugin1' as Plugin]), core.account.System)
|
const client1 = new TxOperations(
|
||||||
|
await createClient(connectPlugin, filterPlugin('testPlugin1' as Plugin)),
|
||||||
|
core.account.System
|
||||||
|
)
|
||||||
const result1 = await client1.findAll(core.class.PluginConfiguration, {})
|
const result1 = await client1.findAll(core.class.PluginConfiguration, {})
|
||||||
|
|
||||||
expect(result1).toHaveLength(1)
|
expect(result1).toHaveLength(1)
|
||||||
@ -153,7 +167,10 @@ describe('client', () => {
|
|||||||
}
|
}
|
||||||
const txCreateDoc2 = txFactory.createTxCreateDoc(core.class.PluginConfiguration, core.space.Model, pluginData2)
|
const txCreateDoc2 = txFactory.createTxCreateDoc(core.class.PluginConfiguration, core.space.Model, pluginData2)
|
||||||
txes.push(txCreateDoc2)
|
txes.push(txCreateDoc2)
|
||||||
const client2 = new TxOperations(await createClient(connectPlugin, ['testPlugin1' as Plugin]), core.account.System)
|
const client2 = new TxOperations(
|
||||||
|
await createClient(connectPlugin, filterPlugin('testPlugin1' as Plugin)),
|
||||||
|
core.account.System
|
||||||
|
)
|
||||||
const result2 = await client2.findAll(core.class.PluginConfiguration, {})
|
const result2 = await client2.findAll(core.class.PluginConfiguration, {})
|
||||||
|
|
||||||
expect(result2).toHaveLength(2)
|
expect(result2).toHaveLength(2)
|
||||||
@ -176,7 +193,10 @@ describe('client', () => {
|
|||||||
pluginData3
|
pluginData3
|
||||||
)
|
)
|
||||||
txes.push(txUpdateDoc)
|
txes.push(txUpdateDoc)
|
||||||
const client3 = new TxOperations(await createClient(connectPlugin, ['testPlugin2' as Plugin]), core.account.System)
|
const client3 = new TxOperations(
|
||||||
|
await createClient(connectPlugin, filterPlugin('testPlugin2' as Plugin)),
|
||||||
|
core.account.System
|
||||||
|
)
|
||||||
const result3 = await client3.findAll(core.class.PluginConfiguration, {})
|
const result3 = await client3.findAll(core.class.PluginConfiguration, {})
|
||||||
|
|
||||||
expect(result3).toHaveLength(1)
|
expect(result3).toHaveLength(1)
|
||||||
|
@ -13,17 +13,16 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import { Plugin } from '@hcengineering/platform'
|
|
||||||
import { BackupClient, DocChunk } from './backup'
|
import { BackupClient, DocChunk } from './backup'
|
||||||
import { Account, AttachedDoc, Class, DOMAIN_MODEL, Doc, Domain, PluginConfiguration, Ref, Timestamp } from './classes'
|
import { Account, AttachedDoc, Class, DOMAIN_MODEL, Doc, Domain, Ref, Timestamp } from './classes'
|
||||||
import core from './component'
|
import core from './component'
|
||||||
import { Hierarchy } from './hierarchy'
|
import { Hierarchy } from './hierarchy'
|
||||||
import { MeasureContext, MeasureMetricsContext } from './measurements'
|
import { MeasureContext, MeasureMetricsContext } from './measurements'
|
||||||
import { ModelDb } from './memdb'
|
import { ModelDb } from './memdb'
|
||||||
import type { DocumentQuery, FindOptions, FindResult, FulltextStorage, Storage, TxResult, WithLookup } from './storage'
|
import type { DocumentQuery, FindOptions, FindResult, FulltextStorage, Storage, TxResult, WithLookup } from './storage'
|
||||||
import { SearchOptions, SearchQuery, SearchResult, SortingOrder } from './storage'
|
import { SearchOptions, SearchQuery, SearchResult, SortingOrder } from './storage'
|
||||||
import { Tx, TxCUD, TxCollectionCUD, TxCreateDoc, TxProcessor, TxUpdateDoc } from './tx'
|
import { Tx, TxCUD, TxCollectionCUD } from './tx'
|
||||||
import { toFindResult, toIdMap } from './utils'
|
import { toFindResult } from './utils'
|
||||||
|
|
||||||
const transactionThreshold = 500
|
const transactionThreshold = 500
|
||||||
|
|
||||||
@ -215,13 +214,15 @@ export interface TxPersistenceStore {
|
|||||||
store: (model: LoadModelResponse) => Promise<void>
|
store: (model: LoadModelResponse) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ModelFilter = (tx: Tx[]) => Promise<Tx[]>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
export async function createClient (
|
export async function createClient (
|
||||||
connect: (txHandler: TxHandler) => Promise<ClientConnection>,
|
connect: (txHandler: TxHandler) => Promise<ClientConnection>,
|
||||||
// If set will build model with only allowed plugins.
|
// If set will build model with only allowed plugins.
|
||||||
allowedPlugins?: Plugin[],
|
modelFilter?: ModelFilter,
|
||||||
txPersistence?: TxPersistenceStore,
|
txPersistence?: TxPersistenceStore,
|
||||||
_ctx?: MeasureContext
|
_ctx?: MeasureContext
|
||||||
): Promise<AccountClient> {
|
): Promise<AccountClient> {
|
||||||
@ -248,14 +249,12 @@ export async function createClient (
|
|||||||
}
|
}
|
||||||
lastTx = tx.reduce((cur, it) => (it.modifiedOn > cur ? it.modifiedOn : cur), 0)
|
lastTx = tx.reduce((cur, it) => (it.modifiedOn > cur ? it.modifiedOn : cur), 0)
|
||||||
}
|
}
|
||||||
const configs = new Map<Ref<PluginConfiguration>, PluginConfiguration>()
|
|
||||||
|
|
||||||
const conn = await ctx.with('connect', {}, async () => await connect(txHandler))
|
const conn = await ctx.with('connect', {}, async () => await connect(txHandler))
|
||||||
|
|
||||||
await ctx.with(
|
await ctx.with(
|
||||||
'load-model',
|
'load-model',
|
||||||
{ reload: false },
|
{ reload: false },
|
||||||
async (ctx) => await loadModel(ctx, conn, allowedPlugins, configs, hierarchy, model, false, txPersistence)
|
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, false, txPersistence)
|
||||||
)
|
)
|
||||||
|
|
||||||
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model)
|
txBuffer = txBuffer.filter((tx) => tx.space !== core.space.Model)
|
||||||
@ -277,7 +276,7 @@ export async function createClient (
|
|||||||
const loadModelResponse = await ctx.with(
|
const loadModelResponse = await ctx.with(
|
||||||
'connect',
|
'connect',
|
||||||
{ reload: true },
|
{ reload: true },
|
||||||
async (ctx) => await loadModel(ctx, conn, allowedPlugins, configs, hierarchy, model, true, txPersistence)
|
async (ctx) => await loadModel(ctx, conn, modelFilter, hierarchy, model, true, txPersistence)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (event === ClientConnectEvent.Reconnected && loadModelResponse.full) {
|
if (event === ClientConnectEvent.Reconnected && loadModelResponse.full) {
|
||||||
@ -286,7 +285,7 @@ export async function createClient (
|
|||||||
model = new ModelDb(hierarchy)
|
model = new ModelDb(hierarchy)
|
||||||
|
|
||||||
await ctx.with('build-model', {}, async (ctx) => {
|
await ctx.with('build-model', {}, async (ctx) => {
|
||||||
await buildModel(ctx, loadModelResponse, allowedPlugins, configs, hierarchy, model)
|
await buildModel(ctx, loadModelResponse, modelFilter, hierarchy, model)
|
||||||
})
|
})
|
||||||
await oldOnConnect?.(ClientConnectEvent.Upgraded)
|
await oldOnConnect?.(ClientConnectEvent.Upgraded)
|
||||||
|
|
||||||
@ -393,8 +392,7 @@ function isPersonAccount (tx: Tx): boolean {
|
|||||||
async function loadModel (
|
async function loadModel (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
conn: ClientConnection,
|
conn: ClientConnection,
|
||||||
allowedPlugins: Plugin[] | undefined,
|
modelFilter: ModelFilter | undefined,
|
||||||
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
|
||||||
hierarchy: Hierarchy,
|
hierarchy: Hierarchy,
|
||||||
model: ModelDb,
|
model: ModelDb,
|
||||||
reload = false,
|
reload = false,
|
||||||
@ -418,19 +416,18 @@ async function loadModel (
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.with('build-model', {}, (ctx) => buildModel(ctx, modelResponse, allowedPlugins, configs, hierarchy, model))
|
await ctx.with('build-model', {}, (ctx) => buildModel(ctx, modelResponse, modelFilter, hierarchy, model))
|
||||||
return modelResponse
|
return modelResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildModel (
|
async function buildModel (
|
||||||
ctx: MeasureContext,
|
ctx: MeasureContext,
|
||||||
modelResponse: LoadModelResponse,
|
modelResponse: LoadModelResponse,
|
||||||
allowedPlugins: Plugin[] | undefined,
|
modelFilter: ModelFilter | undefined,
|
||||||
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
|
||||||
hierarchy: Hierarchy,
|
hierarchy: Hierarchy,
|
||||||
model: ModelDb
|
model: ModelDb
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
let systemTx: Tx[] = []
|
const systemTx: Tx[] = []
|
||||||
const userTx: Tx[] = []
|
const userTx: Tx[] = []
|
||||||
|
|
||||||
const atxes = modelResponse.transactions
|
const atxes = modelResponse.transactions
|
||||||
@ -444,23 +441,11 @@ async function buildModel (
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (allowedPlugins != null) {
|
let txes = systemTx.concat(userTx)
|
||||||
await ctx.with('fill config system', {}, async () => {
|
if (modelFilter !== undefined) {
|
||||||
fillConfiguration(systemTx, configs)
|
txes = await modelFilter(txes)
|
||||||
})
|
|
||||||
await ctx.with('fill config user', {}, async () => {
|
|
||||||
fillConfiguration(userTx, configs)
|
|
||||||
})
|
|
||||||
const excludedPlugins = Array.from(configs.values()).filter(
|
|
||||||
(it) => !it.enabled || !allowedPlugins.includes(it.pluginId)
|
|
||||||
)
|
|
||||||
await ctx.with('filter txes', {}, async () => {
|
|
||||||
systemTx = pluginFilterTx(excludedPlugins, configs, systemTx)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const txes = systemTx.concat(userTx)
|
|
||||||
|
|
||||||
await ctx.with('build hierarchy', {}, async () => {
|
await ctx.with('build hierarchy', {}, async () => {
|
||||||
for (const tx of txes) {
|
for (const tx of txes) {
|
||||||
try {
|
try {
|
||||||
@ -488,60 +473,3 @@ function getLastTxTime (txes: Tx[]): number {
|
|||||||
}
|
}
|
||||||
return lastTxTime
|
return lastTxTime
|
||||||
}
|
}
|
||||||
|
|
||||||
function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void {
|
|
||||||
for (const t of systemTx) {
|
|
||||||
if (t._class === core.class.TxCreateDoc) {
|
|
||||||
const ct = t as TxCreateDoc<Doc>
|
|
||||||
if (ct.objectClass === core.class.PluginConfiguration) {
|
|
||||||
configs.set(ct.objectId as Ref<PluginConfiguration>, TxProcessor.createDoc2Doc(ct) as PluginConfiguration)
|
|
||||||
}
|
|
||||||
} else if (t._class === core.class.TxUpdateDoc) {
|
|
||||||
const ut = t as TxUpdateDoc<Doc>
|
|
||||||
if (ut.objectClass === core.class.PluginConfiguration) {
|
|
||||||
const c = configs.get(ut.objectId as Ref<PluginConfiguration>)
|
|
||||||
if (c !== undefined) {
|
|
||||||
TxProcessor.updateDoc2Doc(c, ut)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function pluginFilterTx (
|
|
||||||
excludedPlugins: PluginConfiguration[],
|
|
||||||
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
|
||||||
systemTx: Tx[]
|
|
||||||
): Tx[] {
|
|
||||||
const stx = toIdMap(systemTx)
|
|
||||||
const totalExcluded = new Set<Ref<Tx>>()
|
|
||||||
let msg = ''
|
|
||||||
for (const a of excludedPlugins) {
|
|
||||||
for (const c of configs.values()) {
|
|
||||||
if (a.pluginId === c.pluginId) {
|
|
||||||
for (const id of c.transactions) {
|
|
||||||
if (c.classFilter !== undefined) {
|
|
||||||
const filter = new Set(c.classFilter)
|
|
||||||
const tx = stx.get(id as Ref<Tx>)
|
|
||||||
if (
|
|
||||||
tx?._class === core.class.TxCreateDoc ||
|
|
||||||
tx?._class === core.class.TxUpdateDoc ||
|
|
||||||
tx?._class === core.class.TxRemoveDoc
|
|
||||||
) {
|
|
||||||
const cud = tx as TxCUD<Doc>
|
|
||||||
if (filter.has(cud.objectClass)) {
|
|
||||||
totalExcluded.add(id as Ref<Tx>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
totalExcluded.add(id as Ref<Tx>)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
msg += ` ${c.pluginId}:${c.transactions.length}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log('exclude plugin', msg)
|
|
||||||
systemTx = systemTx.filter((t) => !totalExcluded.has(t._id))
|
|
||||||
return systemTx
|
|
||||||
}
|
|
||||||
|
@ -30,7 +30,7 @@ export class Hierarchy {
|
|||||||
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
|
private readonly attributes = new Map<Ref<Classifier>, Map<string, AnyAttribute>>()
|
||||||
private readonly attributesById = new Map<Ref<AnyAttribute>, AnyAttribute>()
|
private readonly attributesById = new Map<Ref<AnyAttribute>, AnyAttribute>()
|
||||||
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
private readonly descendants = new Map<Ref<Classifier>, Ref<Classifier>[]>()
|
||||||
private readonly ancestors = new Map<Ref<Classifier>, { ordered: Ref<Classifier>[], set: Set<Ref<Classifier>> }>()
|
private readonly ancestors = new Map<Ref<Classifier>, Set<Ref<Classifier>>>()
|
||||||
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
|
private readonly proxies = new Map<Ref<Mixin<Doc>>, ProxyHandler<Doc>>()
|
||||||
|
|
||||||
private readonly classifierProperties = new Map<Ref<Classifier>, Record<string, any>>()
|
private readonly classifierProperties = new Map<Ref<Classifier>, Record<string, any>>()
|
||||||
@ -166,7 +166,7 @@ export class Hierarchy {
|
|||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
throw new Error('ancestors not found: ' + _class)
|
throw new Error('ancestors not found: ' + _class)
|
||||||
}
|
}
|
||||||
return result.ordered
|
return Array.from(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
getClass<T extends Obj = Obj>(_class: Ref<Class<T>>): Class<T> {
|
getClass<T extends Obj = Obj>(_class: Ref<Class<T>>): Class<T> {
|
||||||
@ -301,7 +301,7 @@ export class Hierarchy {
|
|||||||
* It will iterate over parents.
|
* It will iterate over parents.
|
||||||
*/
|
*/
|
||||||
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
|
isDerived<T extends Obj>(_class: Ref<Class<T>>, from: Ref<Class<T>>): boolean {
|
||||||
return this.ancestors.get(_class)?.set?.has(from) ?? false
|
return this.ancestors.get(_class)?.has(from) ?? false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -388,19 +388,17 @@ export class Hierarchy {
|
|||||||
const list = this.ancestors.get(_class)
|
const list = this.ancestors.get(_class)
|
||||||
if (list === undefined) {
|
if (list === undefined) {
|
||||||
if (add) {
|
if (add) {
|
||||||
this.ancestors.set(_class, { ordered: [classifier], set: new Set([classifier]) })
|
this.ancestors.set(_class, new Set([classifier]))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (add) {
|
if (add) {
|
||||||
if (!list.set.has(classifier)) {
|
if (!list.has(classifier)) {
|
||||||
list.ordered.push(classifier)
|
list.add(classifier)
|
||||||
list.set.add(classifier)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const pos = list.ordered.indexOf(classifier)
|
const pos = list.has(classifier)
|
||||||
if (pos !== -1) {
|
if (pos) {
|
||||||
list.ordered.splice(pos, 1)
|
list.delete(classifier)
|
||||||
list.set.delete(classifier)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,8 @@ import {
|
|||||||
roleOrder,
|
roleOrder,
|
||||||
Space,
|
Space,
|
||||||
TypedSpace,
|
TypedSpace,
|
||||||
WorkspaceMode
|
WorkspaceMode,
|
||||||
|
type PluginConfiguration
|
||||||
} from './classes'
|
} from './classes'
|
||||||
import core from './component'
|
import core from './component'
|
||||||
import { Hierarchy } from './hierarchy'
|
import { Hierarchy } from './hierarchy'
|
||||||
@ -48,7 +49,7 @@ import { TxOperations } from './operations'
|
|||||||
import { isPredicate } from './predicate'
|
import { isPredicate } from './predicate'
|
||||||
import { Branding, BrandingMap } from './server'
|
import { Branding, BrandingMap } from './server'
|
||||||
import { DocumentQuery, FindResult } from './storage'
|
import { DocumentQuery, FindResult } from './storage'
|
||||||
import { DOMAIN_TX } from './tx'
|
import { DOMAIN_TX, TxProcessor, type Tx, type TxCreateDoc, type TxCUD, type TxUpdateDoc } from './tx'
|
||||||
|
|
||||||
function toHex (value: number, chars: number): string {
|
function toHex (value: number, chars: number): string {
|
||||||
const result = value.toString(16)
|
const result = value.toString(16)
|
||||||
@ -835,3 +836,60 @@ export function getBranding (brandings: BrandingMap, key: string | undefined): B
|
|||||||
|
|
||||||
return Object.values(brandings).find((branding) => branding.key === key) ?? null
|
return Object.values(brandings).find((branding) => branding.key === key) ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fillConfiguration (systemTx: Tx[], configs: Map<Ref<PluginConfiguration>, PluginConfiguration>): void {
|
||||||
|
for (const t of systemTx) {
|
||||||
|
if (t._class === core.class.TxCreateDoc) {
|
||||||
|
const ct = t as TxCreateDoc<Doc>
|
||||||
|
if (ct.objectClass === core.class.PluginConfiguration) {
|
||||||
|
configs.set(ct.objectId as Ref<PluginConfiguration>, TxProcessor.createDoc2Doc(ct) as PluginConfiguration)
|
||||||
|
}
|
||||||
|
} else if (t._class === core.class.TxUpdateDoc) {
|
||||||
|
const ut = t as TxUpdateDoc<Doc>
|
||||||
|
if (ut.objectClass === core.class.PluginConfiguration) {
|
||||||
|
const c = configs.get(ut.objectId as Ref<PluginConfiguration>)
|
||||||
|
if (c !== undefined) {
|
||||||
|
TxProcessor.updateDoc2Doc(c, ut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pluginFilterTx (
|
||||||
|
excludedPlugins: PluginConfiguration[],
|
||||||
|
configs: Map<Ref<PluginConfiguration>, PluginConfiguration>,
|
||||||
|
systemTx: Tx[]
|
||||||
|
): Tx[] {
|
||||||
|
const stx = toIdMap(systemTx)
|
||||||
|
const totalExcluded = new Set<Ref<Tx>>()
|
||||||
|
let msg = ''
|
||||||
|
for (const a of excludedPlugins) {
|
||||||
|
for (const c of configs.values()) {
|
||||||
|
if (a.pluginId === c.pluginId) {
|
||||||
|
for (const id of c.transactions) {
|
||||||
|
if (c.classFilter !== undefined) {
|
||||||
|
const filter = new Set(c.classFilter)
|
||||||
|
const tx = stx.get(id as Ref<Tx>)
|
||||||
|
if (
|
||||||
|
tx?._class === core.class.TxCreateDoc ||
|
||||||
|
tx?._class === core.class.TxUpdateDoc ||
|
||||||
|
tx?._class === core.class.TxRemoveDoc
|
||||||
|
) {
|
||||||
|
const cud = tx as TxCUD<Doc>
|
||||||
|
if (filter.has(cud.objectClass)) {
|
||||||
|
totalExcluded.add(id as Ref<Tx>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
totalExcluded.add(id as Ref<Tx>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
msg += ` ${c.pluginId}:${c.transactions.length}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('exclude plugin', msg)
|
||||||
|
systemTx = systemTx.filter((t) => !totalExcluded.has(t._id))
|
||||||
|
return systemTx
|
||||||
|
}
|
||||||
|
@ -24,7 +24,16 @@ import core, {
|
|||||||
TxWorkspaceEvent,
|
TxWorkspaceEvent,
|
||||||
WorkspaceEvent,
|
WorkspaceEvent,
|
||||||
concatLink,
|
concatLink,
|
||||||
createClient
|
createClient,
|
||||||
|
fillConfiguration,
|
||||||
|
pluginFilterTx,
|
||||||
|
type Class,
|
||||||
|
type ClientConnection,
|
||||||
|
type Doc,
|
||||||
|
type ModelFilter,
|
||||||
|
type PluginConfiguration,
|
||||||
|
type Ref,
|
||||||
|
type TxCUD
|
||||||
} from '@hcengineering/core'
|
} from '@hcengineering/core'
|
||||||
import platform, { Severity, Status, getMetadata, getPlugins, setPlatformStatus } from '@hcengineering/platform'
|
import platform, { Severity, Status, getMetadata, getPlugins, setPlatformStatus } from '@hcengineering/platform'
|
||||||
import { connect } from './connection'
|
import { connect } from './connection'
|
||||||
@ -70,68 +79,123 @@ export default async () => {
|
|||||||
return {
|
return {
|
||||||
function: {
|
function: {
|
||||||
GetClient: async (token: string, endpoint: string, opt?: ClientFactoryOptions): Promise<AccountClient> => {
|
GetClient: async (token: string, endpoint: string, opt?: ClientFactoryOptions): Promise<AccountClient> => {
|
||||||
const filterModel = getMetadata(clientPlugin.metadata.FilterModel) ?? false
|
const filterModel = getMetadata(clientPlugin.metadata.FilterModel) ?? 'none'
|
||||||
|
|
||||||
const client = createClient(
|
const handler = async (handler: TxHandler): Promise<ClientConnection> => {
|
||||||
async (handler: TxHandler) => {
|
const url = concatLink(endpoint, `/${token}`)
|
||||||
const url = concatLink(endpoint, `/${token}`)
|
|
||||||
|
|
||||||
const upgradeHandler: TxHandler = (...txes: Tx[]) => {
|
const upgradeHandler: TxHandler = (...txes: Tx[]) => {
|
||||||
for (const tx of txes) {
|
for (const tx of txes) {
|
||||||
if (tx?._class === core.class.TxModelUpgrade) {
|
if (tx?._class === core.class.TxModelUpgrade) {
|
||||||
opt?.onUpgrade?.()
|
opt?.onUpgrade?.()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (tx?._class === core.class.TxWorkspaceEvent) {
|
if (tx?._class === core.class.TxWorkspaceEvent) {
|
||||||
const event = tx as TxWorkspaceEvent
|
const event = tx as TxWorkspaceEvent
|
||||||
if (event.event === WorkspaceEvent.MaintenanceNotification) {
|
if (event.event === WorkspaceEvent.MaintenanceNotification) {
|
||||||
void setPlatformStatus(
|
void setPlatformStatus(
|
||||||
new Status(Severity.WARNING, platform.status.MaintenanceWarning, {
|
new Status(Severity.WARNING, platform.status.MaintenanceWarning, {
|
||||||
time: event.params.timeMinutes
|
time: event.params.timeMinutes
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
handler(...txes)
|
|
||||||
}
|
}
|
||||||
const tokenPayload: { workspace: string, email: string } = decodeTokenPayload(token)
|
handler(...txes)
|
||||||
|
}
|
||||||
|
const tokenPayload: { workspace: string, email: string } = decodeTokenPayload(token)
|
||||||
|
|
||||||
const newOpt = { ...opt }
|
const newOpt = { ...opt }
|
||||||
const connectTimeout = getMetadata(clientPlugin.metadata.ConnectionTimeout)
|
const connectTimeout = getMetadata(clientPlugin.metadata.ConnectionTimeout)
|
||||||
let connectPromise: Promise<void> | undefined
|
let connectPromise: Promise<void> | undefined
|
||||||
if ((connectTimeout ?? 0) > 0) {
|
if ((connectTimeout ?? 0) > 0) {
|
||||||
connectPromise = new Promise<void>((resolve, reject) => {
|
connectPromise = new Promise<void>((resolve, reject) => {
|
||||||
const connectTO = setTimeout(() => {
|
const connectTO = setTimeout(() => {
|
||||||
if (!clientConnection.isConnected()) {
|
if (!clientConnection.isConnected()) {
|
||||||
newOpt.onConnect = undefined
|
newOpt.onConnect = undefined
|
||||||
void clientConnection?.close()
|
void clientConnection?.close()
|
||||||
void opt?.onDialTimeout?.()
|
void opt?.onDialTimeout?.()
|
||||||
reject(new Error(`Connection timeout, and no connection established to ${endpoint}`))
|
reject(new Error(`Connection timeout, and no connection established to ${endpoint}`))
|
||||||
}
|
|
||||||
}, connectTimeout)
|
|
||||||
newOpt.onConnect = (event) => {
|
|
||||||
// Any event is fine, it means server is alive.
|
|
||||||
clearTimeout(connectTO)
|
|
||||||
resolve()
|
|
||||||
}
|
}
|
||||||
})
|
}, connectTimeout)
|
||||||
}
|
newOpt.onConnect = (event) => {
|
||||||
const clientConnection = connect(url, upgradeHandler, tokenPayload.workspace, tokenPayload.email, newOpt)
|
// Any event is fine, it means server is alive.
|
||||||
if (connectPromise !== undefined) {
|
clearTimeout(connectTO)
|
||||||
await connectPromise
|
resolve()
|
||||||
}
|
}
|
||||||
return await Promise.resolve(clientConnection)
|
})
|
||||||
},
|
}
|
||||||
filterModel ? [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])] : undefined,
|
const clientConnection = connect(url, upgradeHandler, tokenPayload.workspace, tokenPayload.email, newOpt)
|
||||||
createModelPersistence(getWSFromToken(token)),
|
if (connectPromise !== undefined) {
|
||||||
opt?.ctx
|
await connectPromise
|
||||||
)
|
}
|
||||||
|
return await Promise.resolve(clientConnection)
|
||||||
|
}
|
||||||
|
|
||||||
|
const modelFilter: ModelFilter = async (txes) => {
|
||||||
|
if (filterModel === 'client') {
|
||||||
|
return returnClientTxes(txes)
|
||||||
|
}
|
||||||
|
if (filterModel === 'ui') {
|
||||||
|
return returnUITxes(txes)
|
||||||
|
}
|
||||||
|
return txes
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = createClient(handler, modelFilter, createModelPersistence(getWSFromToken(token)), opt?.ctx)
|
||||||
return await client
|
return await client
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function returnUITxes (txes: Tx[]): Tx[] {
|
||||||
|
const configs = new Map<Ref<PluginConfiguration>, PluginConfiguration>()
|
||||||
|
fillConfiguration(txes, configs)
|
||||||
|
|
||||||
|
const allowedPlugins = [...getPlugins(), ...(getMetadata(clientPlugin.metadata.ExtraPlugins) ?? [])]
|
||||||
|
const excludedPlugins = Array.from(configs.values()).filter(
|
||||||
|
(it) => !it.enabled || !allowedPlugins.includes(it.pluginId)
|
||||||
|
)
|
||||||
|
return pluginFilterTx(excludedPlugins, configs, txes)
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnClientTxes (txes: Tx[]): Tx[] {
|
||||||
|
const configs = new Map<Ref<PluginConfiguration>, PluginConfiguration>()
|
||||||
|
fillConfiguration(txes, configs)
|
||||||
|
const excludedPlugins = Array.from(configs.values()).filter((it) => !it.enabled || it.pluginId.startsWith('server-'))
|
||||||
|
|
||||||
|
const toExclude = new Set([
|
||||||
|
'workbench:class:Application' as Ref<Class<Doc>>,
|
||||||
|
'presentation:class:ComponentPointExtension' as Ref<Class<Doc>>,
|
||||||
|
'presentation:class:ObjectSearchCategory' as Ref<Class<Doc>>,
|
||||||
|
'notification:class:NotificationGroup' as Ref<Class<Doc>>,
|
||||||
|
'notification:class:NotificationType' as Ref<Class<Doc>>,
|
||||||
|
'view:class:Action' as Ref<Class<Doc>>,
|
||||||
|
'view:class:Viewlet' as Ref<Class<Doc>>,
|
||||||
|
'text-editor:class:TextEditorAction' as Ref<Class<Doc>>,
|
||||||
|
'templates:class:TemplateField' as Ref<Class<Doc>>,
|
||||||
|
'activity:class:DocUpdateMessageViewlet' as Ref<Class<Doc>>,
|
||||||
|
'core:class:PluginConfiguration' as Ref<Class<Doc>>,
|
||||||
|
'core:class:DomainIndexConfiguration' as Ref<Class<Doc>>
|
||||||
|
])
|
||||||
|
|
||||||
|
const result = pluginFilterTx(excludedPlugins, configs, txes).filter((tx) => {
|
||||||
|
// Exclude all matched UI plugins
|
||||||
|
if (
|
||||||
|
tx?._class === core.class.TxCreateDoc ||
|
||||||
|
tx?._class === core.class.TxUpdateDoc ||
|
||||||
|
tx?._class === core.class.TxRemoveDoc
|
||||||
|
) {
|
||||||
|
const cud = tx as TxCUD<Doc>
|
||||||
|
if (toExclude.has(cud.objectClass)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
function createModelPersistence (workspace: string): TxPersistenceStore | undefined {
|
function createModelPersistence (workspace: string): TxPersistenceStore | undefined {
|
||||||
const overrideStore = getMetadata(clientPlugin.metadata.OverridePersistenceStore)
|
const overrideStore = getMetadata(clientPlugin.metadata.OverridePersistenceStore)
|
||||||
if (overrideStore !== undefined) {
|
if (overrideStore !== undefined) {
|
||||||
|
@ -69,10 +69,15 @@ export interface ClientFactoryOptions {
|
|||||||
*/
|
*/
|
||||||
export type ClientFactory = (token: string, endpoint: string, opt?: ClientFactoryOptions) => Promise<AccountClient>
|
export type ClientFactory = (token: string, endpoint: string, opt?: ClientFactoryOptions) => Promise<AccountClient>
|
||||||
|
|
||||||
|
// client - will filter out all server model elements
|
||||||
|
// It will also filter out all UI Elements, like Actions, View declarations etc.
|
||||||
|
// ui - will filter out all server element's and all UI disabled elements.
|
||||||
|
export type FilterMode = 'none' | 'client' | 'ui'
|
||||||
|
|
||||||
export default plugin(clientId, {
|
export default plugin(clientId, {
|
||||||
metadata: {
|
metadata: {
|
||||||
ClientSocketFactory: '' as Metadata<ClientSocketFactory>,
|
ClientSocketFactory: '' as Metadata<ClientSocketFactory>,
|
||||||
FilterModel: '' as Metadata<boolean>,
|
FilterModel: '' as Metadata<FilterMode>,
|
||||||
ExtraPlugins: '' as Metadata<Plugin[]>,
|
ExtraPlugins: '' as Metadata<Plugin[]>,
|
||||||
UseBinaryProtocol: '' as Metadata<boolean>,
|
UseBinaryProtocol: '' as Metadata<boolean>,
|
||||||
UseProtocolCompression: '' as Metadata<boolean>,
|
UseProtocolCompression: '' as Metadata<boolean>,
|
||||||
|
@ -104,6 +104,5 @@ export async function configurePlatform() {
|
|||||||
setMetadata(uiPlugin.metadata.PlatformTitle, 'Tracker')
|
setMetadata(uiPlugin.metadata.PlatformTitle, 'Tracker')
|
||||||
setMetadata(workbench.metadata.PlatformTitle, 'Tracker')
|
setMetadata(workbench.metadata.PlatformTitle, 'Tracker')
|
||||||
|
|
||||||
setMetadata(client.metadata.FilterModel, true)
|
setMetadata(client.metadata.FilterModel, 'ui')
|
||||||
setMetadata(client.metadata.ExtraPlugins, ['preference' as Plugin])
|
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import client from '@hcengineering/client'
|
||||||
import { type Client } from '@hcengineering/core'
|
import { type Client } from '@hcengineering/core'
|
||||||
|
import { setMetadata } from '@hcengineering/platform'
|
||||||
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
import { createClient, getTransactorEndpoint } from '@hcengineering/server-client'
|
||||||
|
|
||||||
export async function getClient (token: string): Promise<Client> {
|
export async function getClient (token: string): Promise<Client> {
|
||||||
const endpoint = await getTransactorEndpoint(token)
|
const endpoint = await getTransactorEndpoint(token)
|
||||||
|
setMetadata(client.metadata.FilterModel, 'client')
|
||||||
return await createClient(endpoint, token)
|
return await createClient(endpoint, token)
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ export async function createPlatformClient (
|
|||||||
{ mode: 'github' }
|
{ mode: 'github' }
|
||||||
)
|
)
|
||||||
setMetadata(client.metadata.ConnectionTimeout, timeout)
|
setMetadata(client.metadata.ConnectionTimeout, timeout)
|
||||||
|
setMetadata(client.metadata.FilterModel, 'client')
|
||||||
const endpoint = await getTransactorEndpoint(token)
|
const endpoint = await getTransactorEndpoint(token)
|
||||||
const connection = await (
|
const connection = await (
|
||||||
await clientResources()
|
await clientResources()
|
||||||
|
Loading…
Reference in New Issue
Block a user