mirror of
https://github.com/hcengineering/platform.git
synced 2025-05-15 20:10:52 +00:00
Add parallel execution restriction feature to processes (#8477)
Signed-off-by: Denis Bykhov <bykhov.denis@gmail.com>
This commit is contained in:
parent
b9c6d337d8
commit
e2a2011ad8
@ -74,6 +74,9 @@ export class TProcess extends TDoc implements Process {
|
|||||||
@Prop(ArrOf(TypeRef(process.class.State)), process.string.States)
|
@Prop(ArrOf(TypeRef(process.class.State)), process.string.States)
|
||||||
states!: Ref<State>[]
|
states!: Ref<State>[]
|
||||||
|
|
||||||
|
@Prop(TypeBoolean(), process.string.ParallelExecutionForbidden)
|
||||||
|
parallelExecutionForbidden?: boolean
|
||||||
|
|
||||||
@Prop(TypeBoolean(), process.string.StartAutomatically)
|
@Prop(TypeBoolean(), process.string.StartAutomatically)
|
||||||
autoStart: boolean | undefined
|
autoStart: boolean | undefined
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
export let readonly: boolean = false
|
export let readonly: boolean = false
|
||||||
export let label: IntlString = card.string.Card
|
export let label: IntlString = card.string.Card
|
||||||
export let _class: Ref<Class<Card>>
|
export let _class: Ref<Class<Card>>
|
||||||
|
export let ignoreObjects: Ref<Card>[] | undefined = undefined
|
||||||
|
|
||||||
export let focusIndex: number | undefined = undefined
|
export let focusIndex: number | undefined = undefined
|
||||||
export let kind: ButtonKind = 'no-border'
|
export let kind: ButtonKind = 'no-border'
|
||||||
@ -53,7 +54,7 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
showPopup(CardsPopup, { selected: value, _class }, eventToHTMLElement(event), change)
|
showPopup(CardsPopup, { selected: value, _class, ignoreObjects }, eventToHTMLElement(event), change)
|
||||||
}
|
}
|
||||||
|
|
||||||
const change = (val: Card | undefined): void => {
|
const change = (val: Card | undefined): void => {
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
export let _class: Ref<Class<Card>>
|
export let _class: Ref<Class<Card>>
|
||||||
export let selected: Ref<Card> | undefined
|
export let selected: Ref<Card> | undefined
|
||||||
export let selectedObjects: Ref<Card>[] | undefined
|
export let selectedObjects: Ref<Card>[] | undefined
|
||||||
|
export let ignoreObjects: Ref<Card>[] | undefined = undefined
|
||||||
export let multiSelect: boolean = false
|
export let multiSelect: boolean = false
|
||||||
export let allowDeselect: boolean = true
|
export let allowDeselect: boolean = true
|
||||||
export let titleDeselect: IntlString | undefined = undefined
|
export let titleDeselect: IntlString | undefined = undefined
|
||||||
@ -41,6 +42,7 @@
|
|||||||
{multiSelect}
|
{multiSelect}
|
||||||
{allowDeselect}
|
{allowDeselect}
|
||||||
{titleDeselect}
|
{titleDeselect}
|
||||||
|
{ignoreObjects}
|
||||||
type={'object'}
|
type={'object'}
|
||||||
groupBy={'_class'}
|
groupBy={'_class'}
|
||||||
on:update
|
on:update
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "První pracovní den po",
|
"FirstWorkingDayAfter": "První pracovní den po",
|
||||||
"FallbackValueError": "Zobrazit chybu, pokud není k dispozici",
|
"FallbackValueError": "Zobrazit chybu, pokud není k dispozici",
|
||||||
"Required": "Povinné",
|
"Required": "Povinné",
|
||||||
|
"ParallelExecutionForbidden": "Nedovoluj paralelní spuštění",
|
||||||
"StartAutomatically": "Spustit automaticky",
|
"StartAutomatically": "Spustit automaticky",
|
||||||
"Error": "Chyba",
|
"Error": "Chyba",
|
||||||
"Continue": "Pokračovat"
|
"Continue": "Pokračovat"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Erste Arbeitstag nach",
|
"FirstWorkingDayAfter": "Erste Arbeitstag nach",
|
||||||
"FallbackValueError": "Fehler anzeigen, wenn leer",
|
"FallbackValueError": "Fehler anzeigen, wenn leer",
|
||||||
"Required": "Erforderlich",
|
"Required": "Erforderlich",
|
||||||
|
"ParallelExecutionForbidden": "Parallele Ausführung verboten",
|
||||||
"StartAutomatically": "Automatisch starten",
|
"StartAutomatically": "Automatisch starten",
|
||||||
"Error": "Fehler",
|
"Error": "Fehler",
|
||||||
"Continue": "Fortsetzen"
|
"Continue": "Fortsetzen"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "First working day after",
|
"FirstWorkingDayAfter": "First working day after",
|
||||||
"FallbackValueError": "Show error if empty",
|
"FallbackValueError": "Show error if empty",
|
||||||
"Required": "Required",
|
"Required": "Required",
|
||||||
|
"ParallelExecutionForbidden": "Parallel execution forbidden",
|
||||||
"StartAutomatically": "Start automatically",
|
"StartAutomatically": "Start automatically",
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Continue": "Continue"
|
"Continue": "Continue"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Primer día de trabajo después",
|
"FirstWorkingDayAfter": "Primer día de trabajo después",
|
||||||
"FallbackValueError": "Mostrar error si está vacío",
|
"FallbackValueError": "Mostrar error si está vacío",
|
||||||
"Required": "Requerido",
|
"Required": "Requerido",
|
||||||
|
"ParallelExecutionForbidden": "Ejecución paralela prohibida",
|
||||||
"StartAutomatically": "Iniciar automáticamente",
|
"StartAutomatically": "Iniciar automáticamente",
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Continue": "Continuar"
|
"Continue": "Continuar"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Premier jour de travail après",
|
"FirstWorkingDayAfter": "Premier jour de travail après",
|
||||||
"FallbackValueError": "Afficher l'erreur si vide",
|
"FallbackValueError": "Afficher l'erreur si vide",
|
||||||
"Required": "Requis",
|
"Required": "Requis",
|
||||||
|
"ParallelExecutionForbidden": "Exécution parallèle interdite",
|
||||||
"StartAutomatically": "Démarrer automatiquement",
|
"StartAutomatically": "Démarrer automatiquement",
|
||||||
"Error": "Erreur",
|
"Error": "Erreur",
|
||||||
"Continue": "Continuer"
|
"Continue": "Continuer"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Primo giorno lavorativo dopo",
|
"FirstWorkingDayAfter": "Primo giorno lavorativo dopo",
|
||||||
"FallbackValueError": "Mostra errore se vuoto",
|
"FallbackValueError": "Mostra errore se vuoto",
|
||||||
"Required": "Obbligatorio",
|
"Required": "Obbligatorio",
|
||||||
|
"ParallelExecutionForbidden": "Esecuzione parallela vietata",
|
||||||
"StartAutomatically": "Avvia automaticamente",
|
"StartAutomatically": "Avvia automaticamente",
|
||||||
"Error": "Errore",
|
"Error": "Errore",
|
||||||
"Continue": "Continua"
|
"Continue": "Continua"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Primeiro Dia de Trabalho Após",
|
"FirstWorkingDayAfter": "Primeiro Dia de Trabalho Após",
|
||||||
"FallbackValueError": "Mostrar erro se vazio",
|
"FallbackValueError": "Mostrar erro se vazio",
|
||||||
"Required": "Obrigatório",
|
"Required": "Obrigatório",
|
||||||
|
"ParallelExecutionForbidden": "Execução Paralela Proibida",
|
||||||
"StartAutomatically": "Iniciar automaticamente",
|
"StartAutomatically": "Iniciar automaticamente",
|
||||||
"Error": "Erro",
|
"Error": "Erro",
|
||||||
"Continue": "Continuar"
|
"Continue": "Continuar"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "Первый рабочий день после",
|
"FirstWorkingDayAfter": "Первый рабочий день после",
|
||||||
"FallbackValueError": "Показывать ошибку, если недоступно",
|
"FallbackValueError": "Показывать ошибку, если недоступно",
|
||||||
"Required": "Обязательно",
|
"Required": "Обязательно",
|
||||||
|
"ParallelExecutionForbidden": "Одновременное выполнение запрещено",
|
||||||
"StartAutomatically": "Запускать автоматически",
|
"StartAutomatically": "Запускать автоматически",
|
||||||
"Error": "Ошибка",
|
"Error": "Ошибка",
|
||||||
"Continue": "Продолжить"
|
"Continue": "Продолжить"
|
||||||
|
@ -43,6 +43,7 @@
|
|||||||
"FirstWorkingDayAfter": "在之后的第一个工作日",
|
"FirstWorkingDayAfter": "在之后的第一个工作日",
|
||||||
"FallbackValueError": "显示错误,如果为空",
|
"FallbackValueError": "显示错误,如果为空",
|
||||||
"Required": "必需",
|
"Required": "必需",
|
||||||
|
"ParallelExecutionForbidden": "禁止并行执行",
|
||||||
"StartAutomatically": "自动启动",
|
"StartAutomatically": "自动启动",
|
||||||
"Error": "错误",
|
"Error": "错误",
|
||||||
"Continue": "继续"
|
"Continue": "继续"
|
||||||
|
@ -78,6 +78,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function saveRestriction (e: CustomEvent<boolean>): Promise<void> {
|
||||||
|
if (value !== undefined) {
|
||||||
|
await client.update(value, { parallelExecutionForbidden: e.detail })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function saveAutoStart (e: CustomEvent<boolean>): Promise<void> {
|
async function saveAutoStart (e: CustomEvent<boolean>): Promise<void> {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
await client.update(value, { autoStart: e.detail })
|
await client.update(value, { autoStart: e.detail })
|
||||||
@ -167,6 +173,13 @@
|
|||||||
<EditBox bind:value={value.name} on:change={saveName} placeholder={process.string.Untitled} />
|
<EditBox bind:value={value.name} on:change={saveName} placeholder={process.string.Untitled} />
|
||||||
<ButtonIcon icon={IconDelete} size="small" kind="secondary" on:click={handleDelete} />
|
<ButtonIcon icon={IconDelete} size="small" kind="secondary" on:click={handleDelete} />
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<ToggleWithLabel
|
||||||
|
on={value.parallelExecutionForbidden ?? false}
|
||||||
|
on:change={saveRestriction}
|
||||||
|
label={process.string.ParallelExecutionForbidden}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<ToggleWithLabel
|
<ToggleWithLabel
|
||||||
on={value.autoStart ?? false}
|
on={value.autoStart ?? false}
|
||||||
|
@ -50,6 +50,30 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
$: selectedProcess = processes.find((p) => p._id === process)
|
$: selectedProcess = processes.find((p) => p._id === process)
|
||||||
|
|
||||||
|
let ignoreObjects: Ref<Card>[] = []
|
||||||
|
|
||||||
|
$: void filter(process)
|
||||||
|
|
||||||
|
async function filter (process: Ref<Process> | undefined): Promise<void> {
|
||||||
|
if (process === undefined) {
|
||||||
|
ignoreObjects = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const pr = client.getModel().findObject(process)
|
||||||
|
if (pr === undefined) {
|
||||||
|
ignoreObjects = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (pr.parallelExecutionForbidden !== true) {
|
||||||
|
ignoreObjects = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const executions = await client.findAll(plugin.class.Execution, { process, done: false }, {})
|
||||||
|
const cards = new Set(executions.map((it) => it.card))
|
||||||
|
ignoreObjects = [...cards]
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CardPopup
|
<CardPopup
|
||||||
@ -74,6 +98,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if selectedProcess !== undefined}
|
{#if selectedProcess !== undefined}
|
||||||
<CardSelector kind={'regular'} size={'medium'} bind:value={card} _class={selectedProcess.masterTag} />
|
<CardSelector
|
||||||
|
kind={'regular'}
|
||||||
|
size={'medium'}
|
||||||
|
bind:value={card}
|
||||||
|
{ignoreObjects}
|
||||||
|
_class={selectedProcess.masterTag}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</CardPopup>
|
</CardPopup>
|
||||||
|
@ -33,6 +33,31 @@
|
|||||||
const res = client.getModel().findAllSync(process.class.Process, {})
|
const res = client.getModel().findAllSync(process.class.Process, {})
|
||||||
const processes = res.filter((it) => resClasses.includes(it.masterTag))
|
const processes = res.filter((it) => resClasses.includes(it.masterTag))
|
||||||
|
|
||||||
|
async function filterProcesses (processes: Process[]): Promise<Process[]> {
|
||||||
|
const res: Process[] = []
|
||||||
|
const shouldCheck: Process[] = []
|
||||||
|
for (const val of processes) {
|
||||||
|
if (val.parallelExecutionForbidden === true) {
|
||||||
|
shouldCheck.push(val)
|
||||||
|
} else {
|
||||||
|
res.push(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldCheck.length === 0) return res
|
||||||
|
|
||||||
|
const executions = await client.findAll(process.class.Execution, {
|
||||||
|
process: { $in: shouldCheck.map((it) => it._id) },
|
||||||
|
done: false
|
||||||
|
})
|
||||||
|
const notAllowed = new Set(executions.map((it) => it.process))
|
||||||
|
for (const val of shouldCheck) {
|
||||||
|
if (!notAllowed.has(val._id)) {
|
||||||
|
res.push(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
async function runProcess (_id: Ref<Process>): Promise<void> {
|
async function runProcess (_id: Ref<Process>): Promise<void> {
|
||||||
@ -57,6 +82,7 @@
|
|||||||
<Label label={process.string.NoProcesses} />
|
<Label label={process.string.NoProcesses} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
{#await filterProcesses(processes) then processes}
|
||||||
{#each processes as process}
|
{#each processes as process}
|
||||||
<button
|
<button
|
||||||
class="ap-menuItem flex-row-center withIcon w-full"
|
class="ap-menuItem flex-row-center withIcon w-full"
|
||||||
@ -67,6 +93,7 @@
|
|||||||
{process.name}
|
{process.name}
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="ap-space x2" />
|
<div class="ap-space x2" />
|
||||||
|
@ -89,6 +89,7 @@ export default mergeIds(processId, process, {
|
|||||||
FirstWorkingDayAfter: '' as IntlString,
|
FirstWorkingDayAfter: '' as IntlString,
|
||||||
FallbackValueError: '' as IntlString,
|
FallbackValueError: '' as IntlString,
|
||||||
Required: '' as IntlString,
|
Required: '' as IntlString,
|
||||||
|
ParallelExecutionForbidden: '' as IntlString,
|
||||||
StartAutomatically: '' as IntlString,
|
StartAutomatically: '' as IntlString,
|
||||||
Continue: '' as IntlString
|
Continue: '' as IntlString
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ export interface Process extends Doc {
|
|||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
states: Ref<State>[]
|
states: Ref<State>[]
|
||||||
|
parallelExecutionForbidden?: boolean
|
||||||
autoStart?: boolean
|
autoStart?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,10 +528,24 @@ export async function RunSubProcess (
|
|||||||
control: TriggerControl
|
control: TriggerControl
|
||||||
): Promise<ExecuteResult | undefined> {
|
): Promise<ExecuteResult | undefined> {
|
||||||
if (params._id === undefined) return
|
if (params._id === undefined) return
|
||||||
|
const processId = params._id as Ref<Process>
|
||||||
|
const target = control.modelDb.findObject(processId)
|
||||||
|
if (target === undefined) return
|
||||||
|
if (target.parallelExecutionForbidden === true) {
|
||||||
|
const currentExecution = await control.findAll(control.ctx, process.class.Execution, {
|
||||||
|
process: target._id,
|
||||||
|
card: execution.card,
|
||||||
|
done: false
|
||||||
|
})
|
||||||
|
if (currentExecution.length > 0) {
|
||||||
|
// todo, show erro after merge another pr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
const res: Tx[] = []
|
const res: Tx[] = []
|
||||||
res.push(
|
res.push(
|
||||||
control.txFactory.createTxCreateDoc(process.class.Execution, core.space.Workspace, {
|
control.txFactory.createTxCreateDoc(process.class.Execution, core.space.Workspace, {
|
||||||
process: params._id as Ref<Process>,
|
process: processId,
|
||||||
currentState: null,
|
currentState: null,
|
||||||
currentToDo: null,
|
currentToDo: null,
|
||||||
card: execution.card,
|
card: execution.card,
|
||||||
|
Loading…
Reference in New Issue
Block a user