mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-22 09:35:00 +02:00
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:
@@ -1,6 +1,6 @@
|
||||
services:
|
||||
cockroach:
|
||||
image: cockroachdb/cockroach:latest-v24.2
|
||||
image: cockroachdb/cockroach:latest-v24.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
ports:
|
||||
@@ -11,7 +11,7 @@ services:
|
||||
- cockroach_db:/cockroach/cockroach-data
|
||||
restart: unless-stopped
|
||||
redpanda:
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v24.3.6
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v25.2.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
command:
|
||||
|
||||
+22
-3
@@ -41,7 +41,7 @@ services:
|
||||
- 4040:4040
|
||||
restart: unless-stopped
|
||||
cockroach:
|
||||
image: cockroachdb/cockroach:latest-v24.2
|
||||
image: cockroachdb/cockroach:latest-v24.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
ports:
|
||||
@@ -52,7 +52,7 @@ services:
|
||||
- cockroach_db:/cockroach/cockroach-data
|
||||
restart: unless-stopped
|
||||
redpanda:
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v24.3.6
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v25.2.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
command:
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
redpanda_console:
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.3
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.10
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
entrypoint: /bin/sh
|
||||
@@ -499,6 +499,25 @@ services:
|
||||
- QUEUE_CONFIG=${QUEUE_CONFIG}
|
||||
- OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318/v1/traces
|
||||
restart: unless-stopped
|
||||
hulylake:
|
||||
image: hardcoreeng/hulylake
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
cockroach:
|
||||
condition: service_started
|
||||
ports:
|
||||
- 8096:8096
|
||||
environment:
|
||||
- HULY_TOKEN_SECRET=secret
|
||||
- HULY_DB_CONNECTION=${DB_CR_URL}
|
||||
- AWS_ENDPOINT_URL=http://minio:9000
|
||||
- AWS_REGION=local
|
||||
- AWS_ACCESS_KEY_ID=minioadmin
|
||||
- AWS_SECRET_ACCESS_KEY=minioadmin
|
||||
restart: unless-stopped
|
||||
hulykvs:
|
||||
image: hardcoreeng/hulykvs
|
||||
depends_on:
|
||||
|
||||
+41
-13
@@ -671,7 +671,8 @@ export function createModel (builder: Builder): void {
|
||||
of: core.class.ArrOf,
|
||||
category: 'array',
|
||||
label: process.string.Insert,
|
||||
editor: process.transformEditor.ArrayElementEditor,
|
||||
allowMany: true,
|
||||
editor: process.transformEditor.MultiArrayElementEditor,
|
||||
type: 'transform'
|
||||
},
|
||||
process.function.Insert
|
||||
@@ -684,6 +685,7 @@ export function createModel (builder: Builder): void {
|
||||
of: core.class.ArrOf,
|
||||
category: 'array',
|
||||
label: process.string.Remove,
|
||||
allowMany: true,
|
||||
editor: process.transformEditor.ArrayElementEditor,
|
||||
type: 'transform'
|
||||
},
|
||||
@@ -697,6 +699,7 @@ export function createModel (builder: Builder): void {
|
||||
of: core.class.ArrOf,
|
||||
category: 'array',
|
||||
label: process.string.RemoveFirst,
|
||||
allowMany: true,
|
||||
type: 'transform'
|
||||
},
|
||||
process.function.RemoveFirst
|
||||
@@ -709,18 +712,7 @@ export function createModel (builder: Builder): void {
|
||||
of: core.class.ArrOf,
|
||||
category: 'array',
|
||||
label: process.string.RemoveLast,
|
||||
type: 'transform'
|
||||
},
|
||||
process.function.RemoveLast
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.ArrOf,
|
||||
category: 'array',
|
||||
label: process.string.RemoveLast,
|
||||
allowMany: true,
|
||||
type: 'transform'
|
||||
},
|
||||
process.function.RemoveLast
|
||||
@@ -739,6 +731,42 @@ export function createModel (builder: Builder): void {
|
||||
process.function.RoleContext
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: contact.class.Person,
|
||||
category: 'attribute',
|
||||
label: process.string.CurrentUser,
|
||||
type: 'context'
|
||||
},
|
||||
process.function.CurrentUser
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: contact.mixin.Employee,
|
||||
category: 'attribute',
|
||||
label: process.string.CurrentUser,
|
||||
type: 'context'
|
||||
},
|
||||
process.function.CurrentEmployee
|
||||
)
|
||||
|
||||
builder.createDoc(
|
||||
process.class.ProcessFunction,
|
||||
core.space.Model,
|
||||
{
|
||||
of: core.class.TypeDate,
|
||||
category: 'attribute',
|
||||
label: process.string.CurrentDate,
|
||||
type: 'context'
|
||||
},
|
||||
process.function.CurrentDate
|
||||
)
|
||||
|
||||
builder.mixin(process.class.Process, core.class.Class, view.mixin.AttributePresenter, {
|
||||
presenter: process.component.ProcessPresenter
|
||||
})
|
||||
|
||||
@@ -195,6 +195,18 @@ export function createModel (builder: Builder): void {
|
||||
func: serverProcess.transform.RoleContext
|
||||
})
|
||||
|
||||
builder.mixin(process.function.CurrentDate, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.CurrentDate
|
||||
})
|
||||
|
||||
builder.mixin(process.function.CurrentEmployee, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.CurrentUser
|
||||
})
|
||||
|
||||
builder.mixin(process.function.CurrentUser, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.CurrentUser
|
||||
})
|
||||
|
||||
builder.mixin(process.function.Insert, process.class.ProcessFunction, serverProcess.mixin.FuncImpl, {
|
||||
func: serverProcess.transform.Insert
|
||||
})
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
export let label: IntlString
|
||||
export let value: string | undefined = undefined
|
||||
export let kind: 'default' | 'ghost' | 'secondary' = 'default'
|
||||
export let kind: 'default' | 'ghost' | 'secondary' | 'transparent' = 'default'
|
||||
export let size: 'small' | 'medium' | 'large' = 'small'
|
||||
export let disabled: boolean = false
|
||||
export let error: boolean = false
|
||||
@@ -185,6 +185,23 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&.transparent {
|
||||
&.small,
|
||||
&.medium {
|
||||
// medium/ghost - not designed
|
||||
padding: var(--spacing-1_5) var(--spacing-2);
|
||||
height: var(--spacing-5);
|
||||
}
|
||||
&.large {
|
||||
padding: var(--spacing-1) var(--spacing-2);
|
||||
height: var(--spacing-6);
|
||||
|
||||
input {
|
||||
font-weight: 500;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
box-shadow: inset 0 0 0 1px var(--input-error-BorderColor);
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
export let enableAttachments: boolean = true
|
||||
export let isScrollable = true
|
||||
export let kitOptions: Partial<EditorKitOptions> = {}
|
||||
export let fullWidth = false
|
||||
|
||||
export let useDirectAttachDelete = false
|
||||
export let boundary: HTMLElement | undefined = undefined
|
||||
@@ -373,6 +374,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
export function getAttachments (): Attachment[] {
|
||||
return Array.from(attachments.values())
|
||||
}
|
||||
|
||||
$: dispatch('attachments', {
|
||||
size: attachments.size,
|
||||
values: attachments.size === 0 ? true : attachments
|
||||
@@ -408,6 +413,7 @@
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div
|
||||
class="flex-col clear-mins"
|
||||
class:w-full={fullWidth}
|
||||
on:paste={(ev) => pasteAction(ev)}
|
||||
on:dragover|preventDefault={() => {}}
|
||||
on:dragleave={() => {}}
|
||||
@@ -435,7 +441,9 @@
|
||||
on:blur
|
||||
on:focus
|
||||
{attachFile}
|
||||
/>
|
||||
>
|
||||
<slot name="actions" slot="actions" />
|
||||
</StyledTextBox>
|
||||
{#if attachments.size > 0 && enableAttachments}
|
||||
<AttachmentsGrid
|
||||
attachments={Array.from(attachments.values())}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Rozšíření pro správu znalostí ve stylu databáze.",
|
||||
"AddCollaborators": "Přidat spolupracovníky",
|
||||
"Home": "Domů",
|
||||
"CardTitle": "Zadejte název karty...",
|
||||
"CardTitle": "Název",
|
||||
"Post": "Přidat",
|
||||
"Compact": "Kompaktní",
|
||||
"Comfortable": "Komfortní",
|
||||
"Comfortable2": "Komfortní 2",
|
||||
"AdvancedCreateCard": "Přepnout na pokročilé vytváření karet"
|
||||
"AdvancedCreateCard": "Skrýt detaily",
|
||||
"CardContent": "Co chcete sdílet?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Erweiterung für Datenbank-basiertes Wissensmanagement.",
|
||||
"AddCollaborators": "Mitarbeiter hinzufügen",
|
||||
"Home": "Startseite",
|
||||
"CardTitle": "Geben Sie einen Kartentitel ein...",
|
||||
"CardTitle": "Titel",
|
||||
"Post": "Beitrag",
|
||||
"Compact": "Kompakt",
|
||||
"Comfortable": "Komfortabel",
|
||||
"Comfortable2": "Komfortabel 2",
|
||||
"AdvancedCreateCard": "Zum erweiterten Kartenerstellungsmodus wechseln"
|
||||
"AdvancedCreateCard": "Weniger anzeigen",
|
||||
"CardContent": "Was möchten Sie teilen?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Extension for database-style knowledge management.",
|
||||
"AddCollaborators": "Add collaborators",
|
||||
"Home": "Home",
|
||||
"CardTitle": "Enter a card title...",
|
||||
"CardTitle": "Title",
|
||||
"Post": "Post",
|
||||
"Compact": "Compact",
|
||||
"Comfortable": "Comfortable",
|
||||
"Comfortable2": "Comfortable 2",
|
||||
"AdvancedCreateCard": "Switch to advanced card creation"
|
||||
"AdvancedCreateCard": "Show less",
|
||||
"CardContent": "What do you want to share?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Extensión para la gestión del conocimiento al estilo de una base de datos.",
|
||||
"AddCollaborators": "Agregar colaboradores",
|
||||
"Home": "Inicio",
|
||||
"CardTitle": "Introduce un título para la tarjeta...",
|
||||
"CardTitle": "Título",
|
||||
"Post": "Publicar",
|
||||
"Compact": "Compacto",
|
||||
"Comfortable": "Confortable",
|
||||
"Comfortable2": "Confortable 2",
|
||||
"AdvancedCreateCard": "Cambiar a creación avanzada de tarjetas"
|
||||
"AdvancedCreateCard": "Mostrar menos",
|
||||
"CardContent": "¿Qué quieres compartir?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Extension pour la gestion des connaissances de style base de données.",
|
||||
"AddCollaborators": "Ajouter des collaborateurs",
|
||||
"Home": "Accueil",
|
||||
"CardTitle": "Entrez un titre de carte...",
|
||||
"CardTitle": "Titre",
|
||||
"Post": "Publier",
|
||||
"Compact": "Compact",
|
||||
"Comfortable": "Confortable",
|
||||
"Comfortable2": "Confortable 2",
|
||||
"AdvancedCreateCard": "Passer à la création avancée de cartes"
|
||||
"AdvancedCreateCard": "Afficher moins",
|
||||
"CardContent": "Que voulez-vous partager ?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Estensione per la gestione della conoscenza in stile database.",
|
||||
"AddCollaborators": "Aggiungi collaboratori",
|
||||
"Home": "Home",
|
||||
"CardTitle": "Inserisci un titolo per la carta...",
|
||||
"CardTitle": "Titolo",
|
||||
"Post": "Pubblica",
|
||||
"Compact": "Compatto",
|
||||
"Comfortable": "Confortabile",
|
||||
"Comfortable2": "Confortabile 2",
|
||||
"AdvancedCreateCard": "Passa alla creazione avanzata della carta"
|
||||
"AdvancedCreateCard": "Mostra meno",
|
||||
"CardContent": "Cosa vuoi condividere?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "データベーススタイルのナレッジマネジメント用の拡張機能。",
|
||||
"AddCollaborators": "コラボレーターを追加",
|
||||
"Home": "ホーム",
|
||||
"CardTitle": "カードのタイトルを入力...",
|
||||
"CardTitle": "タイトル",
|
||||
"Post": "投稿",
|
||||
"Compact": "コンパクト",
|
||||
"Comfortable": "コンファンティブ",
|
||||
"Comfortable2": "コンファンティブ 2",
|
||||
"AdvancedCreateCard": "高度なカード作成に切り替え"
|
||||
"AdvancedCreateCard": "詳細を隠す",
|
||||
"CardContent": "何を共有しますか?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Extensão para gerenciamento de conhecimento no estilo de banco de dados.",
|
||||
"AddCollaborators": "Adicionar colaboradores",
|
||||
"Home": "Início",
|
||||
"CardTitle": "Digite um título para o cartão...",
|
||||
"CardTitle": "Título",
|
||||
"Post": "Publicar",
|
||||
"Compact": "Compacto",
|
||||
"Comfortable": "Confortável",
|
||||
"Comfortable2": "Confortável 2",
|
||||
"AdvancedCreateCard": "Mudar para criação avançada de cartão"
|
||||
"AdvancedCreateCard": "Mostrar menos",
|
||||
"CardContent": "O que você quer compartilhar?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "Расширение для управления знаниями в стиле базы данных.",
|
||||
"AddCollaborators": "Добавить сотрудников",
|
||||
"Home": "Дом",
|
||||
"CardTitle": "Введите название карты...",
|
||||
"CardTitle": "Название",
|
||||
"Post": "Опубликовать",
|
||||
"Compact": "Компактный",
|
||||
"Comfortable": "Комфортный",
|
||||
"Comfortable2": "Комфортный 2",
|
||||
"AdvancedCreateCard": "Переключиться на расширенное создание карты"
|
||||
"AdvancedCreateCard": "Скрыть детали",
|
||||
"CardContent": "Чем вы хотите поделиться?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +42,12 @@
|
||||
"ConfigDescription": "用于数据库风格知识管理的扩展。",
|
||||
"AddCollaborators": "添加协作者",
|
||||
"Home": "主页",
|
||||
"CardTitle": "输入卡片标题...",
|
||||
"CardTitle": "标题",
|
||||
"Post": "发布",
|
||||
"Compact": "紧凑",
|
||||
"Comfortable": "舒适",
|
||||
"Comfortable2": "舒适 2",
|
||||
"AdvancedCreateCard": "切换到高级卡片创建"
|
||||
"AdvancedCreateCard": "收起详情",
|
||||
"CardContent": "你想分享什么?"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
"@hcengineering/view": "^0.6.13",
|
||||
"@hcengineering/text-editor": "^0.6.0",
|
||||
"@hcengineering/text-editor-resources": "^0.6.0",
|
||||
"@hcengineering/text-markdown": "^0.6.0",
|
||||
"@hcengineering/workbench-resources": "^0.6.1",
|
||||
"@hcengineering/view-resources": "^0.6.0",
|
||||
"@hcengineering/notification": "^0.6.23",
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
export let space: CardSpace | undefined = undefined
|
||||
export let changeType: boolean = false
|
||||
export let allowChangeSpace: boolean = true
|
||||
export let description: Markup = EmptyMarkup
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const client = getClient()
|
||||
@@ -41,16 +42,16 @@
|
||||
const me = getCurrentEmployee()
|
||||
const _id = generateId<Card>()
|
||||
|
||||
$: extension = type
|
||||
? client
|
||||
.getModel()
|
||||
.findAllSync(card.mixin.CreateCardExtension, {})
|
||||
.find((it) => hierarchy.isDerived(type, it._id))
|
||||
: undefined
|
||||
$: extension =
|
||||
type != null
|
||||
? client
|
||||
.getModel()
|
||||
.findAllSync(card.mixin.CreateCardExtension, {})
|
||||
.find((it) => hierarchy.isDerived(type, it._id))
|
||||
: undefined
|
||||
|
||||
let data: Partial<Data<Card>> = { title }
|
||||
let _space: Ref<CardSpace> | undefined = space?._id
|
||||
let description: Markup = EmptyMarkup
|
||||
let collaborators: Ref<Employee>[] = [me]
|
||||
|
||||
let creating = false
|
||||
@@ -73,7 +74,7 @@
|
||||
try {
|
||||
creating = true
|
||||
|
||||
if (extension?.canCreate) {
|
||||
if (extension?.canCreate != null) {
|
||||
const fn = await getResource(extension.canCreate)
|
||||
const res = await fn(_space, data)
|
||||
if (res === false) {
|
||||
@@ -156,7 +157,7 @@
|
||||
<ModernEditbox
|
||||
bind:value={data.title}
|
||||
label={view.string.Title}
|
||||
size="large"
|
||||
size="medium"
|
||||
kind="ghost"
|
||||
disabled={extension?.disableTitle ?? false}
|
||||
autoFocus={!(extension?.disableTitle ?? false)}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { ButtonIcon, handler } from '@hcengineering/ui'
|
||||
import type { RefAction, TextEditorHandler } from '@hcengineering/text-editor'
|
||||
import { getEditorHandler } from '@hcengineering/text-editor-resources/src/components/editor-context'
|
||||
|
||||
export let actions: RefAction[] = []
|
||||
|
||||
const editorHandler: TextEditorHandler | undefined = getEditorHandler()
|
||||
|
||||
$: sortedActions = actions.slice().sort((a, b) => a.order - b.order)
|
||||
|
||||
function handleAction (action: RefAction, evt?: Event): void {
|
||||
if (editorHandler === undefined) {
|
||||
console.error('Editor handler is not available')
|
||||
return
|
||||
}
|
||||
action.action(evt?.target as HTMLElement, editorHandler)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="editor-actions">
|
||||
{#each sortedActions as action}
|
||||
<ButtonIcon
|
||||
disabled={action.disabled}
|
||||
icon={action.icon}
|
||||
iconSize="small"
|
||||
size="small"
|
||||
kind="tertiary"
|
||||
tooltip={{ label: action.label }}
|
||||
on:click={handler(action, (a, evt) => {
|
||||
if (a.disabled !== true) {
|
||||
handleAction(a, evt)
|
||||
}
|
||||
})}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,25 +1,53 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { getClient, SpaceSelector } from '@hcengineering/presentation'
|
||||
import { ModernButton, ModernEditbox, ButtonIcon, IconSend, showPopup } from '@hcengineering/ui'
|
||||
import { getClient, getCommunicationClient, SpaceSelector } from '@hcengineering/presentation'
|
||||
import { ModernButton, ModernEditbox, ButtonIcon, IconSend, IconMinimize } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
import CreateCardPopup from './CreateCardPopup.svelte'
|
||||
import card from '../plugin'
|
||||
import { TypeSelector } from '../index'
|
||||
import core, { Data, Ref } from '@hcengineering/core'
|
||||
import core, { Data, generateId, Markup, Ref } from '@hcengineering/core'
|
||||
import { Card, type CardSpace, MasterTag } from '@hcengineering/card'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { EmptyMarkup } from '@hcengineering/text'
|
||||
import { EmptyMarkup, markupToText, isEmptyMarkup, markupToJSON } from '@hcengineering/text'
|
||||
import { createCard } from '../utils'
|
||||
import { AttachmentStyledBox } from '@hcengineering/attachment-resources'
|
||||
import { AttachIcon } from '@hcengineering/text-editor-resources'
|
||||
import { AttachmentID, BlobParams } from '@hcengineering/communication-types'
|
||||
import { markupToMarkdown } from '@hcengineering/text-markdown'
|
||||
import textEditor, { type RefAction } from '@hcengineering/text-editor'
|
||||
import { defaultMessageInputActions } from '@hcengineering/communication-resources'
|
||||
|
||||
import EditorActions from './EditorActions.svelte'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const communicationClient = getCommunicationClient()
|
||||
|
||||
const threadMasterTag = 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
|
||||
let title: string = ''
|
||||
let space: Ref<CardSpace> | undefined = undefined
|
||||
let type: Ref<MasterTag> = 'chat:masterTag:Thread' as Ref<MasterTag>
|
||||
let type: Ref<MasterTag> = threadMasterTag
|
||||
let description = EmptyMarkup
|
||||
let _id = generateId<Card>()
|
||||
let isExpanded = false
|
||||
let descriptionBox: AttachmentStyledBox
|
||||
|
||||
let creating = false
|
||||
$: applyDisabled = title.trim() === '' || space == null || type == null || creating
|
||||
$: applyDisabled = (title.trim() === '' && isEmptyMarkup(description)) || space == null || type == null || creating
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -32,30 +60,15 @@
|
||||
.find((it) => hierarchy.isDerived(type, it._id))
|
||||
: undefined
|
||||
|
||||
function onExtend (): void {
|
||||
showPopup(
|
||||
CreateCardPopup,
|
||||
{ title, type, space, changeType: true, allowChangeSpace: true },
|
||||
'center',
|
||||
async (result) => {
|
||||
if (result !== undefined) {
|
||||
const doc = await getClient().findOne(card.class.Card, { _id: result })
|
||||
if (doc === undefined) return
|
||||
dispatch('selectCard', doc)
|
||||
}
|
||||
}
|
||||
)
|
||||
title = ''
|
||||
}
|
||||
|
||||
async function okAction (): Promise<void> {
|
||||
if (space === undefined || type == null || title.trim() === '') return
|
||||
if (space === undefined || type == null) return
|
||||
|
||||
try {
|
||||
creating = true
|
||||
|
||||
const cardTitle = title.trim().length > 0 ? title.trim() : markupToText(description).slice(0, 50)
|
||||
const data: Partial<Data<Card>> = {
|
||||
title
|
||||
title: cardTitle
|
||||
}
|
||||
|
||||
if (extension?.canCreate !== undefined) {
|
||||
@@ -68,68 +81,152 @@
|
||||
}
|
||||
}
|
||||
|
||||
const createdCard = await createCard(type, space, data, EmptyMarkup)
|
||||
const isThread = type === threadMasterTag
|
||||
const cardDescription = isThread ? EmptyMarkup : description
|
||||
const createdCard = await createCard(type, space, data, cardDescription, _id)
|
||||
if (isThread) {
|
||||
if (createdCard == null) {
|
||||
console.error('Failed to create thread card')
|
||||
return
|
||||
}
|
||||
const blobs: BlobParams[] = descriptionBox.getAttachments().map((attachment) => ({
|
||||
blobId: attachment.file,
|
||||
mimeType: attachment.type,
|
||||
fileName: attachment.name,
|
||||
size: attachment.size
|
||||
}))
|
||||
await createMessage(createdCard, description, blobs)
|
||||
} else {
|
||||
await descriptionBox.createAttachments()
|
||||
}
|
||||
if (createdCard != null) {
|
||||
dispatch('selectCard', createdCard)
|
||||
}
|
||||
title = ''
|
||||
clear()
|
||||
} finally {
|
||||
_id = generateId<Card>()
|
||||
creating = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createMessage (card: Ref<Card>, markup: Markup, blobs: BlobParams[]): Promise<void> {
|
||||
const markdown = markupToMarkdown(markupToJSON(markup))
|
||||
const { messageId } = await communicationClient.createMessage(card, type, markdown)
|
||||
|
||||
if (blobs.length > 0) {
|
||||
void communicationClient.attachmentPatch<BlobParams>(card, messageId, {
|
||||
add: blobs.map((it) => ({
|
||||
id: it.blobId as any as AttachmentID,
|
||||
type: it.mimeType,
|
||||
params: it
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function clear (): void {
|
||||
title = ''
|
||||
description = EmptyMarkup
|
||||
_id = generateId<Card>()
|
||||
}
|
||||
|
||||
function expand (): void {
|
||||
const prevExpanded = isExpanded
|
||||
isExpanded = true
|
||||
if (!prevExpanded) {
|
||||
setTimeout(() => {
|
||||
descriptionBox?.focus?.()
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
const attachAction: RefAction = {
|
||||
label: textEditor.string.Attach,
|
||||
icon: AttachIcon,
|
||||
action: () => {
|
||||
dispatch('focus')
|
||||
descriptionBox.handleAttach()
|
||||
},
|
||||
order: 1000
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="create-card rounded-form">
|
||||
<div class="corner-container">
|
||||
<ButtonIcon
|
||||
icon={card.icon.Expand}
|
||||
size="extra-small"
|
||||
on:click={onExtend}
|
||||
kind="tertiary"
|
||||
disabled={creating}
|
||||
tooltip={{ label: card.string.AdvancedCreateCard }}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<ModernEditbox
|
||||
bind:value={title}
|
||||
label={card.string.CardTitle}
|
||||
size="medium"
|
||||
kind="ghost"
|
||||
width="100%"
|
||||
disabled={creating}
|
||||
autoFocus={true}
|
||||
on:keydown={(evt) => {
|
||||
if (evt.key === 'Enter') {
|
||||
void okAction()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="form-row form-row-bottom">
|
||||
<div class="form-dropdowns">
|
||||
<SpaceSelector
|
||||
_class={card.class.CardSpace}
|
||||
query={{ archived: false }}
|
||||
label={core.string.Space}
|
||||
bind:space
|
||||
focus={false}
|
||||
readonly={creating}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
{#if isExpanded}
|
||||
<div class="corner-container">
|
||||
<ButtonIcon
|
||||
icon={IconMinimize}
|
||||
size="extra-small"
|
||||
on:click={() => (isExpanded = false)}
|
||||
kind="tertiary"
|
||||
tooltip={{ label: card.string.ShowLess }}
|
||||
disabled={creating}
|
||||
/>
|
||||
<TypeSelector size={'small'} bind:value={type} disabled={creating} />
|
||||
</div>
|
||||
<ModernButton
|
||||
label={card.string.Post}
|
||||
icon={IconSend}
|
||||
iconSize="small"
|
||||
size="small"
|
||||
kind="primary"
|
||||
disabled={applyDisabled}
|
||||
loading={creating}
|
||||
on:click={okAction}
|
||||
/>
|
||||
<div class="form-row title">
|
||||
<ModernEditbox
|
||||
bind:value={title}
|
||||
label={card.string.CardTitle}
|
||||
size="large"
|
||||
kind="transparent"
|
||||
width="100%"
|
||||
disabled={creating}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div class="form-row description" class:collapsed={!isExpanded} on:click={expand}>
|
||||
<AttachmentStyledBox
|
||||
bind:this={descriptionBox}
|
||||
objectId={_id}
|
||||
_class={type}
|
||||
{space}
|
||||
alwaysEdit
|
||||
showButtons={true}
|
||||
bind:content={description}
|
||||
placeholder={card.string.CardContent}
|
||||
kind="indented"
|
||||
maxHeight="limited"
|
||||
isScrollable={false}
|
||||
kitOptions={{ reference: true }}
|
||||
enableAttachments={true}
|
||||
fullWidth={true}
|
||||
on:focus={() => (isExpanded = true)}
|
||||
>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if isExpanded}
|
||||
<div class="form-row form-row-bottom">
|
||||
<div class="form-dropdowns">
|
||||
<SpaceSelector
|
||||
_class={card.class.CardSpace}
|
||||
query={{ archived: false }}
|
||||
label={core.string.Space}
|
||||
bind:space
|
||||
focus={false}
|
||||
readonly={creating}
|
||||
kind={'regular'}
|
||||
size={'small'}
|
||||
/>
|
||||
<TypeSelector size={'small'} bind:value={type} disabled={creating} />
|
||||
<div class="spacer" />
|
||||
<div class="right-divider" />
|
||||
<EditorActions actions={[...defaultMessageInputActions, attachAction]} />
|
||||
</div>
|
||||
<ModernButton
|
||||
label={card.string.Post}
|
||||
icon={IconSend}
|
||||
iconSize="small"
|
||||
size="small"
|
||||
kind="primary"
|
||||
disabled={applyDisabled}
|
||||
loading={creating}
|
||||
on:click={okAction}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</AttachmentStyledBox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -138,7 +235,7 @@
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 1rem;
|
||||
z-index: 1;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
@@ -162,13 +259,26 @@
|
||||
.form-row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
&.title {
|
||||
z-index: 1;
|
||||
}
|
||||
&.description {
|
||||
padding-left: 0.375rem;
|
||||
margin-top: -2rem;
|
||||
padding-top: 1rem;
|
||||
width: 100%;
|
||||
max-height: 20rem;
|
||||
|
||||
&.collapsed {
|
||||
margin-bottom: -1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-row-bottom {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0;
|
||||
gap: 1rem;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
.form-dropdowns {
|
||||
display: flex;
|
||||
|
||||
@@ -103,10 +103,11 @@ export default mergeIds(cardId, card, {
|
||||
AddCollaborators: '' as IntlString,
|
||||
Home: '' as IntlString,
|
||||
CardTitle: '' as IntlString,
|
||||
CardContent: '' as IntlString,
|
||||
Post: '' as IntlString,
|
||||
Compact: '' as IntlString,
|
||||
Comfortable: '' as IntlString,
|
||||
Comfortable2: '' as IntlString,
|
||||
AdvancedCreateCard: '' as IntlString
|
||||
ShowLess: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -44,6 +44,7 @@ import { createPoll, getPollTitle } from './poll'
|
||||
|
||||
export { isActivityMessage } from './activity'
|
||||
export * from './stores'
|
||||
export { defaultMessageInputActions } from './utils'
|
||||
|
||||
export { default as MessagePresenter } from './components/message/MessagePresenter.svelte'
|
||||
export { default as MessageInput } from './components/message/MessageInput.svelte'
|
||||
|
||||
@@ -59,6 +59,7 @@
|
||||
export let avatarSize: IconSize = kind === 'regular' ? 'small' : 'card'
|
||||
export let justify: 'left' | 'center' = 'center'
|
||||
export let width: string | undefined = undefined
|
||||
export let height: string | undefined = undefined
|
||||
export let shrink: number = 0
|
||||
export let focusIndex = -1
|
||||
export let showTooltip: LabelAndProps | PersonLabelTooltip | undefined = undefined
|
||||
@@ -132,7 +133,14 @@
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<div {id} bind:this={container} class="min-w-0" class:w-full={width === '100%'} style:flex-shrink={shrink}>
|
||||
<div
|
||||
{id}
|
||||
bind:this={container}
|
||||
class="min-w-0"
|
||||
class:w-full={width === '100%'}
|
||||
class:h-full={height === '100%'}
|
||||
style:flex-shrink={shrink}
|
||||
>
|
||||
{#if $$slots.content}
|
||||
<div
|
||||
class="w-full h-full flex-streatch"
|
||||
|
||||
+1
@@ -23,6 +23,7 @@
|
||||
|
||||
<div class="text-editor-popup w-85">
|
||||
<ReferenceInput
|
||||
autofocus
|
||||
focusable
|
||||
kindSend="primary"
|
||||
placeholder={chunter.string.AddCommentPlaceholder}
|
||||
|
||||
@@ -204,13 +204,14 @@
|
||||
if (isOffice(room) && rX === 0 && rY === 0) e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
{#if isOffice(room) && rX === 0 && rY === 0}
|
||||
{#if isOffice(room) && rX === 0 && rY === 0 && !room.person}
|
||||
<AssigneeBox
|
||||
_class={contact.class.Person}
|
||||
excluded={excludedPersons}
|
||||
shouldShowName={false}
|
||||
showNavigate={false}
|
||||
width={'100%'}
|
||||
height={'100%'}
|
||||
label={contact.string.Person}
|
||||
value={room.person}
|
||||
avatarSize={'full'}
|
||||
|
||||
@@ -97,7 +97,9 @@
|
||||
"RemoveFirst": "Odstranit první",
|
||||
"RemoveLast": "Odstranit poslední",
|
||||
"Cancelled": "Zrušeno",
|
||||
"WaitUntil": "Čekat do"
|
||||
"WaitUntil": "Čekat do",
|
||||
"CurrentUser": "Aktuální uživatel",
|
||||
"CurrentDate": "Aktuální datum"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metoda nenalezena: {methodId}",
|
||||
|
||||
@@ -97,7 +97,9 @@
|
||||
"RemoveFirst": "Erstes entfernen",
|
||||
"RemoveLast": "Letztes entfernen",
|
||||
"Cancelled": "Abgebrochen",
|
||||
"WaitUntil": "Warten bis"
|
||||
"WaitUntil": "Warten bis",
|
||||
"CurrentUser": "Aktueller Benutzer",
|
||||
"CurrentDate": "Aktuelles Datum"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Methode nicht gefunden: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"RemoveFirst": "Remove first",
|
||||
"RemoveLast": "Remove last",
|
||||
"Cancelled": "Cancelled",
|
||||
"WaitUntil": "Wait until"
|
||||
"WaitUntil": "Wait until",
|
||||
"CurrentUser": "Current user",
|
||||
"CurrentDate": "Current date"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Method not found: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "Tamaño menor que",
|
||||
"ArraySizeLte": "Tamaño menor o igual que",
|
||||
"Cancelled": "Cancelado",
|
||||
"WaitUntil": "Esperar hasta"
|
||||
"WaitUntil": "Esperar hasta",
|
||||
"CurrentUser": "Usuario Actual",
|
||||
"CurrentDate": "Fecha Actual"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método no encontrado: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "Taille inférieure à",
|
||||
"ArraySizeLte": "Taille inférieure ou égale à",
|
||||
"Cancelled": "Annulé",
|
||||
"WaitUntil": "Attendre jusqu'à"
|
||||
"WaitUntil": "Attendre jusqu'à",
|
||||
"CurrentUser": "Utilisateur actuel",
|
||||
"CurrentDate": "Date actuelle"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Méthode introuvable : {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "Dimensione minore di",
|
||||
"ArraySizeLte": "Dimensione minore o uguale a",
|
||||
"Cancelled": "Annullato",
|
||||
"WaitUntil": "Aspetta fino a"
|
||||
"WaitUntil": "Aspetta fino a",
|
||||
"CurrentUser": "Utente corrente",
|
||||
"CurrentDate": "Data corrente"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Metodo non trovato: {methodId}",
|
||||
|
||||
@@ -101,7 +101,9 @@
|
||||
"ArraySizeLt": "サイズがより小さい",
|
||||
"ArraySizeLte": "サイズが以下",
|
||||
"Cancelled": "キャンセル済み",
|
||||
"WaitUntil": "待機する"
|
||||
"WaitUntil": "待機する",
|
||||
"CurrentUser": "現在のユーザー",
|
||||
"CurrentDate": "現在の日付"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "メソッドが見つかりません: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "Tamanho menor que",
|
||||
"ArraySizeLte": "Tamanho menor ou igual a",
|
||||
"Cancelled": "Cancelado",
|
||||
"WaitUntil": "Aguardar até"
|
||||
"WaitUntil": "Aguardar até",
|
||||
"CurrentUser": "Usuário Atual",
|
||||
"CurrentDate": "Data Atual"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Método não encontrado: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "Количество меньше",
|
||||
"ArraySizeLte": "Количество меньше или равно",
|
||||
"Cancelled": "Отменено",
|
||||
"WaitUntil": "Ждать до"
|
||||
"WaitUntil": "Ждать до",
|
||||
"CurrentUser": "Текущий пользователь",
|
||||
"CurrentDate": "Текущая дата"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "Метод не найден: {methodId}",
|
||||
|
||||
@@ -102,7 +102,9 @@
|
||||
"ArraySizeLt": "大小小于",
|
||||
"ArraySizeLte": "大小小于等于",
|
||||
"Cancelled": "已取消",
|
||||
"WaitUntil": "等待直到"
|
||||
"WaitUntil": "等待直到",
|
||||
"CurrentUser": "当前用户",
|
||||
"CurrentDate": "当前日期"
|
||||
},
|
||||
"error": {
|
||||
"MethodNotFound": "找不到方法:{methodId}",
|
||||
|
||||
@@ -187,13 +187,14 @@
|
||||
onChange(contextValue)
|
||||
}
|
||||
|
||||
function onConfigure (e: MouseEvent, func: ProcessFunction, pos: number): void {
|
||||
if (contextValue.functions === undefined || func.editor === undefined) return
|
||||
function onConfigure (e: MouseEvent, func: Func, pos: number): void {
|
||||
const f = getFunction(func.func)
|
||||
if (contextValue.functions === undefined || f.editor === undefined) return
|
||||
const val = contextValue.functions[pos]
|
||||
showPopup(
|
||||
func.editor,
|
||||
f.editor,
|
||||
{
|
||||
func,
|
||||
func: f,
|
||||
masterTag: process.masterTag,
|
||||
process,
|
||||
context,
|
||||
@@ -224,6 +225,10 @@
|
||||
}
|
||||
onChange(contextValue)
|
||||
}
|
||||
|
||||
function getFunction (_id: Ref<ProcessFunction>): ProcessFunction {
|
||||
return client.getModel().findAllSync(plugin.class.ProcessFunction, { _id })[0]
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="selectPopup" use:resizeObserver={() => dispatch('changeContent')}>
|
||||
@@ -246,11 +251,13 @@
|
||||
options={{ component: plugin.component.FunctionSelector }}
|
||||
withHover
|
||||
/>
|
||||
<div class="menu-separator" />
|
||||
{/if}
|
||||
{#if availableFunctions.length > 0 || functionsLength > 0}
|
||||
{#each funcs as f, i}
|
||||
{#if reduceFuncs.includes(f._id)}
|
||||
{#if sourceFunc !== undefined}
|
||||
<div class="menu-separator" />
|
||||
{/if}
|
||||
{#each contextValue.functions ?? [] as f, i}
|
||||
{#if reduceFuncs.includes(f.func)}
|
||||
<Submenu
|
||||
bind:element={elements[i + (sourceFunc !== undefined ? 1 : 0)]}
|
||||
on:keydown={(event) => {
|
||||
@@ -259,7 +266,7 @@
|
||||
on:mouseover={() => {
|
||||
elements[i + (sourceFunc !== undefined ? 1 : 0)]?.focus()
|
||||
}}
|
||||
label={f.label}
|
||||
label={getFunction(f.func)?.label}
|
||||
props={{
|
||||
availableFunctions: reduceFuncs,
|
||||
onSelect: getFunctionChange(i)
|
||||
@@ -281,10 +288,10 @@
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Label label={f.label} />
|
||||
<Label label={getFunction(f.func).label} />
|
||||
</div>
|
||||
<div>
|
||||
{#if f.editor}
|
||||
{#if getFunction(f.func).editor}
|
||||
<ButtonIcon
|
||||
icon={IconSettings}
|
||||
size="small"
|
||||
@@ -325,7 +332,9 @@
|
||||
/>
|
||||
<!-- <div class="menu-separator" /> -->
|
||||
{/if}
|
||||
<div class="menu-separator" />
|
||||
{#if !forbidValue}
|
||||
<div class="menu-separator" />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if !forbidValue}
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { ExecutionContext, parseContext, Process, SelectedUserRequest, Transition } from '@hcengineering/process'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import ProcessAttributeEditor from './ProcessAttributeEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
export let targetProcess: Ref<Process>
|
||||
export let context: ExecutionContext
|
||||
|
||||
const client = getClient()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
$: initTransition = client.getModel().findAllSync(plugin.class.Transition, {
|
||||
process: targetProcess,
|
||||
from: null
|
||||
})[0]
|
||||
|
||||
let contexts: SelectedUserRequest[] = []
|
||||
|
||||
function fillParams (initTransition: Transition | undefined): void {
|
||||
contexts = []
|
||||
for (const action of initTransition?.actions ?? []) {
|
||||
for (const key in action.params) {
|
||||
const value = (action.params as any)[key]
|
||||
const request = parseContext(value)
|
||||
if (request !== undefined && request.type === 'userRequest') {
|
||||
contexts.push(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
contexts = contexts
|
||||
}
|
||||
|
||||
$: fillParams(initTransition)
|
||||
</script>
|
||||
|
||||
{#each contexts as ctx}
|
||||
<ProcessAttributeEditor
|
||||
{process}
|
||||
_class={ctx._class}
|
||||
key={ctx.key}
|
||||
objectKey={ctx.id}
|
||||
object={context}
|
||||
allowRemove
|
||||
on:change={(e) => {
|
||||
context = e.detail.object
|
||||
dispatch('change', context)
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
@@ -17,9 +17,9 @@
|
||||
import { getAttributeEditor, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { Process } from '@hcengineering/process'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getContext } from '../../utils'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let process: Process
|
||||
export let _class: Ref<Class<Doc>>
|
||||
@@ -28,6 +28,7 @@
|
||||
export let allowRemove: boolean = false
|
||||
export let forbidValue: boolean = false
|
||||
export let allowArray: boolean = false
|
||||
export let objectKey: string | undefined = undefined
|
||||
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
@@ -36,14 +37,14 @@
|
||||
function onChange (value: any | undefined): void {
|
||||
if (value === undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete (object as any)[key]
|
||||
delete (object as any)[objectKey ?? key]
|
||||
} else {
|
||||
;(object as any)[key] = value
|
||||
;(object as any)[objectKey ?? key] = value
|
||||
}
|
||||
dispatch('change', { value })
|
||||
dispatch('change', { value, object })
|
||||
}
|
||||
|
||||
$: value = object[key]
|
||||
$: value = object[objectKey ?? key]
|
||||
|
||||
$: attribute = hierarchy.getAttribute(_class, key)
|
||||
$: presenterClass = getAttributePresenterClass(hierarchy, attribute.type)
|
||||
|
||||
@@ -14,12 +14,14 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import card from '@hcengineering/card'
|
||||
import { Ref } from '@hcengineering/core'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Execution, parseContext, Process, Step } from '@hcengineering/process'
|
||||
import { Execution, ExecutionContext, parseContext, Process, Step } from '@hcengineering/process'
|
||||
import { DropdownLabels, DropdownTextItem, Label, Toggle } from '@hcengineering/ui'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import plugin from '../../plugin'
|
||||
import { getContextMasterTag } from '../../utils'
|
||||
import InitParamsEditor from './InitParamsEditor.svelte'
|
||||
import ProcessAttributeEditor from './ProcessAttributeEditor.svelte'
|
||||
|
||||
export let process: Process
|
||||
@@ -28,15 +30,18 @@
|
||||
const client = getClient()
|
||||
const hierarchy = client.getHierarchy()
|
||||
|
||||
let _id = step.params._id
|
||||
let _id: Ref<Process> = step.params._id as any
|
||||
$: context = parseContext(step.params.card)
|
||||
$: masterTag = getContextMasterTag(client, context, process.masterTag) ?? process.masterTag
|
||||
|
||||
$: executionContext = (step.params.context ?? {}) as ExecutionContext
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
function change (e: CustomEvent<any>): void {
|
||||
if (e.detail !== undefined) {
|
||||
;(step.params as any)._id = e.detail
|
||||
_id = e.detail
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
@@ -62,15 +67,20 @@
|
||||
.findAllSync(plugin.class.Process, { masterTag: { $in: ancestors }, _id: { $ne: process._id } })
|
||||
|
||||
function changeThis (): void {
|
||||
_id = undefined
|
||||
step.params = {}
|
||||
dispatch('change', step)
|
||||
}
|
||||
|
||||
function changeTarget (e: CustomEvent<any>): void {
|
||||
_id = undefined
|
||||
step.params.card = e.detail.value
|
||||
}
|
||||
|
||||
function changeContext (e: CustomEvent<ExecutionContext>): void {
|
||||
if (e.detail !== undefined) {
|
||||
step.params.context = e.detail
|
||||
dispatch('change', step)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid">
|
||||
@@ -103,8 +113,17 @@
|
||||
on:selected={change}
|
||||
/>
|
||||
</div>
|
||||
<div class="divider" />
|
||||
<div class="grid">
|
||||
<InitParamsEditor {process} targetProcess={_id} context={executionContext} on:change={changeContext} />
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.divider {
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1.5fr;
|
||||
|
||||
+5
-25
@@ -14,12 +14,9 @@
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { MasterTag, Tag } from '@hcengineering/card'
|
||||
import core, { AnyAttribute, Ref } from '@hcengineering/core'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import { AnyAttribute, Ref } from '@hcengineering/core'
|
||||
import presentation, { Card, getAttributePresenterClass, getClient } from '@hcengineering/presentation'
|
||||
import { Process, ProcessFunction } from '@hcengineering/process'
|
||||
import { AnySvelteComponent } from '@hcengineering/ui'
|
||||
import view from '@hcengineering/view'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { getContext } from '../../utils'
|
||||
import ProcessAttribute from '../ProcessAttribute.svelte'
|
||||
@@ -29,6 +26,7 @@
|
||||
export let masterTag: Ref<MasterTag | Tag>
|
||||
export let attribute: AnyAttribute
|
||||
export let props: Record<string, any> = {}
|
||||
export let allowArray: boolean = false
|
||||
|
||||
let value: any = props?.value ?? ''
|
||||
|
||||
@@ -40,28 +38,9 @@
|
||||
dispatch('close', { value })
|
||||
}
|
||||
|
||||
let editor: AnySvelteComponent | undefined
|
||||
|
||||
function getEditor (): void {
|
||||
try {
|
||||
const inlineEditor = h.as(h.getClass(core.class.ArrOf), view.mixin.AttributeEditor).inlineEditor
|
||||
void getResource(inlineEditor)
|
||||
.then((p) => {
|
||||
editor = p
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e)
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
getEditor()
|
||||
|
||||
$: presenterClass = getAttributePresenterClass(h, attribute.type)
|
||||
|
||||
$: elementContext = getContext(client, process, presenterClass.attrClass, 'object')
|
||||
$: elementContext = getContext(client, process, presenterClass.attrClass, 'attribute')
|
||||
</script>
|
||||
|
||||
<Card on:close width={'x-small'} label={func.label} canSave okAction={save} okLabel={presentation.string.Save}>
|
||||
@@ -71,12 +50,13 @@
|
||||
{masterTag}
|
||||
context={elementContext}
|
||||
{attribute}
|
||||
editor={undefined}
|
||||
presenterClass={{
|
||||
attrClass: presenterClass.attrClass,
|
||||
category: 'attribute'
|
||||
}}
|
||||
{allowArray}
|
||||
{value}
|
||||
{editor}
|
||||
on:change={(e) => {
|
||||
value = e.detail
|
||||
}}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<!--
|
||||
// Copyright © 2025 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 { MasterTag, Tag } from '@hcengineering/card'
|
||||
import { AnyAttribute, Ref } from '@hcengineering/core'
|
||||
import { Process, ProcessFunction } from '@hcengineering/process'
|
||||
import ArrayElementEditor from './ArrayElementEditor.svelte'
|
||||
|
||||
export let func: ProcessFunction
|
||||
export let process: Process
|
||||
export let masterTag: Ref<MasterTag | Tag>
|
||||
export let attribute: AnyAttribute
|
||||
export let props: Record<string, any> = {}
|
||||
</script>
|
||||
|
||||
<ArrayElementEditor {func} {process} {masterTag} {attribute} {props} allowArray on:close />
|
||||
@@ -43,6 +43,7 @@ import TransitionEditor from './components/settings/TransitionEditor.svelte'
|
||||
import TriggerPresenter from './components/settings/TriggerPresenter.svelte'
|
||||
import UpdateCardEditor from './components/settings/UpdateCardEditor.svelte'
|
||||
import ArrayElementEditor from './components/transformEditors/ArrayElementEditor.svelte'
|
||||
import MultiArrayElementEditor from './components/transformEditors/MultiArrayElementEditor.svelte'
|
||||
import DateOffsetEditor from './components/transformEditors/DateOffsetEditor.svelte'
|
||||
import NumberEditor from './components/transformEditors/NumberEditor.svelte'
|
||||
|
||||
@@ -143,6 +144,7 @@ export default async (): Promise<Resources> => ({
|
||||
},
|
||||
transformEditor: {
|
||||
ArrayElementEditor,
|
||||
MultiArrayElementEditor,
|
||||
DateOffsetEditor,
|
||||
NumberEditor,
|
||||
AppendEditor,
|
||||
|
||||
@@ -78,6 +78,7 @@ export default mergeIds(processId, process, {
|
||||
RefCriteria: '' as AnyComponent
|
||||
},
|
||||
transformEditor: {
|
||||
MultiArrayElementEditor: '' as AnyComponent,
|
||||
ArrayElementEditor: '' as AnyComponent,
|
||||
DateOffsetEditor: '' as AnyComponent,
|
||||
NumberEditor: '' as AnyComponent,
|
||||
@@ -188,6 +189,8 @@ export default mergeIds(processId, process, {
|
||||
ArraySizeGt: '' as IntlString,
|
||||
ArraySizeGte: '' as IntlString,
|
||||
ArraySizeLt: '' as IntlString,
|
||||
ArraySizeLte: '' as IntlString
|
||||
ArraySizeLte: '' as IntlString,
|
||||
CurrentUser: '' as IntlString,
|
||||
CurrentDate: '' as IntlString
|
||||
}
|
||||
})
|
||||
|
||||
@@ -81,7 +81,9 @@ export function getContextMasterTag (
|
||||
const model = client.getModel()
|
||||
if (context.type === 'attribute') {
|
||||
const attr = h.findAttribute(masterTag, context.key)
|
||||
return (attr?.type as RefTo<Doc>)?.to
|
||||
if (attr === undefined) return
|
||||
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
|
||||
if (parentType._class === core.class.RefTo) return (parentType as RefTo<Doc>).to
|
||||
}
|
||||
if (context.type === 'nested') {
|
||||
const attr = h.findAttribute(masterTag, context.path)
|
||||
@@ -284,19 +286,6 @@ function getContextFunctions (
|
||||
const funcs = client.getModel().findAllSync(process.class.ProcessFunction, { type: 'context' })
|
||||
for (const func of funcs) {
|
||||
switch (category) {
|
||||
case 'object': {
|
||||
if (func.category === 'array') {
|
||||
if (hierarchy.isDerived(func.of, target)) {
|
||||
matched.push(func._id)
|
||||
}
|
||||
}
|
||||
if (func.category === 'object') {
|
||||
if (hierarchy.isDerived(func.of, target)) {
|
||||
matched.push(func._id)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'array': {
|
||||
if (func.category === 'array') {
|
||||
if (hierarchy.isDerived(func.of, target)) {
|
||||
@@ -306,7 +295,7 @@ function getContextFunctions (
|
||||
break
|
||||
}
|
||||
default: {
|
||||
if (func.of === target) {
|
||||
if (hierarchy.isDerived(func.of, target)) {
|
||||
matched.push(func._id)
|
||||
}
|
||||
}
|
||||
@@ -358,6 +347,12 @@ function getClassAttributes (
|
||||
if (attr[1].type._class === target) {
|
||||
matchedAttributes.push(attr[1])
|
||||
}
|
||||
if (attr[1].type._class === core.class.RefTo) {
|
||||
const to = (attr[1].type as RefTo<Doc>).to
|
||||
if (hierarchy.isDerived(to, target)) {
|
||||
matchedAttributes.push(attr[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -450,13 +445,13 @@ export async function getTransitionUserInput (
|
||||
transition: Transition,
|
||||
userContext: ExecutionContext
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
const changed = false
|
||||
let changed = false
|
||||
for (const action of transition.actions) {
|
||||
if (action == null) continue
|
||||
for (const key in action.params) {
|
||||
const value = (action.params as any)[key]
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined && context.type === 'userRequest') {
|
||||
if (context !== undefined && context.type === 'userRequest' && userContext[context.id] === undefined) {
|
||||
const promise = new Promise<void>((resolve) => {
|
||||
showPopup(
|
||||
process.component.RequestUserInput,
|
||||
@@ -464,6 +459,7 @@ export async function getTransitionUserInput (
|
||||
undefined,
|
||||
(res) => {
|
||||
if (res?.value !== undefined) {
|
||||
changed = true
|
||||
userContext[context.id] = res.value
|
||||
}
|
||||
resolve()
|
||||
@@ -481,7 +477,7 @@ export async function getSubProcessesUserInput (
|
||||
transition: Transition,
|
||||
userContext: ExecutionContext
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
const changed = false
|
||||
let changed = false
|
||||
for (const action of transition.actions) {
|
||||
if (action.methodId !== process.method.RunSubProcess) continue
|
||||
const processId = action.params._id as Ref<Process>
|
||||
@@ -489,6 +485,7 @@ export async function getSubProcessesUserInput (
|
||||
const res = await newExecutionUserInput(processId)
|
||||
if (action.context == null || res === undefined) continue
|
||||
userContext[action.context._id] = res
|
||||
changed = true
|
||||
}
|
||||
return changed ? userContext : undefined
|
||||
}
|
||||
|
||||
@@ -285,6 +285,9 @@ export default plugin(processId, {
|
||||
Insert: '' as Ref<ProcessFunction>,
|
||||
Remove: '' as Ref<ProcessFunction>,
|
||||
RemoveFirst: '' as Ref<ProcessFunction>,
|
||||
RemoveLast: '' as Ref<ProcessFunction>
|
||||
RemoveLast: '' as Ref<ProcessFunction>,
|
||||
CurrentEmployee: '' as Ref<ProcessFunction>,
|
||||
CurrentUser: '' as Ref<ProcessFunction>,
|
||||
CurrentDate: '' as Ref<ProcessFunction>
|
||||
}
|
||||
})
|
||||
|
||||
@@ -312,6 +312,7 @@
|
||||
dispatch('changeContent', evt.detail)
|
||||
}}
|
||||
>
|
||||
<slot name="actions" slot="actions" />
|
||||
{#if !alwaysEdit && !hideExtraButtons}
|
||||
<div class="flex flex-reverse flex-grow gap-2 reverse">
|
||||
<ActionIcon
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import { EditorKitOptions } from '../kits/editor-kit'
|
||||
import { defaultRefActions, getModelRefActions } from './editor/actions'
|
||||
import TextEditor from './TextEditor.svelte'
|
||||
import { setEditorHandler } from './editor-context'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -107,6 +108,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Set the editor handler in context so child components can access it
|
||||
setEditorHandler(editorHandler)
|
||||
|
||||
let actions: RefAction[] = defaultRefActions.concat(...extraActions).sort((a, b) => a.order - b.order)
|
||||
|
||||
void getModelRefActions().then((modelActions) => {
|
||||
@@ -198,28 +202,30 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if showButtons}
|
||||
<div class="flex-between">
|
||||
<div class="buttons-group {buttonsGap} mt-3">
|
||||
{#each actions as a}
|
||||
<Button
|
||||
icon={a.icon}
|
||||
iconProps={{ size: buttonSize }}
|
||||
kind="ghost"
|
||||
showTooltip={{ label: a.label }}
|
||||
size={buttonSize}
|
||||
on:click={(evt) => {
|
||||
handleAction(a, evt)
|
||||
}}
|
||||
/>
|
||||
{#if a.order % 10 === 1}
|
||||
<div class="buttons-divider {buttonsHeight}" />
|
||||
{/if}
|
||||
{/each}
|
||||
<slot />
|
||||
<slot name="actions">
|
||||
{#if showButtons}
|
||||
<div class="flex-between">
|
||||
<div class="buttons-group {buttonsGap} mt-3">
|
||||
{#each actions as a}
|
||||
<Button
|
||||
icon={a.icon}
|
||||
iconProps={{ size: buttonSize }}
|
||||
kind="ghost"
|
||||
showTooltip={{ label: a.label }}
|
||||
size={buttonSize}
|
||||
on:click={(evt) => {
|
||||
handleAction(a, evt)
|
||||
}}
|
||||
/>
|
||||
{#if a.order % 10 === 1}
|
||||
<div class="buttons-divider {buttonsHeight}" />
|
||||
{/if}
|
||||
{/each}
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</slot>
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright © 2025 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 { getContext, setContext } from 'svelte'
|
||||
import type { TextEditorHandler } from '@hcengineering/text-editor'
|
||||
|
||||
const EDITOR_HANDLER_CONTEXT = Symbol('editor-handler')
|
||||
|
||||
export function setEditorHandler (handler: TextEditorHandler): void {
|
||||
try {
|
||||
setContext(EDITOR_HANDLER_CONTEXT, handler)
|
||||
} catch (err: any) {
|
||||
console.warn('Failed to set editor handler context: ', err)
|
||||
}
|
||||
}
|
||||
|
||||
export function getEditorHandler (): TextEditorHandler | undefined {
|
||||
return getContext(EDITOR_HANDLER_CONTEXT)
|
||||
}
|
||||
@@ -64,6 +64,7 @@ export { default as TableOfContents } from './components/toc/TableOfContents.sve
|
||||
export { default as TableOfContentsContent } from './components/toc/TableOfContentsContent.svelte'
|
||||
export type { EditorKitOptions } from './kits/editor-kit'
|
||||
export * from './utils'
|
||||
export * from './components/editor-context'
|
||||
|
||||
export * from './command/deleteAttachment'
|
||||
export { EmojiExtension } from './components/extension/emoji'
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
hulylake hardcoreeng/service_hulylake:0.1.1
|
||||
|
||||
@@ -10,7 +10,7 @@ services:
|
||||
command: start-single-node --insecure
|
||||
restart: unless-stopped
|
||||
redpanda:
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v24.3.6
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v25.2.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
command:
|
||||
@@ -37,7 +37,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
redpanda_console:
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.3
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.10
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
entrypoint: /bin/sh
|
||||
|
||||
@@ -14,7 +14,17 @@
|
||||
//
|
||||
|
||||
import cardPlugin, { Card } from '@hcengineering/card'
|
||||
import core, { Tx, TxCreateDoc, TxCUD, TxProcessor, TxRemoveDoc, TxUpdateDoc } from '@hcengineering/core'
|
||||
import core, {
|
||||
ArrOf,
|
||||
Doc,
|
||||
RefTo,
|
||||
Tx,
|
||||
TxCreateDoc,
|
||||
TxCUD,
|
||||
TxProcessor,
|
||||
TxRemoveDoc,
|
||||
TxUpdateDoc
|
||||
} from '@hcengineering/core'
|
||||
import process, {
|
||||
ContextId,
|
||||
Execution,
|
||||
@@ -33,6 +43,8 @@ import {
|
||||
All,
|
||||
Append,
|
||||
Ceil,
|
||||
CurrentDate,
|
||||
CurrentUser,
|
||||
Cut,
|
||||
Divide,
|
||||
FirstValue,
|
||||
@@ -276,11 +288,16 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
id: transition.result._id,
|
||||
key: ''
|
||||
}
|
||||
const parentType =
|
||||
transition.result.type._class === core.class.ArrOf
|
||||
? (transition.result.type as ArrOf<Doc>).of
|
||||
: transition.result.type
|
||||
const _class = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
_process.context[transition.result._id] = {
|
||||
name: context?.name ?? transition.result.name,
|
||||
isResult: true,
|
||||
type: transition.result.type,
|
||||
_class: transition.result.type._class,
|
||||
_class,
|
||||
index: index++,
|
||||
producer: transition._id,
|
||||
value: ctx
|
||||
@@ -325,6 +342,8 @@ async function syncContext (control: TriggerControl, _process: Process): Promise
|
||||
}
|
||||
}
|
||||
|
||||
export * from './utils'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
export default async () => ({
|
||||
func: {
|
||||
@@ -338,6 +357,8 @@ export default async () => ({
|
||||
CheckTime
|
||||
},
|
||||
transform: {
|
||||
CurrentDate,
|
||||
CurrentUser,
|
||||
FirstValue,
|
||||
LastValue,
|
||||
Random,
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import contact, { Employee } from '@hcengineering/contact'
|
||||
import contact, { Employee, Person } from '@hcengineering/contact'
|
||||
import { Doc, Ref, Timestamp } from '@hcengineering/core'
|
||||
import { Execution, parseContext } from '@hcengineering/process'
|
||||
import { ProcessControl } from '@hcengineering/server-process'
|
||||
import { getAttributeValue } from './utils'
|
||||
import { getContextValue } from './utils'
|
||||
|
||||
// #region ArrayReduce
|
||||
|
||||
@@ -54,10 +54,8 @@ export async function Insert (
|
||||
if (props.value == null) return value
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
value.push(addition)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
Array.isArray(val) ? value.push(...val) : value.push(val)
|
||||
} else {
|
||||
value.push(props.value)
|
||||
}
|
||||
@@ -74,14 +72,11 @@ export async function Remove (
|
||||
if (props.value == null) return value
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
return value.filter((item) => item !== addition)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
return value.filter((item) => item !== val)
|
||||
} else {
|
||||
return value.filter((item) => item !== props.value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
export function RemoveFirst (value: Doc[], props: Record<string, any>): Doc[] {
|
||||
@@ -121,11 +116,9 @@ export async function Prepend (
|
||||
): Promise<string> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
if (typeof addition !== 'string') return value
|
||||
return addition + value
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'string') return value
|
||||
return val + value
|
||||
} else if (typeof value === 'string') {
|
||||
return props.value + value
|
||||
}
|
||||
@@ -140,11 +133,9 @@ export async function Append (
|
||||
): Promise<string> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const addition = getAttributeValue(control, execution, context)
|
||||
if (typeof addition !== 'string') return value
|
||||
return value + addition
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'string') return value
|
||||
return value + val
|
||||
} else if (typeof value === 'string') {
|
||||
return value + props.value
|
||||
}
|
||||
@@ -216,10 +207,9 @@ export async function Add (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = getAttributeValue(control, execution, context)
|
||||
return value + offset
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value + val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value + props.value
|
||||
}
|
||||
@@ -234,10 +224,9 @@ export async function Subtract (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const offset = getAttributeValue(control, execution, context)
|
||||
return value - offset
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value - val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value - props.value
|
||||
}
|
||||
@@ -252,10 +241,9 @@ export async function Multiply (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
return value * val
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return value * val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return value * props.value
|
||||
}
|
||||
@@ -270,13 +258,11 @@ export async function Divide (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
if (val === 0) {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value / val
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (val === 0 || typeof val !== 'number') {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value / val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
if (props.value === 0) {
|
||||
return value // Avoid division by zero
|
||||
@@ -294,13 +280,11 @@ export async function Modulo (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
if (val === 0) {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value % val
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (val === 0 || typeof val !== 'number') {
|
||||
return value // Avoid division by zero
|
||||
}
|
||||
return value % val
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
if (props.value === 0) {
|
||||
return value // Avoid division by zero
|
||||
@@ -318,10 +302,9 @@ export async function Power (
|
||||
): Promise<number> {
|
||||
const context = parseContext(props.value)
|
||||
if (context !== undefined) {
|
||||
if (context.type === 'attribute') {
|
||||
const val = getAttributeValue(control, execution, context)
|
||||
return Math.pow(value, val)
|
||||
}
|
||||
const val = await getContextValue(props.value, control, execution)
|
||||
if (typeof val !== 'number') return value
|
||||
return Math.pow(value, val)
|
||||
} else if (typeof value === 'number' && typeof props.value === 'number') {
|
||||
return Math.pow(value, props.value)
|
||||
}
|
||||
@@ -372,4 +355,17 @@ export async function RoleContext (
|
||||
return users.map((it) => it.user)
|
||||
}
|
||||
|
||||
export async function CurrentUser (
|
||||
value: null,
|
||||
props: Record<string, any>,
|
||||
control: ProcessControl
|
||||
): Promise<Ref<Person> | undefined> {
|
||||
const socialId = await control.client.findOne(contact.class.SocialIdentity, { _id: control.client.user as any })
|
||||
return socialId?.attachedTo
|
||||
}
|
||||
|
||||
export async function CurrentDate (): Promise<Timestamp> {
|
||||
return Date.now()
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
@@ -13,22 +13,266 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import { getObjectValue } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel } from '@hcengineering/platform'
|
||||
import process, { Execution, SelectedContext, processError } from '@hcengineering/process'
|
||||
import { ProcessControl } from '@hcengineering/server-process'
|
||||
import core, { ArrOf, Doc, getObjectValue, RefTo } from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
||||
import process, {
|
||||
Execution,
|
||||
ExecutionError,
|
||||
parseContext,
|
||||
ProcessError,
|
||||
processError,
|
||||
SelectedContext,
|
||||
SelectedContextFunc,
|
||||
SelectedExecutionContext,
|
||||
SelectedNested,
|
||||
SelectedRelation,
|
||||
SelectedUserRequest
|
||||
} from '@hcengineering/process'
|
||||
import serverProcess, { ProcessControl } from '@hcengineering/server-process'
|
||||
|
||||
export function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any {
|
||||
export async function getContextValue (value: any, control: ProcessControl, execution: Execution): Promise<any> {
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let value: any | undefined
|
||||
try {
|
||||
if (context.type === 'attribute') {
|
||||
value = getAttributeValue(control, execution, context)
|
||||
} else if (context.type === 'relation') {
|
||||
value = await getRelationValue(control, execution, context)
|
||||
} else if (context.type === 'nested') {
|
||||
value = await getNestedValue(control, execution, context)
|
||||
} else if (context.type === 'userRequest') {
|
||||
value = getUserRequestValue(control, execution, context)
|
||||
} else if (context.type === 'function') {
|
||||
value = await getFunctionValue(control, execution, context)
|
||||
} else if (context.type === 'context') {
|
||||
value = await getExecutionContextValue(control, execution, context)
|
||||
}
|
||||
return await fillValue(value, context, control, execution)
|
||||
} catch (err: any) {
|
||||
if (err instanceof ProcessError && context.fallbackValue !== undefined) {
|
||||
return await fillValue(context.fallbackValue, context, control, execution)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
for (const key in value) {
|
||||
value[key] = await getContextValue(value[key], control, execution)
|
||||
}
|
||||
return value
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card !== undefined) {
|
||||
const val = getObjectValue(context.key, card)
|
||||
if (val == null) {
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.key)
|
||||
throw processError(
|
||||
process.error.EmptyAttributeContextValue,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
} else {
|
||||
throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function fillValue (
|
||||
value: any,
|
||||
context: SelectedContext,
|
||||
control: ProcessControl,
|
||||
execution: Execution
|
||||
): Promise<any> {
|
||||
for (const func of context.functions ?? []) {
|
||||
const transform = control.client.getModel().findObject(func.func)
|
||||
if (transform === undefined) throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
value = await f(value, func.props, control, execution)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
async function getNestedValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedNested
|
||||
): Promise<any | ExecutionError> {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
const val = getObjectValue(context.key, card)
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.path)
|
||||
if (attr === undefined) throw processError(process.error.AttributeNotExists, { key: context.path })
|
||||
const nestedValue = getObjectValue(context.path, card)
|
||||
if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label })
|
||||
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
|
||||
const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
const target = await control.client.findAll(targetClass, {
|
||||
_id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] }
|
||||
})
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label })
|
||||
const nested = control.client.getHierarchy().findAttribute(targetClass, context.key)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = Array.isArray(reduced)
|
||||
? reduced.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val =
|
||||
Array.isArray(target) && target.length > 1
|
||||
? target.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.key)
|
||||
throw processError(
|
||||
process.error.EmptyAttributeContextValue,
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function getRelationValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedRelation
|
||||
): Promise<any> {
|
||||
const assoc = control.client.getModel().findObject(context.association)
|
||||
if (assoc === undefined) throw processError(process.error.RelationNotExists, {})
|
||||
const targetClass = context.direction === 'A' ? assoc.classA : assoc.classB
|
||||
const q = context.direction === 'A' ? { docB: execution.card } : { docA: execution.card }
|
||||
const relations = await control.client.findAll(core.class.Relation, { association: assoc._id, ...q })
|
||||
const name = context.direction === 'A' ? assoc.nameA : assoc.nameB
|
||||
if (relations.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: name })
|
||||
const ids = relations.map((it) => {
|
||||
return context.direction === 'A' ? it.docA : it.docB
|
||||
})
|
||||
const target = await control.client.findAll(targetClass, { _id: { $in: ids } })
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name })
|
||||
const attr = context.key !== '' ? control.client.getHierarchy().findAttribute(targetClass, context.key) : undefined
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = Array.isArray(reduced)
|
||||
? reduced.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val =
|
||||
Array.isArray(target) && target.length > 1
|
||||
? target.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function getFunctionValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedContextFunc
|
||||
): Promise<any> {
|
||||
const func = control.client.getModel().findObject(context.func)
|
||||
if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const impl = control.client.getHierarchy().as(func, serverProcess.mixin.FuncImpl)
|
||||
if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const f = await getResource(impl.func)
|
||||
const res = await f(null, context.props, control, execution)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const val = await f(res, {}, control, execution)
|
||||
if (val == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return val
|
||||
}
|
||||
if (res == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getUserRequestValue (control: ProcessControl, execution: Execution, context: SelectedUserRequest): any {
|
||||
const userContext = execution.context[context.id]
|
||||
if (userContext !== undefined) return userContext
|
||||
const attr = control.client.getHierarchy().findAttribute(context._class, context.key)
|
||||
throw processError(
|
||||
process.error.UserRequestedValueNotProvided,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
|
||||
async function getExecutionContextValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedExecutionContext
|
||||
): Promise<any> {
|
||||
const userContext = execution.context[context.id]
|
||||
const _process = control.client.getModel().findObject(execution.process)
|
||||
const processContext = _process?.context?.[context.id]
|
||||
if (userContext !== undefined) {
|
||||
if (context.key === '' || context.key === '_id') return userContext
|
||||
if (processContext !== undefined) {
|
||||
const contextVal = await control.client.findOne(processContext?._class, { _id: userContext })
|
||||
if (contextVal !== undefined) {
|
||||
const val = getObjectValue(context.key, contextVal)
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
throw processError(process.error.ContextValueNotProvided, { name: processContext?.name ?? context.id })
|
||||
}
|
||||
|
||||
@@ -80,7 +80,9 @@ export default plugin(serverProcessId, {
|
||||
Insert: '' as Resource<TransformFunc>,
|
||||
Remove: '' as Resource<TransformFunc>,
|
||||
RemoveFirst: '' as Resource<TransformFunc>,
|
||||
RemoveLast: '' as Resource<TransformFunc>
|
||||
RemoveLast: '' as Resource<TransformFunc>,
|
||||
CurrentUser: '' as Resource<TransformFunc>,
|
||||
CurrentDate: '' as Resource<TransformFunc>
|
||||
},
|
||||
trigger: {
|
||||
OnTransition: '' as Resource<TriggerFunc>,
|
||||
|
||||
+10
-258
@@ -14,20 +14,17 @@
|
||||
//
|
||||
|
||||
import cardPlugin from '@hcengineering/card'
|
||||
import core, {
|
||||
ArrOf,
|
||||
import {
|
||||
Doc,
|
||||
generateId,
|
||||
getDiffUpdate,
|
||||
getObjectValue,
|
||||
MeasureContext,
|
||||
Ref,
|
||||
RefTo,
|
||||
Tx,
|
||||
TxProcessor,
|
||||
WorkspaceUuid
|
||||
} from '@hcengineering/core'
|
||||
import { getEmbeddedLabel, getResource } from '@hcengineering/platform'
|
||||
import { getResource } from '@hcengineering/platform'
|
||||
import process, {
|
||||
Execution,
|
||||
ExecutionError,
|
||||
@@ -38,12 +35,6 @@ import process, {
|
||||
parseError,
|
||||
processError,
|
||||
ProcessError,
|
||||
SelectedContext,
|
||||
SelectedContextFunc,
|
||||
SelectedExecutionContext,
|
||||
SelectedNested,
|
||||
SelectedRelation,
|
||||
SelectedUserRequest,
|
||||
State,
|
||||
Step,
|
||||
Transition
|
||||
@@ -55,11 +46,12 @@ import serverProcess, {
|
||||
ProcessMessage,
|
||||
TriggerImpl
|
||||
} from '@hcengineering/server-process'
|
||||
import { isError } from './errors'
|
||||
import { getClient, releaseClient } from './utils'
|
||||
import { getTemporalClient } from './temporal'
|
||||
import { getContextValue } from '@hcengineering/server-process-resources'
|
||||
import { Client as TemporalClient } from '@temporalio/client'
|
||||
import config from './config'
|
||||
import { isError } from './errors'
|
||||
import { getTemporalClient } from './temporal'
|
||||
import { getClient, releaseClient } from './utils'
|
||||
|
||||
const activeExecutions = new Set<Ref<Execution>>()
|
||||
|
||||
@@ -149,11 +141,13 @@ async function findTransitions (
|
||||
if (record.event === process.trigger.OnExecutionContinue) {
|
||||
const transition = execution.error?.[0].transition
|
||||
if (transition === undefined) return
|
||||
return control.client.getModel().findAllSync(process.class.Transition, {
|
||||
const res = control.client.getModel().findAllSync(process.class.Transition, {
|
||||
_id: transition,
|
||||
from: execution.currentState,
|
||||
process: execution.process
|
||||
})[0]
|
||||
if (res.from === execution.currentState || res.from === null) {
|
||||
return res
|
||||
}
|
||||
}
|
||||
const transitions = control.client.getModel().findAllSync(process.class.Transition, {
|
||||
from: execution.currentState,
|
||||
@@ -449,130 +443,6 @@ async function executeAction<T extends Doc> (
|
||||
}
|
||||
}
|
||||
|
||||
async function fillValue (
|
||||
value: any,
|
||||
context: SelectedContext,
|
||||
control: ProcessControl,
|
||||
execution: Execution
|
||||
): Promise<any> {
|
||||
for (const func of context.functions ?? []) {
|
||||
const transform = control.client.getModel().findObject(func.func)
|
||||
if (transform === undefined) throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: func.func }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
value = await f(value, func.props, control, execution)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
async function getNestedValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedNested
|
||||
): Promise<any | ExecutionError> {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card === undefined) throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.path)
|
||||
if (attr === undefined) throw processError(process.error.AttributeNotExists, { key: context.path })
|
||||
const nestedValue = getObjectValue(context.path, card)
|
||||
if (nestedValue === undefined) throw processError(process.error.EmptyAttributeContextValue, {}, { attr: attr.label })
|
||||
const parentType = attr.type._class === core.class.ArrOf ? (attr.type as ArrOf<Doc>).of : attr.type
|
||||
const targetClass = parentType._class === core.class.RefTo ? (parentType as RefTo<Doc>).to : parentType._class
|
||||
const target = await control.client.findAll(targetClass, {
|
||||
_id: { $in: Array.isArray(nestedValue) ? nestedValue : [nestedValue] }
|
||||
})
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, {}, { attr: attr.label })
|
||||
const nested = control.client.getHierarchy().findAttribute(targetClass, context.key)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val = getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{},
|
||||
{ parent: attr.label, attr: nested?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function getRelationValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedRelation
|
||||
): Promise<any> {
|
||||
const assoc = control.client.getModel().findObject(context.association)
|
||||
if (assoc === undefined) throw processError(process.error.RelationNotExists, {})
|
||||
const targetClass = context.direction === 'A' ? assoc.classA : assoc.classB
|
||||
const q = context.direction === 'A' ? { docB: execution.card } : { docA: execution.card }
|
||||
const relations = await control.client.findAll(core.class.Relation, { association: assoc._id, ...q })
|
||||
const name = context.direction === 'A' ? assoc.nameA : assoc.nameB
|
||||
if (relations.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: name })
|
||||
const ids = relations.map((it) => {
|
||||
return context.direction === 'A' ? it.docA : it.docB
|
||||
})
|
||||
const target = await control.client.findAll(targetClass, { _id: { $in: ids } })
|
||||
if (target.length === 0) throw processError(process.error.RelatedObjectNotFound, { attr: context.name })
|
||||
const attr = context.key !== '' ? control.client.getHierarchy().findAttribute(targetClass, context.key) : undefined
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const reduced = await f(target, {}, control, execution)
|
||||
const val = Array.isArray(reduced)
|
||||
? reduced.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, reduced)
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
const val =
|
||||
Array.isArray(target) && target.length > 1
|
||||
? target.map((v) => getObjectValue(context.key, v))
|
||||
: getObjectValue(context.key, target[0])
|
||||
if (val == null) {
|
||||
throw processError(
|
||||
process.error.EmptyRelatedObjectValue,
|
||||
{ parent: name },
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.name) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
async function fillParams<T extends Doc> (
|
||||
params: MethodParams<T>,
|
||||
execution: Execution,
|
||||
@@ -587,124 +457,6 @@ async function fillParams<T extends Doc> (
|
||||
return res
|
||||
}
|
||||
|
||||
function getAttributeValue (control: ProcessControl, execution: Execution, context: SelectedContext): any {
|
||||
const card = control.cache.get(execution.card)
|
||||
if (card !== undefined) {
|
||||
const val = getObjectValue(context.key, card)
|
||||
if (val == null) {
|
||||
const attr = control.client.getHierarchy().findAttribute(card._class, context.key)
|
||||
throw processError(
|
||||
process.error.EmptyAttributeContextValue,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
return val
|
||||
} else {
|
||||
throw processError(process.error.ObjectNotFound, { _id: execution.card }, {}, true)
|
||||
}
|
||||
}
|
||||
|
||||
async function getContextValue (value: any, control: ProcessControl, execution: Execution): Promise<any> {
|
||||
const context = parseContext(value)
|
||||
if (context !== undefined) {
|
||||
let value: any | undefined
|
||||
try {
|
||||
if (context.type === 'attribute') {
|
||||
value = getAttributeValue(control, execution, context)
|
||||
} else if (context.type === 'relation') {
|
||||
value = await getRelationValue(control, execution, context)
|
||||
} else if (context.type === 'nested') {
|
||||
value = await getNestedValue(control, execution, context)
|
||||
} else if (context.type === 'userRequest') {
|
||||
value = getUserRequestValue(control, execution, context)
|
||||
} else if (context.type === 'function') {
|
||||
value = await getFunctionValue(control, execution, context)
|
||||
} else if (context.type === 'context') {
|
||||
value = await getExecutionContextValue(control, execution, context)
|
||||
}
|
||||
return await fillValue(value, context, control, execution)
|
||||
} catch (err: any) {
|
||||
if (err instanceof ProcessError && context.fallbackValue !== undefined) {
|
||||
return await fillValue(context.fallbackValue, context, control, execution)
|
||||
}
|
||||
throw err
|
||||
}
|
||||
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
for (const key in value) {
|
||||
value[key] = await getContextValue(value[key], control, execution)
|
||||
}
|
||||
return value
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
async function getFunctionValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedContextFunc
|
||||
): Promise<any> {
|
||||
const func = control.client.getModel().findObject(context.func)
|
||||
if (func === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const impl = control.client.getHierarchy().as(func, serverProcess.mixin.FuncImpl)
|
||||
if (impl === undefined) throw processError(process.error.MethodNotFound, { methodId: context.func }, {}, true)
|
||||
const f = await getResource(impl.func)
|
||||
const res = await f(null, context.props, control, execution)
|
||||
if (context.sourceFunction !== undefined) {
|
||||
const transform = control.client.getModel().findObject(context.sourceFunction)
|
||||
if (transform === undefined) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
if (!control.client.getHierarchy().hasMixin(transform, serverProcess.mixin.FuncImpl)) {
|
||||
throw processError(process.error.MethodNotFound, { methodId: context.sourceFunction }, {}, true)
|
||||
}
|
||||
const funcImpl = control.client.getHierarchy().as(transform, serverProcess.mixin.FuncImpl)
|
||||
const f = await getResource(funcImpl.func)
|
||||
const val = await f(res, {}, control, execution)
|
||||
if (val == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return val
|
||||
}
|
||||
if (res == null) {
|
||||
throw processError(process.error.EmptyFunctionResult, {}, { func: func.label })
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
function getUserRequestValue (control: ProcessControl, execution: Execution, context: SelectedUserRequest): any {
|
||||
const userContext = execution.context[context.id]
|
||||
if (userContext !== undefined) return userContext
|
||||
const attr = control.client.getHierarchy().findAttribute(context._class, context.key)
|
||||
throw processError(
|
||||
process.error.UserRequestedValueNotProvided,
|
||||
{},
|
||||
{ attr: attr?.label ?? getEmbeddedLabel(context.key) }
|
||||
)
|
||||
}
|
||||
|
||||
async function getExecutionContextValue (
|
||||
control: ProcessControl,
|
||||
execution: Execution,
|
||||
context: SelectedExecutionContext
|
||||
): Promise<any> {
|
||||
const userContext = execution.context[context.id]
|
||||
const _process = control.client.getModel().findObject(execution.process)
|
||||
const processContext = _process?.context?.[context.id]
|
||||
if (userContext !== undefined) {
|
||||
if (context.key === '' || context.key === '_id') return userContext
|
||||
if (processContext !== undefined) {
|
||||
const contextVal = await control.client.findOne(processContext?._class, { _id: userContext })
|
||||
if (contextVal !== undefined) {
|
||||
const val = getObjectValue(context.key, contextVal)
|
||||
return val
|
||||
}
|
||||
}
|
||||
}
|
||||
throw processError(process.error.ContextValueNotProvided, { name: processContext?.name ?? context.id })
|
||||
}
|
||||
|
||||
async function checkParent (execution: Execution, control: ProcessControl): Promise<void> {
|
||||
const subProcesses = await control.client.findAll(process.class.Execution, {
|
||||
parentId: execution.parentId,
|
||||
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
command: start-single-node --insecure
|
||||
restart: unless-stopped
|
||||
redpanda:
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v24.3.6
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v25.2.3
|
||||
command:
|
||||
- redpanda
|
||||
- start
|
||||
@@ -60,7 +60,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
redpanda_console:
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.3
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.10
|
||||
entrypoint: /bin/sh
|
||||
command: -c 'echo "$$CONSOLE_CONFIG_FILE" > /tmp/config.yml; /app/console'
|
||||
environment:
|
||||
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
command: start-single-node --insecure
|
||||
restart: unless-stopped
|
||||
redpanda:
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v24.3.6
|
||||
image: docker.redpanda.com/redpandadata/redpanda:v25.2.3
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
command:
|
||||
@@ -66,7 +66,7 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
redpanda_console:
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.3
|
||||
image: docker.redpanda.com/redpandadata/console:v2.8.10
|
||||
extra_hosts:
|
||||
- 'huly.local:host-gateway'
|
||||
entrypoint: /bin/sh
|
||||
|
||||
Reference in New Issue
Block a user