Merge branch 'develop' of https://github.com/hcengineering/platform into staging-new

Signed-off-by: Artem Savchenko <armisav@gmail.com>
This commit is contained in:
Artem Savchenko
2026-02-25 11:32:42 +07:00
28 changed files with 235 additions and 23 deletions
+3 -1
View File
@@ -969,4 +969,6 @@ jobs:
run: |
REF="${{ github.event.inputs.ref || github.ref }}"
VERSION="${REF#refs/tags/v}"
node common/scripts/bump.js "$VERSION" --publish
VERSION="${VERSION#v}"
node common/scripts/bump.js "$VERSION"
node common/scripts/safe-publish.js
+10
View File
@@ -2,6 +2,16 @@
Changelog.
## [0.7.375] - 2026-02-23
* 🚀 FEATURES: · Add activity section in controlled doc ([#10457](https://github.com/hcengineering/platform/issues/10457)) · Add data converter plugin ([#10460](https://github.com/hcengineering/platform/issues/10460)) · *(ui)* Add Turkish language to settings popup ([#10441](https://github.com/hcengineering/platform/issues/10441)) · Turkish language in dev setup ([#10462](https://github.com/hcengineering/platform/issues/10462)) · Add import notifications ([#10464](https://github.com/hcengineering/platform/issues/10464)) · Guest users to upload attachments and use reactions ([#10469](https://github.com/hcengineering/platform/issues/10469)) · Add activity input for controlled docs ([#10470](https://github.com/hcengineering/platform/issues/10470)) · Ability to select multiple docs and to export to pdf ([#10468](https://github.com/hcengineering/platform/issues/10468)) · Headers legibility ([#10473](https://github.com/hcengineering/platform/issues/10473)) · Brazilian Portuguese translation ([#10478](https://github.com/hcengineering/platform/issues/10478)) · Add drive permissions ([#10489](https://github.com/hcengineering/platform/issues/10489)) · Add unlock card and section functionality with UI updates ([#10492](https://github.com/hcengineering/platform/issues/10492)) · To define different viewlet actions ([#10502](https://github.com/hcengineering/platform/issues/10502)) · Add ability to copy all data from cards/docs tables ([#10505](https://github.com/hcengineering/platform/issues/10505)) · Redesign workspace join ([#10507](https://github.com/hcengineering/platform/issues/10507)) · For guests to update notification settings ([#10517](https://github.com/hcengineering/platform/issues/10517)) · Add project breadcrumb ([#10515](https://github.com/hcengineering/platform/issues/10515)) · Implement process import/export functionality, enhance process … ([#10528](https://github.com/hcengineering/platform/issues/10528)) · Implement CancelSubProcess action, fix process execution flow. ([#10530](https://github.com/hcengineering/platform/issues/10530)) · Add ability to group by issues by project ([#10531](https://github.com/hcengineering/platform/issues/10531))
* 🐛 BUG FIXES: · Okit.graphql is not a function ([#10442](https://github.com/hcengineering/platform/issues/10442)) · Custom attributes in markdown table ([#10445](https://github.com/hcengineering/platform/issues/10445)) · Relationship table diff/refresh ([#10446](https://github.com/hcengineering/platform/issues/10446)) · Handle youtu.be links ([#10458](https://github.com/hcengineering/platform/issues/10458)) · Calendar timezone day-shift bug ([#7048](https://github.com/hcengineering/platform/issues/7048)) ([#10459](https://github.com/hcengineering/platform/issues/10459)) · Mute account unauthorized error ([#10461](https://github.com/hcengineering/platform/issues/10461)) · Do not write analytics event when no token ([#10463](https://github.com/hcengineering/platform/issues/10463)) · Documents space export ([#10465](https://github.com/hcengineering/platform/issues/10465)) · Print layout ([#10472](https://github.com/hcengineering/platform/issues/10472)) · Use the error logging level for expected situations ([#10474](https://github.com/hcengineering/platform/issues/10474)) · Allow to open mermaid diagram in fullscreen ([#10477](https://github.com/hcengineering/platform/issues/10477)) · Rows order in relationsheep table compare ([#10479](https://github.com/hcengineering/platform/issues/10479)) · Custom attributes display in original view ([#10480](https://github.com/hcengineering/platform/issues/10480)) · Card formatting in markdown ([#10475](https://github.com/hcengineering/platform/issues/10475)) · Proper mermaid diagram size ([#10487](https://github.com/hcengineering/platform/issues/10487)) · Embed pdf via direct link ([#10491](https://github.com/hcengineering/platform/issues/10491)) · Secure blobs ([#10490](https://github.com/hcengineering/platform/issues/10490)) · Relationship table refresh ([#10494](https://github.com/hcengineering/platform/issues/10494)) · Issues layout ([#10498](https://github.com/hcengineering/platform/issues/10498)) · Exception during github issue id reading ([#10500](https://github.com/hcengineering/platform/issues/10500)) · Email notifications for export ([#10506](https://github.com/hcengineering/platform/issues/10506)) · Prevent datalake from error flood ([#10521](https://github.com/hcengineering/platform/issues/10521)) · Brazilian Portuguese translation ([#10522](https://github.com/hcengineering/platform/issues/10522)) · Datalake in dev setup does not support secure mode ([#10525](https://github.com/hcengineering/platform/issues/10525)) · Date formatting in md table ([#10523](https://github.com/hcengineering/platform/issues/10523)) · Bump with foundation packages ([#10536](https://github.com/hcengineering/platform/issues/10536)) · Process ([#10543](https://github.com/hcengineering/platform/issues/10543))
* 🚜 REFACTOR: · One metadata interface ([#10444](https://github.com/hcengineering/platform/issues/10444))
* ⚙️ MISCELLANEOUS TASKS: · Changelog · Changelog ([#10439](https://github.com/hcengineering/platform/issues/10439)) · Mail URL from export pod ([#10476](https://github.com/hcengineering/platform/issues/10476)) · Etc/hosts path for Windows ([#10482](https://github.com/hcengineering/platform/issues/10482)) · Space security and permissions ([#10529](https://github.com/hcengineering/platform/issues/10529)) · Kvs and pulse versions ([#10539](https://github.com/hcengineering/platform/issues/10539)) · And publish npm packages for v tags ([#10542](https://github.com/hcengineering/platform/issues/10542))
* FIX: · Images not rendering on GitHub side of GitHub integration ([#10520](https://github.com/hcengineering/platform/issues/10520)) · Show print actions for individual documents ([#10484](https://github.com/hcengineering/platform/issues/10484)) · Password signup crashes with JSON parse error when MAIL_URL is configured ([#10519](https://github.com/hcengineering/platform/issues/10519))
* QFIX: · Allow notifications for guests with collab security ([#10510](https://github.com/hcengineering/platform/issues/10510))
* QFIX: · Remove unused ui dependencies ([#10511](https://github.com/hcengineering/platform/issues/10511))
## [0.7.353] - 2026-01-24
* 🚀 FEATURES: · Link preview service ([#10424](https://github.com/hcengineering/platform/issues/10424)) · Add confirmation modal for table refresh ([#10428](https://github.com/hcengineering/platform/issues/10428)) · Guest update profile (avatar, name etc) ([#10429](https://github.com/hcengineering/platform/issues/10429))
+18 -2
View File
@@ -43,8 +43,24 @@ function checkPackageExists(packageName, version) {
*/
function getPublishablePackages(includePattern) {
try {
const output = execSync('rush list -p --json', { encoding: 'utf-8' })
const config = JSON.parse(output)
const repoRoot = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim()
const output = execSync('node common/scripts/install-run-rush.js list -p --json', {
encoding: 'utf-8',
cwd: repoRoot
})
const lines = output.split('\n')
let jsonStart = -1
for (let i = 0; i < lines.length; i++) {
if (lines[i].trim().startsWith('{')) {
jsonStart = i
break
}
}
if (jsonStart === -1) {
console.error('Could not find JSON output from rush list')
return []
}
const config = JSON.parse(lines.slice(jsonStart).join('\n'))
return config.projects.filter((project) => {
// Check if package should be published according to rush.json
+4 -1
View File
@@ -436,7 +436,10 @@ export abstract class TxProcessor implements WithTx {
}
static txHasUpdate<T extends Doc>(tx: TxUpdateDoc<T>, attribute: string): boolean {
const ops = tx.operations
return TxProcessor.hasUpdate(tx.operations, attribute)
}
static hasUpdate<T extends Doc>(ops: DocumentUpdate<T>, attribute: string): boolean {
if ((ops as any)[attribute] !== undefined) return true
for (const op in ops) {
if (op.startsWith('$')) {
+3
View File
@@ -103,6 +103,9 @@ export class TProcess extends TDoc implements Process {
@Prop(TypeBoolean(), process.string.StartAutomatically)
autoStart: boolean | undefined
@Prop(TypeBoolean(), process.string.AutomationOnly)
automationOnly: boolean | undefined
context!: Record<ContextId, ProcessContext>
}
@@ -0,0 +1,52 @@
<!--
// 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.
-->
<script lang="ts">
import { onDestroy } from 'svelte'
import EmbeddedPDF from './EmbeddedPDF.svelte'
import Loading from './Loading.svelte'
export let src: string
export let name: string
export let fit: boolean = false
export let css: string | undefined = undefined
let iframeSrc: string | undefined
async function loadFile (src: string): Promise<void> {
if (iframeSrc !== undefined) {
URL.revokeObjectURL(iframeSrc)
iframeSrc = undefined
}
const response = await fetch(src)
const blob = await response.blob()
iframeSrc = URL.createObjectURL(blob)
}
$: void loadFile(src)
onDestroy(() => {
if (iframeSrc !== undefined) {
URL.revokeObjectURL(iframeSrc)
}
})
</script>
{#if iframeSrc}
<EmbeddedPDF src={iframeSrc} {name} {fit} {css} />
{:else}
<Loading />
{/if}
+1
View File
@@ -284,6 +284,7 @@ export { default as CodeForm } from './components/CodeForm.svelte'
export { default as CodeInput } from './components/CodeInput.svelte'
export { default as TimeLeft } from './components/TimeLeft.svelte'
export { default as SectionEmpty } from './components/SectionEmpty.svelte'
export { default as EmbeddedHTML } from './components/EmbeddedHTML.svelte'
export { default as EmbeddedPDF } from './components/EmbeddedPDF.svelte'
export { default as NestedMenu } from './components/NestedMenu.svelte'
export { default as NestedDropdown } from './components/NestedDropdown.svelte'
@@ -9,7 +9,7 @@
import { getMetadata } from '@hcengineering/platform'
import presentation, { getFileUrl } from '@hcengineering/presentation'
import { convertToHTML } from '@hcengineering/print'
import { EmbeddedPDF, Spinner, themeStore } from '@hcengineering/ui'
import { EmbeddedHTML, Spinner, themeStore } from '@hcengineering/ui'
export let value: Ref<Blob>
export let name: string
@@ -270,7 +270,7 @@
<Spinner size="medium" />
</div>
{:else}
<EmbeddedPDF {src} {name} {css} />
<EmbeddedHTML {src} {name} {css} />
{/if}
{/if}
+1
View File
@@ -45,6 +45,7 @@
"Required": "Povinné",
"ParallelExecutionForbidden": "Nedovoluj paralelní spuštění",
"StartAutomatically": "Spustit automaticky",
"AutomationOnly": "Pouze automatizace",
"Error": "Chyba",
"Continue": "Pokračovat",
"RequestFromUser": "Požádat uživatele",
+1
View File
@@ -45,6 +45,7 @@
"Required": "Erforderlich",
"ParallelExecutionForbidden": "Parallele Ausführung verboten",
"StartAutomatically": "Automatisch starten",
"AutomationOnly": "Nur Automatisierung",
"Error": "Fehler",
"Continue": "Fortsetzen",
"RequestFromUser": "Anfrage von Benutzer",
+1
View File
@@ -45,6 +45,7 @@
"Required": "Required",
"ParallelExecutionForbidden": "Parallel execution forbidden",
"StartAutomatically": "Start automatically",
"AutomationOnly": "Automation only",
"Error": "Error",
"Continue": "Continue",
"RequestFromUser": "Request from user",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Requerido",
"ParallelExecutionForbidden": "Ejecución paralela prohibida",
"StartAutomatically": "Iniciar automáticamente",
"AutomationOnly": "Solo automatización",
"Error": "Error",
"Continue": "Continuar",
"RequestFromUser": "Solicitar desde el usuario",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Requis",
"ParallelExecutionForbidden": "Exécution parallèle interdite",
"StartAutomatically": "Démarrer automatiquement",
"AutomationOnly": "Automatisation uniquement",
"Error": "Erreur",
"Continue": "Continuer",
"RequestFromUser": "Demander à partir de l'utilisateur",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Obbligatorio",
"ParallelExecutionForbidden": "Esecuzione parallela vietata",
"StartAutomatically": "Avvia automaticamente",
"AutomationOnly": "Solo automazione",
"Error": "Errore",
"Continue": "Continua",
"RequestFromUser": "Richiedi da utente",
+1
View File
@@ -49,6 +49,7 @@
"Required": "必須",
"ParallelExecutionForbidden": "並列実行は禁止されています",
"StartAutomatically": "自動的に開始",
"AutomationOnly": "自動化のみ",
"Error": "エラー",
"Continue": "続行",
"RequestFromUser": "ユーザーからのリクエスト",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Obrigatório",
"ParallelExecutionForbidden": "Execução Paralela Proibida",
"StartAutomatically": "Iniciar automaticamente",
"AutomationOnly": "Apenas automação",
"Error": "Erro",
"Continue": "Continuar",
"RequestFromUser": "Solicitar do usuário",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Obrigatório",
"ParallelExecutionForbidden": "Execução Paralela Proibida",
"StartAutomatically": "Iniciar automaticamente",
"AutomationOnly": "Apenas automação",
"Error": "Erro",
"Continue": "Continuar",
"RequestFromUser": "Solicitar do usuário",
+1
View File
@@ -49,6 +49,7 @@
"Required": "Обязательно",
"ParallelExecutionForbidden": "Одновременное выполнение запрещено",
"StartAutomatically": "Запускать автоматически",
"AutomationOnly": "Только автоматизация",
"Error": "Ошибка",
"Continue": "Продолжить",
"RequestFromUser": "Запросить у пользователя",
+1
View File
@@ -45,6 +45,7 @@
"Required": "Gerekli",
"ParallelExecutionForbidden": "Paralel yürütme yasak",
"StartAutomatically": "Otomatik başlat",
"AutomationOnly": "Sadece otomasyon",
"Error": "Hata",
"Continue": "Devam et",
"RequestFromUser": "Kullanıcıdan talep et",
+1
View File
@@ -49,6 +49,7 @@
"Required": "必需",
"ParallelExecutionForbidden": "禁止并行执行",
"StartAutomatically": "自动启动",
"AutomationOnly": "仅限自动化",
"Error": "错误",
"Continue": "继续",
"RequestFromUser": "从用户请求",
@@ -30,7 +30,10 @@
let process: Ref<Process> | undefined = value
let card: Card | undefined
const processes = client.getModel().findAllSync(plugin.class.Process, {})
const processes = client
.getModel()
.findAllSync(plugin.class.Process, {})
.filter((p) => p.automationOnly !== true)
const items: ListItem[] = processes.map((p) => ({ label: p.name, _id: p._id }))
@@ -32,7 +32,7 @@
const resClasses = getPossibleClasses()
const res = client.getModel().findAllSync(process.class.Process, {})
const processes = res.filter((it) => resClasses.includes(it.masterTag))
const processes = res.filter((it) => resClasses.includes(it.masterTag) && it.automationOnly !== true)
function getCardPossibleClasses (card: Card): Ref<Class<Doc>>[] {
const asc = h.getAncestors(card._class)
@@ -35,6 +35,12 @@
await client.update(value, { autoStart: e.detail })
}
}
async function saveAutomationOnly (e: CustomEvent<boolean>): Promise<void> {
if (value !== undefined) {
await client.update(value, { automationOnly: e.detail })
}
}
</script>
<Card
@@ -54,5 +60,16 @@
label={process.string.ParallelExecutionForbidden}
/>
</div>
<ToggleWithLabel on={value.autoStart ?? false} on:change={saveAutoStart} label={process.string.StartAutomatically} />
<div class="mb-2">
<ToggleWithLabel
on={value.autoStart ?? false}
on:change={saveAutoStart}
label={process.string.StartAutomatically}
/>
</div>
<ToggleWithLabel
on={value.automationOnly ?? false}
on:change={saveAutomationOnly}
label={process.string.AutomationOnly}
/>
</Card>
+94 -7
View File
@@ -1,21 +1,37 @@
import { type MasterTag } from '@hcengineering/card'
import core, { type Doc, type Ref, type Class, generateId } from '@hcengineering/core'
import card from '@hcengineering/card'
import core, {
type Doc,
type Ref,
type Class,
generateId,
type EnumOf,
type RefTo,
type ArrOf,
type ModelDb,
type Hierarchy
} from '@hcengineering/core'
import { getClient } from '@hcengineering/presentation'
import process from './plugin'
import { type Process, type Transition } from '@hcengineering/process'
import processPlugin from './plugin'
export function exportProcesses (_id: Ref<MasterTag>): {
docs: Doc[]
required: Array<Ref<Class<Doc>>>
} {
const docs: Doc[] = []
const required: Array<Ref<Class<Doc>>> = []
const client = getClient()
const m = client.getModel()
const processes = m.findAllSync(process.class.Process, { masterTag: _id })
const processes = m.findAllSync(processPlugin.class.Process, { masterTag: _id })
for (const proc of processes) {
const res = exportProcess(proc)
docs.push(...res.docs)
for (const req of res.required) {
if (!required.includes(req)) required.push(req)
}
}
return { docs, required: [] }
return { docs, required }
}
export function exportProcess (proc: Doc): {
@@ -23,13 +39,84 @@ export function exportProcess (proc: Doc): {
required: Array<Ref<Class<Doc>>>
} {
const docs: Doc[] = [proc]
const required: Array<Ref<Class<Doc>>> = []
const client = getClient()
const m = client.getModel()
const h = client.getHierarchy()
docs.push(...m.findAllSync(process.class.State, { process: proc._id as any }))
docs.push(...m.findAllSync(process.class.Transition, { process: proc._id as any }))
docs.push(...m.findAllSync(processPlugin.class.State, { process: proc._id as any }))
const transitions = m.findAllSync(processPlugin.class.Transition, { process: proc._id as any }) as Transition[]
docs.push(...transitions)
return { docs, required: [] }
const processDoc = proc as Process
for (const tr of transitions) {
processParams(tr.triggerParams, processDoc.masterTag, docs, required, m, h)
for (const action of tr.actions) {
processParams(action.params, processDoc.masterTag, docs, required, m, h)
}
}
return { docs, required }
}
function processParams (
params: Record<string, any> | undefined,
masterTag: Ref<MasterTag>,
docs: Doc[],
required: Array<Ref<Class<Doc>>>,
m: ModelDb,
h: Hierarchy
): void {
if (params === undefined) return
for (const key in params) {
if (key.startsWith('$')) {
const val = params[key]
if (Array.isArray(val)) {
for (const item of val) {
if (typeof item === 'object' && item !== null) {
processParams(item, masterTag, docs, required, m, h)
}
}
} else if (typeof val === 'object' && val !== null) {
processParams(val, masterTag, docs, required, m, h)
}
continue
}
const attr = h.findAttribute(masterTag, key)
if (attr !== undefined) {
const type = attr.type
if (type._class === core.class.EnumOf) {
const enumRef = (type as EnumOf).of
const enumDoc = m.findObject(enumRef)
if (enumDoc !== undefined && !docs.some((d) => d._id === enumDoc._id)) {
docs.push(enumDoc)
}
}
if (type._class === core.class.RefTo) {
const to = (type as RefTo<Doc>).to
if (h.isDerived(to, card.class.Card) && !required.includes(to)) {
required.push(to)
}
}
if (type._class === core.class.ArrOf) {
const of = (type as ArrOf<Doc>).of
if (of._class === core.class.EnumOf) {
const enumRef = (of as EnumOf).of
const enumDoc = m.findObject(enumRef)
if (enumDoc !== undefined && !docs.some((d) => d._id === enumDoc._id)) {
docs.push(enumDoc)
}
}
if (of._class === core.class.RefTo) {
const to = (of as RefTo<Doc>).to
if (h.isDerived(to, card.class.Card) && !required.includes(to)) {
required.push(to)
}
}
}
}
}
}
export async function importProcess (masterTag: Ref<MasterTag>, json: string): Promise<void> {
+1
View File
@@ -181,6 +181,7 @@ export default mergeIds(processId, process, {
Required: '' as IntlString,
ParallelExecutionForbidden: '' as IntlString,
StartAutomatically: '' as IntlString,
AutomationOnly: '' as IntlString,
Continue: '' as IntlString,
RequestFromUser: '' as IntlString,
EnterValue: '' as IntlString,
+8 -5
View File
@@ -20,12 +20,14 @@ import core, {
type Client,
type Doc,
type DocumentQuery,
type DocumentUpdate,
generateId,
matchQuery,
type Ref,
type RefTo,
type Space,
type TxOperations,
TxProcessor,
type Type
} from '@hcengineering/core'
import { getResource, type IntlString, PlatformError, Severity, Status } from '@hcengineering/platform'
@@ -207,9 +209,9 @@ export function getContext (
}
}
const allRelations = client.getModel().findAllSync(core.class.Association, {})
const descendants = new Set(client.getHierarchy().getDescendants(process.masterTag))
const ancestors = new Set(client.getHierarchy().getAncestors(process.masterTag))
const relationsA = allRelations.filter((it) => descendants.has(it.classA))
const relationsA = allRelations.filter((it) => ancestors.has(it.classA))
for (const rel of relationsA) {
if (['object', 'array'].includes(category) && client.getHierarchy().isDerived(rel.classB, target)) {
relations[rel.nameB] = {
@@ -231,7 +233,7 @@ export function getContext (
}
}
const relationsB = allRelations.filter((it) => descendants.has(it.classB))
const relationsB = allRelations.filter((it) => ancestors.has(it.classB))
for (const rel of relationsB) {
if (['object', 'array'].includes(category) && client.getHierarchy().isDerived(rel.classA, target)) {
relations[rel.nameA] = {
@@ -683,8 +685,9 @@ export function fieldChangesCheck (
): boolean {
const doc = context.card
if (doc === undefined) return false
const param = Object.keys(params)[0]
if (!Object.keys(context.operations ?? {}).includes(param)) return false
const operations = (context.operations ?? {}) as DocumentUpdate<Doc>
const target = Object.keys(params)[0]
if (!TxProcessor.hasUpdate(operations, target)) return false
const res = matchQuery([doc], params, doc._class, client.getHierarchy(), true)
return res.length > 0
}
+1
View File
@@ -31,6 +31,7 @@ export interface Process extends Doc {
description: string
parallelExecutionForbidden?: boolean
autoStart?: boolean
automationOnly?: boolean
context: Record<ContextId, ProcessContext>
resultType?: Type<any>
}
@@ -20,6 +20,7 @@ import core, {
Class,
Data,
Doc,
DocumentUpdate,
fillDefaults,
findProperty,
generateId,
@@ -169,9 +170,9 @@ export function FieldChangedCheck (
const process = control.client.getModel().findObject(execution.process)
if (process === undefined) return false
if (context.operations === undefined) return false
const changedFields = Object.keys(context.operations)
const operations = context.operations as DocumentUpdate<Doc>
const target = Object.keys(params)[0]
if (!changedFields.includes(target)) return false
if (!TxProcessor.hasUpdate(operations, target)) return false
const res = matchQuery([context.card], params, process.masterTag, control.client.getHierarchy(), true)
return res.length > 0
}