mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-13 05:07:43 +02:00
Merge branch 'develop' into staging-new
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import activity from '@hcengineering/activity'
|
||||
import activity, { type ActivityMessageControl } from '@hcengineering/activity'
|
||||
import { type Role, type Card } from '@hcengineering/card'
|
||||
import {
|
||||
AvatarType,
|
||||
@@ -340,6 +340,13 @@ export function createModel (builder: Builder): void {
|
||||
preposition: contact.string.For
|
||||
})
|
||||
|
||||
// Prevent leaking private social identifiers (emails, external handles) via activity updates.
|
||||
builder.createDoc<ActivityMessageControl<Person>>(activity.class.ActivityMessageControl, core.space.Model, {
|
||||
objectClass: contact.class.Person,
|
||||
skip: [],
|
||||
skipFields: ['socialIds']
|
||||
})
|
||||
|
||||
builder.mixin(contact.mixin.Employee, core.class.Class, activity.mixin.ActivityDoc, {
|
||||
preposition: contact.string.For
|
||||
})
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte'
|
||||
import { Icon, Label, tooltip } from '@hcengineering/ui'
|
||||
import contact, { SocialIdentity, SocialIdentityProvider } from '@hcengineering/contact'
|
||||
import contact, { SocialIdentity, SocialIdentityProvider, getCurrentEmployee } from '@hcengineering/contact'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
|
||||
export let value: SocialIdentity
|
||||
@@ -23,6 +23,7 @@
|
||||
export let shouldShowAvatar = true
|
||||
|
||||
const client = getClient()
|
||||
let isOwner = false
|
||||
|
||||
onMount(() => {
|
||||
if (socialIdProvider == null) {
|
||||
@@ -31,6 +32,10 @@
|
||||
})
|
||||
|
||||
$: icon = socialIdProvider?.icon ?? contact.icon.Profile
|
||||
$: {
|
||||
const me = getCurrentEmployee()
|
||||
isOwner = me != null && value.attachedTo === me
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if socialIdProvider != null}
|
||||
@@ -46,8 +51,12 @@
|
||||
</div>
|
||||
|
||||
<div class="flex-col flex-gap-0-5">
|
||||
<div>{value.displayValue ?? value.value}</div>
|
||||
{#if shouldShowAvatar}
|
||||
{#if isOwner}
|
||||
<div>{value.displayValue ?? value.value}</div>
|
||||
{#if shouldShowAvatar}
|
||||
<div class="type"><Label label={socialIdProvider.label} /></div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="type"><Label label={socialIdProvider.label} /></div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -41,7 +41,8 @@
|
||||
|
||||
async function runProcess (): Promise<void> {
|
||||
if (process === undefined || card === undefined) return
|
||||
await createExecution(card._id, process, card.space)
|
||||
const tx = await createExecution(card._id, process, card.space, client.txFactory)
|
||||
if (tx) await client.tx(tx)
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,8 @@
|
||||
async function runProcess (_id: Ref<Process>): Promise<void> {
|
||||
if (!value) return
|
||||
for (const element of values) {
|
||||
await createExecution(element._id, _id, element.space)
|
||||
const tx = await createExecution(element._id, _id, element.space, client.txFactory)
|
||||
if (tx) await client.tx(tx)
|
||||
}
|
||||
dispatch('close')
|
||||
}
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
|
||||
import cardPlugin, { type Card } from '@hcengineering/card'
|
||||
import core, {
|
||||
generateId,
|
||||
getCurrentAccount,
|
||||
SortingOrder,
|
||||
TxOperations,
|
||||
TxProcessor,
|
||||
type Client,
|
||||
type Doc,
|
||||
type Tx,
|
||||
type TxApplyIf,
|
||||
type TxCreateDoc,
|
||||
type TxCUD,
|
||||
type TxMixin,
|
||||
type TxResult,
|
||||
type TxUpdateDoc
|
||||
@@ -50,27 +53,47 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
return new ProcessMiddleware(client, next)
|
||||
}
|
||||
|
||||
private readonly txFactory = new TxOperations(this.client, getCurrentAccount().primarySocialId).txFactory
|
||||
|
||||
async tx (tx: Tx): Promise<TxResult> {
|
||||
await this.handleTx(tx)
|
||||
const preTx: Array<TxCUD<Doc>> = []
|
||||
const postTx: Array<TxCUD<Doc>> = []
|
||||
await this.handleTx(preTx, postTx, tx)
|
||||
|
||||
if (preTx.length > 0 || postTx.length > 0) {
|
||||
if (TxProcessor.isExtendsCUD(tx._class)) {
|
||||
const applyIf = this.txFactory.createTxApplyIf(
|
||||
core.space.Tx,
|
||||
generateId(),
|
||||
[],
|
||||
[],
|
||||
[...preTx, tx as TxCUD<Doc>, ...postTx],
|
||||
'process',
|
||||
true
|
||||
)
|
||||
return await this.provideTx(applyIf)
|
||||
}
|
||||
}
|
||||
|
||||
return await this.provideTx(tx)
|
||||
}
|
||||
|
||||
private async handleTx (...txes: Tx[]): Promise<void> {
|
||||
private async handleTx (preTx: Array<TxCUD<Doc>>, postTx: Array<TxCUD<Doc>>, ...txes: Tx[]): Promise<void> {
|
||||
for (const etx of txes) {
|
||||
if (etx._class === core.class.TxApplyIf) {
|
||||
const applyIf = etx as TxApplyIf
|
||||
await this.handleTx(...applyIf.txes)
|
||||
await this.handleTx(preTx, postTx, ...applyIf.txes)
|
||||
}
|
||||
|
||||
await this.handleCardCreate(etx)
|
||||
await this.handleCardUpdate(etx)
|
||||
await this.handleTagAdd(etx)
|
||||
await this.handleToDoDone(etx)
|
||||
await this.handleApproveRequest(etx)
|
||||
await this.handleCardCreate(postTx, etx)
|
||||
await this.handleCardUpdate(preTx, etx)
|
||||
await this.handleTagAdd(postTx, etx)
|
||||
await this.handleToDoDone(preTx, etx)
|
||||
await this.handleApproveRequest(preTx, etx)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCardUpdate (etx: Tx): Promise<void> {
|
||||
private async handleCardUpdate (preTx: Array<TxCUD<Doc>>, etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc || etx._class === core.class.TxMixin) {
|
||||
const updateTx = etx as TxUpdateDoc<Card> | TxMixin<Card, Card>
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
@@ -97,21 +120,26 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
},
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
const inputContext = {
|
||||
...execution.context,
|
||||
card: updated,
|
||||
operations: isUpdateTx(updateTx) ? updateTx.operations : updateTx.attributes
|
||||
})
|
||||
}
|
||||
const transition = await pickTransition(this.client, execution, transitions, inputContext)
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
const result = await getNextStateUserInput(execution, transition, execution.context, inputContext)
|
||||
if (result?.changed === true) {
|
||||
preTx.push(
|
||||
this.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
|
||||
context: result.context
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleCardCreate (etx: Tx): Promise<void> {
|
||||
private async handleCardCreate (postTx: Array<TxCUD<Doc>>, etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxCreateDoc) {
|
||||
const createTx = etx as TxCreateDoc<Card>
|
||||
const doc = TxProcessor.createDoc2Doc(createTx)
|
||||
@@ -130,12 +158,13 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
autoStart: true
|
||||
})
|
||||
for (const proc of processes) {
|
||||
await createExecution(createTx.objectId, proc._id, createTx.objectSpace)
|
||||
const res = await createExecution(createTx.objectId, proc._id, createTx.objectSpace, this.txFactory)
|
||||
if (res !== undefined) postTx.push(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleTagAdd (tx: Tx): Promise<void> {
|
||||
private async handleTagAdd (postTx: Array<TxCUD<Doc>>, tx: Tx): Promise<void> {
|
||||
if (tx._class !== core.class.TxMixin) return
|
||||
const mixinTx = tx as TxMixin<Card, Card>
|
||||
const hierarchy = this.client.getHierarchy()
|
||||
@@ -146,11 +175,12 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
.getModel()
|
||||
.findAllSync(process.class.Process, { masterTag: mixinTx.mixin, autoStart: true })
|
||||
for (const proc of processes) {
|
||||
await createExecution(mixinTx.objectId, proc._id, mixinTx.objectSpace)
|
||||
const res = await createExecution(mixinTx.objectId, proc._id, mixinTx.objectSpace, this.txFactory)
|
||||
if (res !== undefined) postTx.push(res)
|
||||
}
|
||||
}
|
||||
|
||||
private async handleApproveRequest (etx: Tx): Promise<void> {
|
||||
private async handleApproveRequest (preTx: Array<TxCUD<Doc>>, etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
const cud = etx as TxUpdateDoc<ApproveRequest>
|
||||
if (cud.objectClass !== process.class.ApproveRequest) return
|
||||
@@ -163,7 +193,6 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
_id: approveRequest.execution
|
||||
})
|
||||
if (execution === undefined) return
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
const transitions = this.client.getModel().findAllSync(
|
||||
process.class.Transition,
|
||||
{
|
||||
@@ -176,18 +205,24 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
{ sort: { rank: SortingOrder.Ascending } }
|
||||
)
|
||||
const updatedApproveRequest = TxProcessor.updateDoc2Doc(approveRequest, cud)
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
const inputContext = {
|
||||
...execution.context,
|
||||
todo: updatedApproveRequest
|
||||
})
|
||||
}
|
||||
const transition = await pickTransition(this.client, execution, transitions, inputContext)
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
const result = await getNextStateUserInput(execution, transition, execution.context, inputContext)
|
||||
if (result?.changed === true) {
|
||||
preTx.push(
|
||||
this.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
|
||||
context: result.context
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async handleToDoDone (etx: Tx): Promise<void> {
|
||||
private async handleToDoDone (preTx: Array<TxCUD<Doc>>, etx: Tx): Promise<void> {
|
||||
if (etx._class === core.class.TxUpdateDoc) {
|
||||
const cud = etx as TxUpdateDoc<ProcessToDo>
|
||||
if (cud.objectClass !== process.class.ProcessToDo) return
|
||||
@@ -200,22 +235,42 @@ export class ProcessMiddleware extends BasePresentationMiddleware implements Pre
|
||||
_id: todo.execution
|
||||
})
|
||||
if (execution === undefined) return
|
||||
const txop = new TxOperations(this.client, getCurrentAccount().primarySocialId)
|
||||
await requestResult(txop, execution, todo.results, execution.context)
|
||||
|
||||
const context = await requestResult(execution, todo.results, execution.context)
|
||||
|
||||
const transitions = this.client.getModel().findAllSync(process.class.Transition, {
|
||||
process: execution.process,
|
||||
from: execution.currentState,
|
||||
trigger: process.trigger.OnToDoClose
|
||||
})
|
||||
const transition = await pickTransition(this.client, execution, transitions, {
|
||||
const inputContext = {
|
||||
...(context ?? execution.context),
|
||||
todo
|
||||
})
|
||||
if (transition === undefined) return
|
||||
const context = await getNextStateUserInput(execution, transition, execution.context)
|
||||
if (context !== undefined) {
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
}
|
||||
const transition = await pickTransition(this.client, execution, transitions, inputContext)
|
||||
if (transition === undefined) {
|
||||
if (context !== undefined) {
|
||||
preTx.push(
|
||||
this.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
|
||||
context
|
||||
})
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
const finalResult = await getNextStateUserInput(execution, transition, context ?? execution.context, inputContext)
|
||||
if (finalResult?.changed === true) {
|
||||
preTx.push(
|
||||
this.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
|
||||
context: finalResult.context
|
||||
})
|
||||
)
|
||||
} else if (context !== undefined) {
|
||||
preTx.push(
|
||||
this.txFactory.createTxUpdateDoc(execution._class, execution.space, execution._id, {
|
||||
context
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ import core, {
|
||||
type Ref,
|
||||
type RefTo,
|
||||
type Space,
|
||||
type TxOperations,
|
||||
type TxCUD,
|
||||
type TxFactory,
|
||||
TxProcessor,
|
||||
type Type
|
||||
} from '@hcengineering/core'
|
||||
@@ -422,13 +423,13 @@ export async function continueExecution (value: Execution): Promise<void> {
|
||||
let context = value.context
|
||||
const transition = value.error[0].transition
|
||||
if (transition == null) {
|
||||
const res = await newExecutionUserInput(value.process, value.space, context)
|
||||
context = res ?? context
|
||||
const res = await newExecutionUserInput(value.process, value.space, value)
|
||||
context = res?.context ?? context
|
||||
} else {
|
||||
const _transition = client.getModel().findObject(transition)
|
||||
if (_transition === undefined) return
|
||||
const res = await getNextStateUserInput(value, _transition, context)
|
||||
context = res ?? context
|
||||
context = res?.context ?? context
|
||||
}
|
||||
await client.update(value, { status: ExecutionStatus.Active, context })
|
||||
}
|
||||
@@ -437,17 +438,57 @@ export async function requestUserInput (
|
||||
processId: Ref<Process>,
|
||||
space: Ref<Space>,
|
||||
target: Transition,
|
||||
userContext: ExecutionContext
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
execution: Execution,
|
||||
userContext: ExecutionContext,
|
||||
inputContext: Record<string, any> = {}
|
||||
): Promise<{ context: ExecutionContext, state: Ref<State>, changed: boolean }> {
|
||||
const client = getClient()
|
||||
let changed = false
|
||||
const tr = await getTransitionUserInput(processId, space, target, userContext)
|
||||
if (tr !== undefined) {
|
||||
userContext = { ...userContext, ...tr }
|
||||
changed = true
|
||||
}
|
||||
const sub = await getSubProcessesUserInput(space, target, userContext)
|
||||
if (sub !== undefined) {
|
||||
userContext = { ...userContext, ...sub }
|
||||
changed = true
|
||||
}
|
||||
return sub !== undefined || tr !== undefined ? userContext : undefined
|
||||
|
||||
// Follow auto transitions
|
||||
const nextAutoTransitions = client
|
||||
.getModel()
|
||||
.findAllSync(process.class.Transition, {
|
||||
process: processId,
|
||||
from: target.to
|
||||
})
|
||||
.filter((t) => client.getModel().findObject(t.trigger)?.auto === true)
|
||||
|
||||
if (nextAutoTransitions.length > 0) {
|
||||
const newExecution = {
|
||||
...execution,
|
||||
context: userContext,
|
||||
currentState: target.to
|
||||
}
|
||||
const nextTransition = await pickTransition(client, newExecution, nextAutoTransitions, inputContext)
|
||||
if (nextTransition !== undefined) {
|
||||
const recursive = await requestUserInput(
|
||||
processId,
|
||||
space,
|
||||
nextTransition,
|
||||
newExecution,
|
||||
userContext,
|
||||
inputContext
|
||||
)
|
||||
return {
|
||||
context: recursive.context,
|
||||
state: recursive.state,
|
||||
changed: changed || recursive.changed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { context: userContext, state: target.to, changed }
|
||||
}
|
||||
|
||||
export async function getTransitionUserInput (
|
||||
@@ -523,47 +564,76 @@ export async function getSubProcessesUserInput (
|
||||
export async function newExecutionUserInput (
|
||||
_id: Ref<Process>,
|
||||
space: Ref<Space>,
|
||||
userContext?: ExecutionContext
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
execution: Execution
|
||||
): Promise<{ context: ExecutionContext, state: Ref<State>, changed: boolean } | undefined> {
|
||||
const client = getClient()
|
||||
const initTransition = client.getModel().findAllSync(process.class.Transition, {
|
||||
process: _id,
|
||||
from: null
|
||||
})[0]
|
||||
if (initTransition === undefined) return userContext
|
||||
return await requestUserInput(_id, space, initTransition, userContext ?? getEmptyContext())
|
||||
if (initTransition === undefined) return undefined
|
||||
return await requestUserInput(_id, space, initTransition, execution, execution.context)
|
||||
}
|
||||
|
||||
export async function getNextStateUserInput (
|
||||
execution: Execution,
|
||||
transition: Transition,
|
||||
userContext: ExecutionContext
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
userContext: ExecutionContext,
|
||||
inputContext: Record<string, any> = {}
|
||||
): Promise<{ context: ExecutionContext, state: Ref<State>, changed: boolean } | undefined> {
|
||||
const client = getClient()
|
||||
const process = client.getModel().findObject(execution.process)
|
||||
if (process === undefined) return userContext
|
||||
return await requestUserInput(execution.process, execution.space, transition, userContext)
|
||||
const _process = client.getModel().findObject(execution.process)
|
||||
if (_process === undefined) return undefined
|
||||
return await requestUserInput(execution.process, execution.space, transition, execution, userContext, inputContext)
|
||||
}
|
||||
|
||||
export async function createExecution (card: Ref<Card>, _id: Ref<Process>, space: Ref<Space>): Promise<void> {
|
||||
export async function createExecution (
|
||||
card: Ref<Card>,
|
||||
_id: Ref<Process>,
|
||||
space: Ref<Space>,
|
||||
txFactory: TxFactory
|
||||
): Promise<TxCUD<Doc> | undefined> {
|
||||
const client = getClient()
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
const context = await newExecutionUserInput(_id, space)
|
||||
|
||||
const _process = client.getModel().findObject(_id)
|
||||
if (_process === undefined) return
|
||||
|
||||
const initTransition = client.getModel().findAllSync(process.class.Transition, {
|
||||
process: _id,
|
||||
from: null
|
||||
})[0]
|
||||
if (initTransition === undefined) return
|
||||
await client.createDoc(process.class.Execution, space, {
|
||||
const executionId = generateId<Execution>()
|
||||
|
||||
const mockExecution: Execution = {
|
||||
_id: executionId,
|
||||
process: _id,
|
||||
currentState: initTransition.to,
|
||||
card,
|
||||
rollback: [],
|
||||
context: context ?? getEmptyContext(),
|
||||
status: ExecutionStatus.Active
|
||||
})
|
||||
context: getEmptyContext(),
|
||||
status: ExecutionStatus.Active,
|
||||
space,
|
||||
_class: process.class.Execution,
|
||||
modifiedOn: 0,
|
||||
modifiedBy: client.user
|
||||
}
|
||||
|
||||
const result = await newExecutionUserInput(_id, space, mockExecution)
|
||||
|
||||
return txFactory.createTxCreateDoc(
|
||||
process.class.Execution,
|
||||
space,
|
||||
{
|
||||
process: _id,
|
||||
currentState: initTransition.to,
|
||||
card,
|
||||
rollback: [],
|
||||
context: result?.context ?? getEmptyContext(),
|
||||
status: ExecutionStatus.Active
|
||||
},
|
||||
executionId
|
||||
)
|
||||
}
|
||||
|
||||
export function getToDoEndAction (prevState: State): Step<Doc> {
|
||||
@@ -590,11 +660,10 @@ export function getToDoEndAction (prevState: State): Step<Doc> {
|
||||
}
|
||||
|
||||
export async function requestResult (
|
||||
txop: TxOperations,
|
||||
execution: Execution,
|
||||
results: UserResult[] | undefined,
|
||||
context: ExecutionContext
|
||||
): Promise<void> {
|
||||
): Promise<ExecutionContext | undefined> {
|
||||
if (results == null || results.length === 0) return
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
showPopup(process.component.ResultInput, { results, context }, undefined, (res) => {
|
||||
@@ -610,9 +679,7 @@ export async function requestResult (
|
||||
})
|
||||
})
|
||||
await promise
|
||||
await txop.update(execution, {
|
||||
context
|
||||
})
|
||||
return context
|
||||
}
|
||||
|
||||
export function todoTranstionCheck (
|
||||
|
||||
Reference in New Issue
Block a user