diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml index ae7ededf41..01f83432fb 100644 --- a/common/config/rush/pnpm-lock.yaml +++ b/common/config/rush/pnpm-lock.yaml @@ -40441,6 +40441,9 @@ importers: '@hcengineering/analytics-service': specifier: workspace:^0.7.19 version: link:../../../foundations/core/packages/analytics-service + '@hcengineering/api-client': + specifier: workspace:^0.7.25 + version: link:../../../foundations/core/packages/api-client '@hcengineering/core': specifier: workspace:^0.7.26 version: link:../../../foundations/core/packages/core @@ -40450,6 +40453,9 @@ importers: '@hcengineering/server-core': specifier: workspace:^0.7.19 version: link:../../../foundations/server/packages/core + '@hcengineering/server-guest-resources': + specifier: workspace:^0.7.0 + version: link:../../../server-plugins/guest-resources '@hcengineering/server-storage': specifier: workspace:^0.7.16 version: link:../../../foundations/server/packages/server-storage diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index fe928696fb..dc910a0790 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -415,6 +415,7 @@ services: - STATS_URL=http://huly.local:4900 - ACCOUNTS_URL=http://huly.local:3000 - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces + - FRONT_URL=http://huly.local:8087 sign: image: hardcoreeng/sign extra_hosts: diff --git a/dev/local-mongo/docker-compose.yaml b/dev/local-mongo/docker-compose.yaml index ba43b35551..7269fd199f 100644 --- a/dev/local-mongo/docker-compose.yaml +++ b/dev/local-mongo/docker-compose.yaml @@ -153,6 +153,7 @@ services: - MONGO_URL=mongodb://huly.local:27017?compressors=snappy - 'MONGO_OPTIONS={"appName":"print","maxPoolSize":1}' - STORAGE_CONFIG=${STORAGE_CONFIG} + - FRONT_URL=http://huly.local:8087 deploy: resources: limits: diff --git a/dev/tool/src/index.ts b/dev/tool/src/index.ts index d624a32500..56acb52fea 100644 --- a/dev/tool/src/index.ts +++ b/dev/tool/src/index.ts @@ -331,28 +331,10 @@ export function devTool ( // }) // }) - function parseAccountRole (raw: unknown): AccountRole { - const rawRole = typeof raw === 'string' ? raw.trim() : String(raw ?? 'User').trim() - const normalized = rawRole.trim().toLowerCase() - - const match = Object.values(AccountRole).find((v) => v.toLowerCase() === normalized) - if (match !== undefined) return match - - switch (normalized) { - case 'readonly': - return AccountRole.ReadOnlyGuest - case 'docguest': - return AccountRole.DocGuest - default: - throw new Error(`Unknown role: ${rawRole}`) - } - } - program .command('assign-workspace ') .description('assign workspace') - .option('--role ', 'Workspace role (User, Guest, ReadOnlyGuest, DocGuest, Maintainer, Owner, Admin)', 'User') - .action(async (email: string, workspace: string, cmd: { role: string }) => { + .action(async (email: string, workspace: string, cmd) => { await withAccountDatabase(async (db) => { console.log(`assigning user ${email} to ${workspace}...`) try { @@ -361,12 +343,10 @@ export function devTool ( throw new Error(`Workspace ${workspace} not found`) } - const role = cmd.role != null ? parseAccountRole(cmd.role) : AccountRole.User - await assignWorkspace(toolCtx, db, null, getToolToken(), { email, workspaceUuid: ws.uuid, - role + role: AccountRole.User }) } catch (err: any) { console.error(err) diff --git a/foundations/core/packages/api-client/src/rest/adapter.ts b/foundations/core/packages/api-client/src/rest/adapter.ts new file mode 100644 index 0000000000..18f1cf8afe --- /dev/null +++ b/foundations/core/packages/api-client/src/rest/adapter.ts @@ -0,0 +1,96 @@ +// +// Copyright © 2026 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 { + type Class, + Client, + type Doc, + type DocumentQuery, + type DomainParams, + type DomainRequestOptions, + type DomainResult, + type FindOptions, + type FindResult, + Hierarchy, + ModelDb, + OperationDomain, + type Ref, + type SearchOptions, + type SearchQuery, + type SearchResult, + type Tx, + type TxResult, + type WithLookup +} from '@hcengineering/core' + +import type { RestClient } from './types' + +export class RestClientAdapter implements Client { + constructor ( + private readonly client: RestClient, + private readonly hierarchy: Hierarchy | undefined, + private readonly model: ModelDb | undefined + ) {} + + async domainRequest( + domain: OperationDomain, + params: DomainParams, + options?: DomainRequestOptions + ): Promise> { + return await this.client.domainRequest(domain, params, options) + } + + async findAll( + _class: Ref>, + query: DocumentQuery, + options?: FindOptions + ): Promise> { + return await this.client.findAll(_class, query, options) + } + + async tx (tx: Tx): Promise { + return await this.client.tx(tx) + } + + async findOne( + _class: Ref>, + query: DocumentQuery, + options?: FindOptions + ): Promise | undefined> { + return await this.client.findOne(_class, query, options) + } + + async searchFulltext (query: SearchQuery, options: SearchOptions): Promise { + return await this.client.searchFulltext(query, options) + } + + async close (): Promise { + // No ned to close the REST client + } + + getHierarchy (): Hierarchy { + if (this.hierarchy === undefined) { + throw new Error('Hierarchy is not defined') + } + return this.hierarchy + } + + getModel (): ModelDb { + if (this.model === undefined) { + throw new Error('Model is not defined') + } + return this.model + } +} diff --git a/foundations/core/packages/api-client/src/rest/index.ts b/foundations/core/packages/api-client/src/rest/index.ts index 371187c27a..e2f5f2608f 100644 --- a/foundations/core/packages/api-client/src/rest/index.ts +++ b/foundations/core/packages/api-client/src/rest/index.ts @@ -13,6 +13,7 @@ // limitations under the License. // +export { RestClientAdapter } from './adapter' export { createRestClient, connectRest } from './rest' export { createRestTxOperations } from './tx' export * from './types' diff --git a/foundations/server/packages/middleware/src/spaceSecurity.ts b/foundations/server/packages/middleware/src/spaceSecurity.ts index ff52d11a72..4c8bd4223e 100644 --- a/foundations/server/packages/middleware/src/spaceSecurity.ts +++ b/foundations/server/packages/middleware/src/spaceSecurity.ts @@ -613,54 +613,6 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar return domain === 'tx' ? 'objectSpace' : domain === 'space' ? '_id' : 'space' } - private mergeDocIdRestriction(query: DocumentQuery, allowed: Ref[]): DocumentQuery { - const allowedIds: DocumentQuery['_id'] = { $in: allowed.length === 0 ? [] : allowed } - const prevId = query._id - if (prevId === undefined) { - return { ...query, _id: allowedIds } - } - type WithAnd = DocumentQuery & { $and?: DocumentQuery[] } - const { _id: _drop, $and, ...rest } = query as WithAnd - const andParts: DocumentQuery[] = [...($and ?? []), { _id: prevId }, { _id: allowedIds }] - const merged: DocumentQuery = { ...rest, $and: andParts } - return merged - } - - private async applyGuestCollaboratorReadRestriction( - ctx: MeasureContext, - _class: Ref>, - domain: Domain, - query: DocumentQuery - ): Promise> { - const account = ctx.contextData.account - if ( - ![AccountRole.Guest, AccountRole.ReadOnlyGuest].includes(account.role) || - isSystem(account, ctx) || - domain === DOMAIN_MODEL - ) { - return query - } - - const collabSec = getClassCollaborators(this.context.modelDb, this.context.hierarchy, _class) - if (collabSec?.provideSecurity !== true) { - return query - } - - const rootClass = collabSec.attachedTo - const docClasses = [...this.context.hierarchy.getDescendants(rootClass), rootClass] - const collabs = (await this.provideFindAll( - ctx, - core.class.Collaborator, - { - collaborator: account.uuid, - attachedToClass: { $in: docClasses } - }, - { projection: { attachedTo: 1 }, limit: 10_000 } - )) as Collaborator[] - const allowed = collabs.map((c) => c.attachedTo) as Ref[] - return this.mergeDocIdRestriction(query, allowed) - } - override async findAll( ctx: MeasureContext, _class: Ref>, @@ -727,10 +679,7 @@ export class SpaceSecurityMiddleware extends BaseMiddleware implements Middlewar } } - let queryToUse = !this.skipFindCheck ? newQuery : query - queryToUse = await this.applyGuestCollaboratorReadRestriction(ctx, _class, domain, queryToUse) - - let findResult = await this.provideFindAll(ctx, _class, queryToUse, options) + let findResult = await this.provideFindAll(ctx, _class, !this.skipFindCheck ? newQuery : query, options) if (clientFilterSpaces !== undefined) { const cfs = clientFilterSpaces findResult = toFindResult( diff --git a/foundations/server/packages/middleware/src/tests/spaceSecurity.test.ts b/foundations/server/packages/middleware/src/tests/spaceSecurity.test.ts deleted file mode 100644 index 62e45c76ec..0000000000 --- a/foundations/server/packages/middleware/src/tests/spaceSecurity.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -// -// Copyright © 2026 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 core, { - AccountRole, - MeasureMetricsContext, - generateId, - toFindResult, - type Account, - type AccountUuid, - type Class, - type ClassCollaborators, - type Collaborator, - type Doc, - type Domain, - type MeasureContext, - type PersonId, - type Ref, - type SessionData -} from '@hcengineering/core' -import type { Middleware, PipelineContext } from '@hcengineering/server-core' -import { SpaceSecurityMiddleware } from '../spaceSecurity' - -const DOC_CLASS = 'test:class:Doc' as Ref> -const DOC_ID = 'test:doc:1' as Ref -const TEST_DOMAIN = 'test-domain' as Domain - -function makeAccount (role: AccountRole): Account { - return { - uuid: generateId() as unknown as AccountUuid, - role, - primarySocialId: 'test-social' as PersonId, - socialIds: ['test-social' as PersonId], - fullSocialIds: [] - } -} - -function makeCtx (account: Account): MeasureContext { - const ctx = new MeasureMetricsContext('test', {}) as MeasureContext - ctx.contextData = { - account, - broadcast: { txes: [], queue: [], sessions: {} }, - socialStringsToUsers: new Map(), - contextCache: new Map() - } as unknown as SessionData - return ctx -} - -function makeMiddleware ( - role: AccountRole, - opts: { provideSecurity: boolean } = { provideSecurity: false } -): { mw: SpaceSecurityMiddleware, account: Account, calls: Array<{ cls: Ref>, query: any }> } { - const account = makeAccount(role) - const calls: Array<{ cls: Ref>, query: any }> = [] - const collabMixin = { - _id: generateId(), - _class: core.class.ClassCollaborators, - space: core.space.Model, - attachedTo: DOC_CLASS, - fields: ['createdBy'], - provideSecurity: opts.provideSecurity, - modifiedOn: Date.now(), - modifiedBy: core.account.System - } as unknown as ClassCollaborators - - // Partial Hierarchy test double — SpaceSecurityMiddleware only needs these methods for this test. - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- full Hierarchy is not needed here - const hierarchy = { - getDomain: (_class: Ref>) => TEST_DOMAIN, - isDerived: (_class: Ref>, base: Ref>) => - base === core.class.Space && _class === core.class.Space, - getDescendants: (_class: Ref>) => [] as Ref>[], - getAncestors: (_class: Ref>) => [_class] - } as PipelineContext['hierarchy'] - - const modelDb = { - findAllSync: (cl: Ref>, query: { attachedTo?: { $in?: Ref>[] } }) => { - if (cl !== core.class.ClassCollaborators) return [] - const ids = query.attachedTo?.$in ?? [] - return ids.includes(DOC_CLASS) ? [collabMixin] : [] - } - } as unknown as PipelineContext['modelDb'] - - const next: Middleware = { - findAll: async (_ctx: MeasureContext, _class: Ref>, query: any) => { - calls.push({ cls: _class as unknown as Ref>, query: JSON.parse(JSON.stringify(query)) }) - if (_class === (core.class.Space as unknown as Ref>)) { - return toFindResult([]) as any - } - if (_class === (core.class.Collaborator as unknown as Ref>)) { - const collabDoc: Collaborator = { - _id: generateId(), - _class: core.class.Collaborator, - space: core.space.Workspace, - attachedTo: DOC_ID, - attachedToClass: DOC_CLASS, - collection: 'collaborators', - collaborator: account.uuid, - modifiedOn: Date.now(), - modifiedBy: core.account.System - } - return toFindResult([collabDoc]) as any - } - return toFindResult([]) as any - }, - tx: async () => ({}), - groupBy: async () => new Map(), - searchFulltext: async () => ({ docs: [] }) as any, - handleBroadcast: async () => {}, - loadModel: async () => [], - domainRequest: async () => ({ value: undefined }) as any, - closeSession: async () => {}, - close: async () => {} - } - - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- minimal PipelineContext stub for unit test - const context = { - workspace: { uuid: generateId() as any, url: 'test', dataId: 'test' as any }, - hierarchy, - modelDb, - branding: null as any, - adapterManager: {} as any, - storageAdapter: {} as any, - contextVars: {}, - lastTx: '', - lastHash: '', - broadcastEvent: async () => {} - } as PipelineContext - - const mw = new (SpaceSecurityMiddleware as any)(false, context, next) as SpaceSecurityMiddleware - return { mw, account, calls } -} - -describe('SpaceSecurityMiddleware guest collaborator read restriction', () => { - it('applies collaborator _id filter for guest when provideSecurity is enabled', async () => { - const { mw, account, calls } = makeMiddleware(AccountRole.Guest, { provideSecurity: true }) - const ctx = makeCtx(account) - - await mw.findAll(ctx, DOC_CLASS, { title: 'Meeting minutes' }) - - expect(calls).toHaveLength(3) - expect(calls[1].cls).toBe(core.class.Collaborator) - expect(calls[2].cls).toBe(DOC_CLASS) - expect(calls[2].query).toEqual({ - title: 'Meeting minutes', - _id: { $in: [DOC_ID] } - }) - }) - - it('keeps query unchanged for regular user', async () => { - const { mw, account, calls } = makeMiddleware(AccountRole.User, { provideSecurity: true }) - const ctx = makeCtx(account) - - await mw.findAll(ctx, DOC_CLASS, { title: 'Meeting minutes' }) - - expect(calls).toHaveLength(2) - expect(calls[1].cls).toBe(DOC_CLASS) - expect(calls[1].query).toEqual({ title: 'Meeting minutes' }) - }) -}) diff --git a/models/card/src/migration.ts b/models/card/src/migration.ts index dedc9a91de..f34279c618 100644 --- a/models/card/src/migration.ts +++ b/models/card/src/migration.ts @@ -16,6 +16,7 @@ import cardPlugin, { cardId, DOMAIN_CARD, type Card, type Role } from '@hcengineering/card' import core, { DOMAIN_MODEL, + SortingOrder, TxOperations, type Class, type ClassPermission, @@ -71,7 +72,7 @@ export const cardOperation: MigrateOperation = { async upgrade (state: Map>, client: () => Promise, mode): Promise { await tryUpgrade(mode, state, client, cardId, [ { - state: 'migrateViewlets-v7', + state: 'migrateViewlets-6', func: migrateViewlets }, { @@ -128,6 +129,11 @@ export const cardOperation: MigrateOperation = { state: 'migrate-restricted-permissions', mode: 'upgrade', func: migrateRestrictedPermissions + }, + { + state: 'add-grid-viewlet', + mode: 'upgrade', + func: addGridViewlet } ]) } @@ -286,6 +292,40 @@ async function migrateRestrictedPermissions (_client: MigrationUpgradeClient): P } } +async function addGridViewlet (client: MigrationUpgradeClient): Promise { + const txOp = new TxOperations(client, core.account.System) + const masterTags = await client.findAll(card.class.MasterTag, {}) + const currentViewlets = await client.findAll(view.class.Viewlet, { + descriptor: card.viewlet.CardGridDescriptor, + attachTo: { $in: masterTags.map((p) => p._id) } + }) + for (const masterTag of masterTags) { + const current = currentViewlets.find((p) => p.attachTo === masterTag._id) + if (current === undefined) { + await txOp.createDoc(view.class.Viewlet, core.space.Model, { + descriptor: card.viewlet.CardGridDescriptor, + baseQuery: { + isLatest: true + }, + config: [''], + configOptions: { + strict: true + }, + viewOptions: { + groupBy: [], + orderBy: [ + ['modifiedOn', SortingOrder.Descending], + ['rank', SortingOrder.Ascending], + ['title', SortingOrder.Descending] + ], + other: [] + }, + attachTo: masterTag._id + }) + } + } +} + async function addVersionForVersionableTypes (client: MigrationUpgradeClient): Promise { const txOp = new TxOperations(client, core.account.System) const versionableTypes = await client.findAll(card.class.MasterTag, {}) diff --git a/models/process/src/functions.ts b/models/process/src/functions.ts index 62f49afee9..5beae3ee06 100644 --- a/models/process/src/functions.ts +++ b/models/process/src/functions.ts @@ -461,6 +461,18 @@ export function defineFunctions (builder: Builder): void { process.function.RoleContext ) + builder.createDoc( + process.class.ProcessFunction, + core.space.Model, + { + of: core.class.TypeAny, + category: 'attribute', + label: process.string.EmptyValue, + type: 'context' + }, + process.function.EmptyValue + ) + builder.createDoc( process.class.ProcessFunction, core.space.Model, diff --git a/models/server-process/src/index.ts b/models/server-process/src/index.ts index ad39002e89..8f53293117 100644 --- a/models/server-process/src/index.ts +++ b/models/server-process/src/index.ts @@ -343,6 +343,10 @@ export function createModel (builder: Builder): void { func: serverProcess.transform.RemoveLast }) + builder.mixin(process.function.EmptyValue, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, { + func: serverProcess.transform.EmptyValue + }) + builder.mixin(process.function.EmptyArray, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, { func: serverProcess.transform.EmptyArray }) diff --git a/plugins/card-resources/src/components/CardGridView.svelte b/plugins/card-resources/src/components/CardGridView.svelte index 6fdad5b8a1..0c758c72d5 100644 --- a/plugins/card-resources/src/components/CardGridView.svelte +++ b/plugins/card-resources/src/components/CardGridView.svelte @@ -13,9 +13,10 @@ // limitations under the License. --> @@ -353,6 +346,8 @@ // TODO UBERF-9639: restore defaults }} on:save={(event) => { + viewlet.config = event.detail + viewlet = viewlet dispatch('update', event.detail) }} /> diff --git a/plugins/communication-resources/src/components/message/activity/ActivitySetAttributeViewer.svelte b/plugins/communication-resources/src/components/message/activity/ActivitySetAttributeViewer.svelte index dc4cfc901a..85758955ab 100644 --- a/plugins/communication-resources/src/components/message/activity/ActivitySetAttributeViewer.svelte +++ b/plugins/communication-resources/src/components/message/activity/ActivitySetAttributeViewer.svelte @@ -31,7 +31,7 @@ {:else} diff --git a/plugins/process-assets/lang/cs.json b/plugins/process-assets/lang/cs.json index 8f39f477f5..5c292d2dfd 100644 --- a/plugins/process-assets/lang/cs.json +++ b/plugins/process-assets/lang/cs.json @@ -163,7 +163,8 @@ "YearFromDate": "Rok z data", "MonthFromDate": "Měsíc z data", "DayFromDate": "Den z data", - "DateDifference": "Rozdíl dat" + "DateDifference": "Rozdíl dat", + "EmptyValue": "Prázdná hodnota" }, "error": { "MethodNotFound": "Metoda nenalezena: {methodId}", diff --git a/plugins/process-assets/lang/de.json b/plugins/process-assets/lang/de.json index 7fef53bfdb..05a1a39f03 100644 --- a/plugins/process-assets/lang/de.json +++ b/plugins/process-assets/lang/de.json @@ -163,7 +163,8 @@ "YearFromDate": "Jahr aus Datum", "MonthFromDate": "Monat aus Datum", "DayFromDate": "Tag aus Datum", - "DateDifference": "Datumsdifferenz" + "DateDifference": "Datumsdifferenz", + "EmptyValue": "Leerer Wert" }, "error": { "MethodNotFound": "Methode nicht gefunden: {methodId}", diff --git a/plugins/process-assets/lang/en.json b/plugins/process-assets/lang/en.json index 63961b48f2..8c1829fddb 100644 --- a/plugins/process-assets/lang/en.json +++ b/plugins/process-assets/lang/en.json @@ -168,7 +168,8 @@ "YearFromDate": "Year from date", "MonthFromDate": "Month from date", "DayFromDate": "Day from date", - "DateDifference": "Date difference" + "DateDifference": "Date difference", + "EmptyValue": "Empty value" }, "error": { "MethodNotFound": "Method not found: {methodId}", diff --git a/plugins/process-assets/lang/es.json b/plugins/process-assets/lang/es.json index 5c0b3b47bc..7d6d098e49 100644 --- a/plugins/process-assets/lang/es.json +++ b/plugins/process-assets/lang/es.json @@ -168,7 +168,8 @@ "YearFromDate": "Año desde fecha", "MonthFromDate": "Mes desde fecha", "DayFromDate": "Día desde fecha", - "DateDifference": "Diferencia de fechas" + "DateDifference": "Diferencia de fechas", + "EmptyValue": "Valor vacío" }, "error": { "MethodNotFound": "Método no encontrado: {methodId}", diff --git a/plugins/process-assets/lang/fr.json b/plugins/process-assets/lang/fr.json index de441fc6cc..c87bec0baa 100644 --- a/plugins/process-assets/lang/fr.json +++ b/plugins/process-assets/lang/fr.json @@ -168,7 +168,8 @@ "YearFromDate": "Année depuis date", "MonthFromDate": "Mois depuis date", "DayFromDate": "Jour depuis date", - "DateDifference": "Différence de dates" + "DateDifference": "Différence de dates", + "EmptyValue": "Valeur vide" }, "error": { "MethodNotFound": "Méthode introuvable : {methodId}", diff --git a/plugins/process-assets/lang/it.json b/plugins/process-assets/lang/it.json index edfa5e14ca..496731ee37 100644 --- a/plugins/process-assets/lang/it.json +++ b/plugins/process-assets/lang/it.json @@ -168,7 +168,8 @@ "YearFromDate": "Anno da data", "MonthFromDate": "Mese da data", "DayFromDate": "Giorno da data", - "DateDifference": "Differenza di date" + "DateDifference": "Differenza di date", + "EmptyValue": "Valore vuoto" }, "error": { "MethodNotFound": "Metodo non trovato: {methodId}", diff --git a/plugins/process-assets/lang/ja.json b/plugins/process-assets/lang/ja.json index 34fa8c7487..80e895f8cc 100644 --- a/plugins/process-assets/lang/ja.json +++ b/plugins/process-assets/lang/ja.json @@ -167,7 +167,8 @@ "YearFromDate": "日付から年", "MonthFromDate": "日付から月", "DayFromDate": "日付から日", - "DateDifference": "日付の差" + "DateDifference": "日付の差", + "EmptyValue": "空の値" }, "error": { "MethodNotFound": "メソッドが見つかりません: {methodId}", diff --git a/plugins/process-assets/lang/pt-br.json b/plugins/process-assets/lang/pt-br.json index c3d676f3dd..c9b7d44cad 100644 --- a/plugins/process-assets/lang/pt-br.json +++ b/plugins/process-assets/lang/pt-br.json @@ -156,7 +156,8 @@ "YearFromDate": "Ano de data", "MonthFromDate": "Mês de data", "DayFromDate": "Dia de data", - "DateDifference": "Diferença de datas" + "DateDifference": "Diferença de datas", + "EmptyValue": "Valor vazio" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/pt.json b/plugins/process-assets/lang/pt.json index e6abcac702..d0fbc58cfa 100644 --- a/plugins/process-assets/lang/pt.json +++ b/plugins/process-assets/lang/pt.json @@ -168,7 +168,8 @@ "YearFromDate": "Ano de data", "MonthFromDate": "Mês de data", "DayFromDate": "Dia de data", - "DateDifference": "Diferença de datas" + "DateDifference": "Diferença de datas", + "EmptyValue": "Valor vazio" }, "error": { "MethodNotFound": "Método não encontrado: {methodId}", diff --git a/plugins/process-assets/lang/ru.json b/plugins/process-assets/lang/ru.json index a3432c1c6e..055a25df36 100644 --- a/plugins/process-assets/lang/ru.json +++ b/plugins/process-assets/lang/ru.json @@ -168,7 +168,8 @@ "YearFromDate": "Год из даты", "MonthFromDate": "Месяц из даты", "DayFromDate": "День из даты", - "DateDifference": "Разница дат" + "DateDifference": "Разница дат", + "EmptyValue": "Пустое значение" }, "error": { "MethodNotFound": "Метод не найден: {methodId}", diff --git a/plugins/process-assets/lang/tr.json b/plugins/process-assets/lang/tr.json index 5cae8eb743..7d85fdd7b3 100644 --- a/plugins/process-assets/lang/tr.json +++ b/plugins/process-assets/lang/tr.json @@ -163,7 +163,8 @@ "YearFromDate": "Tarihten yıl", "MonthFromDate": "Tarihten ay", "DayFromDate": "Tarihten gün", - "DateDifference": "Tarih farkı" + "DateDifference": "Tarih farkı", + "EmptyValue": "Boş değer" }, "error": { "MethodNotFound": "Metod bulunamadı: {methodId}", diff --git a/plugins/process-assets/lang/zh.json b/plugins/process-assets/lang/zh.json index cff0efcaa5..736fed7612 100644 --- a/plugins/process-assets/lang/zh.json +++ b/plugins/process-assets/lang/zh.json @@ -168,7 +168,8 @@ "YearFromDate": "日期到年份", "MonthFromDate": "日期到月份", "DayFromDate": "日期到天", - "DateDifference": "日期差异" + "DateDifference": "日期差异", + "EmptyValue": "空值" }, "error": { "MethodNotFound": "找不到方法:{methodId}", diff --git a/plugins/process-resources/src/plugin.ts b/plugins/process-resources/src/plugin.ts index 74263da445..1d1104fea0 100644 --- a/plugins/process-resources/src/plugin.ts +++ b/plugins/process-resources/src/plugin.ts @@ -233,6 +233,7 @@ export default mergeIds(processId, process, { For: '' as IntlString, Attribute: '' as IntlString, Context: '' as IntlString, + EmptyValue: '' as IntlString, EmptyArray: '' as IntlString, ExecutionInitiator: '' as IntlString, ExecutionStarted: '' as IntlString, diff --git a/plugins/process-resources/src/utils.ts b/plugins/process-resources/src/utils.ts index db0553edb9..059289cecd 100644 --- a/plugins/process-resources/src/utils.ts +++ b/plugins/process-resources/src/utils.ts @@ -331,7 +331,7 @@ function getContextFunctions ( break } default: { - if (hierarchy.isDerived(func.of, target)) { + if (hierarchy.isDerived(func.of, target) || func.of === core.class.TypeAny) { matched.push(func._id) } } diff --git a/plugins/process/src/index.ts b/plugins/process/src/index.ts index d25283d2f5..d735a5c9af 100644 --- a/plugins/process/src/index.ts +++ b/plugins/process/src/index.ts @@ -373,6 +373,7 @@ export default plugin(processId, { ExecutionStarted: '' as Ref, ExecutionEmployeeInitiator: '' as Ref, ExecutionInitiator: '' as Ref, + EmptyValue: '' as Ref, EmptyArray: '' as Ref, CurrentDate: '' as Ref, StringFromNumber: '' as Ref, diff --git a/plugins/support/src/index.ts b/plugins/support/src/index.ts index c472a1ca5a..fd51cc544c 100644 --- a/plugins/support/src/index.ts +++ b/plugins/support/src/index.ts @@ -21,7 +21,7 @@ import { SupportClientFactory, SupportConversation, SupportSystem } from './type export * from './types' export { deleteSupportConversation, updateSupportConversation } from './utils' -export const supportLink = 'https://huly.link/slack' +export const supportLink = 'https://link.huly.io/slack' export const reportBugLink = 'https://github.com/hcengineering/platform/issues/new' export const docsLink = 'http://docs.huly.io/' export const privacyPolicyLink = 'https://v1.huly.io/legal/privacy/' diff --git a/plugins/view-resources/src/components/StringPresenter.svelte b/plugins/view-resources/src/components/StringPresenter.svelte index 495547929e..c246094dfc 100644 --- a/plugins/view-resources/src/components/StringPresenter.svelte +++ b/plugins/view-resources/src/components/StringPresenter.svelte @@ -17,14 +17,14 @@ import { getEmbeddedLabel } from '@hcengineering/platform' import { LabelAndProps, LinkWrapper, tooltip } from '@hcengineering/ui' - export let value: string | string[] | undefined + export let value: string | string[] | null | undefined export let accent: boolean = false export let oneLine: boolean = false $: tooltipParams = getTooltip(value) - function getTooltip (value: string | string[] | undefined): LabelAndProps | undefined { - if (value === undefined) return + function getTooltip (value: string | string[] | null | undefined): LabelAndProps | undefined { + if (value == null) return let str = '' if (Array.isArray(value)) { str = value.reduce((acc, curr, i) => (acc += i === 0 ? curr : ` ${curr}`), '') diff --git a/plugins/workbench-resources/src/components/AppItem.svelte b/plugins/workbench-resources/src/components/AppItem.svelte index 94dc5e768c..dfa0a3efb2 100644 --- a/plugins/workbench-resources/src/components/AppItem.svelte +++ b/plugins/workbench-resources/src/components/AppItem.svelte @@ -25,6 +25,7 @@ export let loading: boolean = false export let notify: boolean = false export let navigator: boolean = false + export let dataId: string | undefined = undefined