Process notifications (#9501)

Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
Denis Bykhov
2025-07-09 10:12:10 +05:00
committed by GitHub
parent 27ab94c9a0
commit f55ac2e3d4
22 changed files with 168 additions and 24 deletions
+11
View File
@@ -122,6 +122,12 @@ export class TExecutionLog extends TDoc implements ExecutionLog {
@Prop(TypeRef(process.class.Execution), process.string.Execution)
execution!: Ref<Execution>
@Prop(TypeRef(process.class.Process), process.string.Process)
process!: Ref<Process>
@Prop(TypeRef(card.class.Card), card.string.Card)
card!: Ref<Card>
@Prop(TypeRef(process.class.Transition), process.string.Transition)
transition?: Ref<Transition>
@@ -925,6 +931,11 @@ export function createModel (builder: Builder): void {
label: process.string.Processes,
component: process.component.ProcessesSettingSection
})
builder.createDoc(presentation.class.ComponentPointExtension, core.space.Model, {
extension: workbench.extensions.WorkbenchExtensions,
component: process.component.NotifierExtension
})
}
export { processId } from '@hcengineering/process'
+1
View File
@@ -71,6 +71,7 @@ export { default as CardSelector } from './components/CardSelector.svelte'
export { default as CardIcon } from './components/CardIcon.svelte'
export { default as Navigator } from './components/navigator-next/Navigator.svelte'
export { default as Favorites } from './components/Favorites.svelte'
export { default as CardPresenter } from './components/CardPresenter.svelte'
export * from './types'
export { getCardIconInfo } from './utils'
+5
View File
@@ -21,4 +21,9 @@
<path d="M16 2.00012C8.2 2.00012 2 8.20012 2 16.0001C2 23.8001 8.2 30.0001 16 30.0001C23.8 30.0001 30 23.8001 30 16.0001C30 8.20012 23.8 2.00012 16 2.00012ZM16 28.0001C9.4 28.0001 4 22.6001 4 16.0001C4 9.40012 9.4 4.00012 16 4.00012C22.6 4.00012 28 9.40012 28 16.0001C28 22.6001 22.6 28.0001 16 28.0001Z" />
<path d="M9.29289 9.29326C8.90237 9.68378 8.90237 10.3169 9.29289 10.7075L14.5858 16.0004L9.29289 21.2933C8.90237 21.6838 8.90237 22.3169 9.29289 22.7075C9.68342 23.098 10.3166 23.098 10.7071 22.7075L16 17.4146L21.2929 22.7075C21.6834 23.098 22.3166 23.098 22.7071 22.7075C23.0976 22.3169 23.0976 21.6838 22.7071 21.2933L17.4142 16.0004L22.7071 10.7075C23.0976 10.3169 23.0976 9.68378 22.7071 9.29326C22.3166 8.90274 21.6834 8.90274 21.2929 9.29326L16 14.5862L10.7071 9.29326C10.3166 8.90274 9.68342 8.90274 9.29289 9.29326Z" />
</symbol>
<symbol id="start" viewBox="0 0 32 32">
<path
d="M27.71 4.29049C27.575 4.15616 27.4045 4.06316 27.2185 4.02241C27.0325 3.98167 26.8388 3.99488 26.66 4.06049L4.66 12.0605C4.47027 12.1325 4.30692 12.2604 4.19165 12.4274C4.07638 12.5944 4.01465 12.7926 4.01465 12.9955C4.01465 13.1984 4.07638 13.3965 4.19165 13.5635C4.30692 13.7305 4.47027 13.8585 4.66 13.9305L14.26 17.7705L18.1 27.3705C18.1721 27.5519 18.2958 27.7082 18.4557 27.8201C18.6157 27.9321 18.8049 27.9948 19 28.0005C19.2021 27.9963 19.3982 27.9311 19.5624 27.8132C19.7266 27.6954 19.8513 27.5306 19.92 27.3405L27.92 5.34049C27.9881 5.16356 28.0046 4.97092 27.9674 4.78501C27.9302 4.5991 27.8409 4.4276 27.71 4.29049ZM19 24.2005L16.21 17.2005L20.295 13.1155C20.6844 12.7261 20.6844 12.0948 20.295 11.7055C19.9056 11.3161 19.2744 11.3161 18.885 11.7055L14.76 15.8305L7.8 13.0005L25.33 6.67049L19 24.2005Z"
/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Zaokrouhlit dolů",
"Ceil": "Zaokrouhlit nahoru",
"Absolute": "Absolutní hodnota",
"LogAction": "Událost"
"LogAction": "Událost",
"Started": "Spuštěno"
},
"error": {
"MethodNotFound": "Metoda nenalezena: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Abrunden",
"Ceil": "Aufrunden",
"Absolute": "Absolutwert",
"LogAction": "Ereignis"
"LogAction": "Ereignis",
"Started": "Gestartet"
},
"error": {
"MethodNotFound": "Methode nicht gefunden: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Absolute": "Absolute",
"Ceil": "Ceil",
"Floor": "Floor",
"LogAction": "Event"
"LogAction": "Event",
"Started": "Started"
},
"error": {
"MethodNotFound": "Method not found: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Redondear hacia abajo",
"Ceil": "Redondear hacia arriba",
"Absolute": "Valor absoluto",
"LogAction": "Evento"
"LogAction": "Evento",
"Started": "Iniciado"
},
"error": {
"MethodNotFound": "Método no encontrado: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Arrondir vers le bas",
"Ceil": "Arrondir vers le haut",
"Absolute": "Valeur absolue",
"LogAction": "Événement"
"LogAction": "Événement",
"Started": "Démarré"
},
"error": {
"MethodNotFound": "Méthode introuvable : {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Arrotondare per difetto",
"Ceil": "Arrotondare per eccesso",
"Absolute": "Valore assoluto",
"LogAction": "Evento"
"LogAction": "Evento",
"Started": "Avviato"
},
"error": {
"MethodNotFound": "Metodo non trovato: {methodId}",
+2 -1
View File
@@ -86,7 +86,8 @@
"Floor": "切り捨て",
"Ceil": "切り上げ",
"Absolute": "絶対値",
"LogAction": "イベント"
"LogAction": "イベント",
"Started": "開始"
},
"error": {
"MethodNotFound": "メソッドが見つかりません: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Arredondar para baixo",
"Ceil": "Arredondar para cima",
"Absolute": "Valor absoluto",
"LogAction": "Evento"
"LogAction": "Evento",
"Started": "Iniciado"
},
"error": {
"MethodNotFound": "Método não encontrado: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "Округлить вниз",
"Ceil": "Округлить вверх",
"Absolute": "Абсолютное значение",
"LogAction": "Событие"
"LogAction": "Событие",
"Started": "Начат"
},
"error": {
"MethodNotFound": "Метод не найден: {methodId}",
+2 -1
View File
@@ -87,7 +87,8 @@
"Floor": "向下取整",
"Ceil": "向上取整",
"Absolute": "绝对值",
"LogAction": "事件"
"LogAction": "事件",
"Started": "已启动"
},
"error": {
"MethodNotFound": "找不到方法:{methodId}",
+2 -1
View File
@@ -23,5 +23,6 @@ loadMetadata(process.icon, {
Steps: `${icons}#steps`,
ToDo: `${icons}#todo`,
ToDoRemove: `${icons}#todo-remove`,
WaitSubprocesses: `${icons}#subprocesses`
WaitSubprocesses: `${icons}#subprocesses`,
Start: `${icons}#start`
})
@@ -118,12 +118,6 @@
}}
/>
<!-- <Card
label={process.string.Execution}
okAction={() => { dispatch('close') }}
width={'large'}
on:close
> -->
<Modal
type={'type-popup'}
width={'large'}
@@ -168,4 +162,3 @@
/>
{/if}
</Modal>
<!-- </Card> -->
@@ -0,0 +1,42 @@
<!--
// 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 { ExecutionLog } from '@hcengineering/process'
import { Notification, NotificationToast } from '@hcengineering/ui'
import LogActionPresenter from './LogActionPresenter.svelte'
import TransitionRefPresenter from './settings/TransitionRefPresenter.svelte'
import { CardPresenter } from '@hcengineering/card-resources'
export let notification: Notification
export let onRemove: () => void
$: val = notification?.params?.event as ExecutionLog
</script>
{#if val}
<NotificationToast title={notification.title} severity={notification.severity} onClose={onRemove}>
<svelte:fragment slot="content">
<div class="fs-title">
<CardPresenter value={val.card} shouldShowAvatar />
</div>
<div class="flex-row-center flex-wrap gap-2 reverse">
<LogActionPresenter value={val.action} />
{#if val.transition}
<TransitionRefPresenter value={val.transition} />
{/if}
</div>
</svelte:fragment>
</NotificationToast>
{/if}
@@ -13,7 +13,7 @@
// limitations under the License.
-->
<script lang="ts">
import { ExecutionLogAction } from '@hcengineering/process'
import process, { ExecutionLogAction } from '@hcengineering/process'
import { Icon } from '@hcengineering/ui'
import view from '@hcengineering/view'
@@ -21,7 +21,7 @@
</script>
{#if value === ExecutionLogAction.Started}
<Icon icon={view.icon.Circle} size={'small'} />
<Icon icon={process.icon.Start} size={'small'} />
{:else if value === ExecutionLogAction.Transition}
<Icon icon={view.icon.ArrowRight} size={'small'} />
{:else}
@@ -0,0 +1,68 @@
<!--
// 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 { ExecutionLog, ExecutionLogAction } from '@hcengineering/process'
import { addNotification, NotificationSeverity, themeStore } from '@hcengineering/ui'
import { translate } from '@hcengineering/platform'
import { createQuery, getClient } from '@hcengineering/presentation'
import process from '../plugin'
import { Ref } from '@hcengineering/core'
import ExecutionNotification from './ExecutionNotification.svelte'
const processed = new Set<Ref<ExecutionLog>>()
const client = getClient()
let init = false
const query = createQuery()
query.query(process.class.ExecutionLog, { createdOn: { $gt: Date.now() } }, (res) => {
if (!init) {
res.forEach((event) => {
processed.add(event._id)
})
init = true
} else {
notifyAll(res)
}
})
async function notifyAll (events: ExecutionLog[]): Promise<void> {
for (const event of events) {
if (processed.has(event._id)) continue
await notify(event)
}
}
async function notify (event: ExecutionLog): Promise<void> {
processed.add(event._id)
const _process = client.getModel().findObject(event.process)
addNotification(
await translate(
event.action === ExecutionLogAction.Started
? process.string.Started
: event.action === ExecutionLogAction.Transition
? process.string.Transition
: process.string.Rollback,
{},
$themeStore.language
),
_process?.name ?? (await translate(process.string.Process, {}, $themeStore.language)),
ExecutionNotification,
{
event
},
event.action === ExecutionLogAction.Rollback ? NotificationSeverity.Warning : NotificationSeverity.Info
)
}
</script>
+3 -1
View File
@@ -52,6 +52,7 @@ import { ProcessMiddleware } from './middleware'
import { continueExecution, showDoneQuery, todoTranstionCheck } from './utils'
import LogActionPresenter from './components/LogActionPresenter.svelte'
import TransitionRefPresenter from './components/settings/TransitionRefPresenter.svelte'
import NotifierExtension from './components/NotifierExtension.svelte'
export default async (): Promise<Resources> => ({
actionImpl: {
@@ -88,7 +89,8 @@ export default async (): Promise<Resources> => ({
ProcessesCardSection,
TransitionEditor,
TransitionRefPresenter,
LogActionPresenter
LogActionPresenter,
NotifierExtension
},
transformEditor: {
DateOffsetEditor,
+4 -2
View File
@@ -55,7 +55,8 @@ export default mergeIds(processId, process, {
TransitionEditor: '' as AnyComponent,
StateEditor: '' as AnyComponent,
TransitionRefPresenter: '' as AnyComponent,
LogActionPresenter: '' as AnyComponent
LogActionPresenter: '' as AnyComponent,
NotifierExtension: '' as AnyComponent
},
transformEditor: {
DateOffsetEditor: '' as AnyComponent,
@@ -148,6 +149,7 @@ export default mergeIds(processId, process, {
Search: '' as IntlString,
Separator: '' as IntlString,
Start: '' as IntlString,
End: '' as IntlString
End: '' as IntlString,
Started: '' as IntlString
}
})
+4 -1
View File
@@ -67,6 +67,8 @@ export interface Transition extends Doc {
export interface ExecutionLog extends Doc {
execution: Ref<Execution>
card: Ref<Card>
process: Ref<Process>
transition?: Ref<Transition>
action: ExecutionLogAction
}
@@ -217,7 +219,8 @@ export default plugin(processId, {
States: '' as Asset,
ToDo: '' as Asset,
WaitSubprocesses: '' as Asset,
ToDoRemove: '' as Asset
ToDoRemove: '' as Asset,
Start: '' as Asset
},
function: {
FirstValue: '' as Ref<ProcessFunction>,
@@ -177,6 +177,8 @@ async function executeTransition (
res.push(
control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, {
execution: execution._id,
process: execution.process,
card: execution.card,
transition: transition._id,
action: ExecutionLogAction.Transition
})
@@ -479,6 +481,8 @@ async function initState (execution: Execution, control: TriggerControl): Promis
res.push(
control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, {
execution: execution._id,
process: execution.process,
card: execution.card,
transition: transition._id,
action: ExecutionLogAction.Started
})
@@ -546,6 +550,8 @@ export async function OnProcessToDoRemove (txes: Tx[], control: TriggerControl):
res.push(
control.txFactory.createTxCreateDoc(process.class.ExecutionLog, execution.space, {
execution: execution._id,
process: execution.process,
card: execution.card,
action: ExecutionLogAction.Rollback
})
)