Skip to content

Commit

Permalink
Fix case approval state visualization
Browse files Browse the repository at this point in the history
The semantics of ApprovalSchemaExecutionInformationType was
more clearly defined and the code for its handling radically simplified.
The stage/executionRecord part was deprecated and eliminated.

See also https://wiki.evolveum.com/display/midPoint/How+to+display+approval+case+%28planned+or+real%29+execution.
  • Loading branch information
mederly committed Apr 20, 2020
1 parent c406971 commit 80a957c
Show file tree
Hide file tree
Showing 21 changed files with 2,129 additions and 1,690 deletions.
Expand Up @@ -74,7 +74,7 @@ public static ApprovalProcessExecutionInformationDto createFrom(ApprovalSchemaEx
targetName, triggers, running);
int startingStageNumber = wholeProcess ? 1 : currentStageNumber+1;
boolean reachable = true;
for (int i = startingStageNumber - 1; i < numberOfStages; i++) {
for (int i = startingStageNumber; i <= numberOfStages; i++) {
ApprovalStageExecutionInformationDto stage = ApprovalStageExecutionInformationDto.createFrom(info, i, resolver, session, opTask, result);
stage.setReachable(reachable);
rv.stages.add(stage);
Expand Down
Expand Up @@ -9,15 +9,10 @@

import com.evolveum.midpoint.repo.common.ObjectResolver;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ObjectTypeUtil;
import com.evolveum.midpoint.schema.util.ApprovalContextUtil;
import com.evolveum.midpoint.schema.util.WorkItemId;
import com.evolveum.midpoint.schema.util.*;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.wf.util.ApprovalUtils;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.commons.collections.CollectionUtils;

import java.io.Serializable;
import java.util.ArrayList;
Expand All @@ -35,7 +30,6 @@ public class ApprovalStageExecutionInformationDto implements Serializable {

private static final long serialVersionUID = 1L;

private static final Trace LOGGER = TraceManager.getTrace(ApprovalStageExecutionInformationDto.class);
public static final String F_APPROVER_ENGAGEMENTS = "approverEngagements";

private final int stageNumber;
Expand All @@ -57,23 +51,25 @@ private ApprovalStageExecutionInformationDto(ApprovalStageDefinitionType definit
evaluationStrategy = definition.getEvaluationStrategy();
}

static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionInformationType processInfo, int stageIndex,
static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionInformationType processInfo, int stageNumber,
ObjectResolver resolver, ObjectResolver.Session session, Task opTask, OperationResult result) {
ApprovalStageExecutionInformationType stageInfo = processInfo.getStage().get(stageIndex);
ApprovalStageExecutionInformationType stageInfo = ApprovalSchemaExecutionInformationUtil.getStage(processInfo, stageNumber);
if (stageInfo == null) {
throw new IllegalStateException("No stage execution information in " + processInfo);
}
ApprovalStageExecutionInformationDto rv = new ApprovalStageExecutionInformationDto(stageInfo.getDefinition());
int stageNumber = stageIndex+1;
int currentStageNumber = defaultIfNull(processInfo.getCurrentStageNumber(), 0);
if (stageNumber <= currentStageNumber) {
addInformationFromRecordedStage(rv, processInfo, stageInfo.getExecutionRecord(), currentStageNumber, resolver, session, opTask, result);
addInformationFromPastOrCurrentStage(rv, processInfo, stageNumber, currentStageNumber, resolver, session, opTask, result);
} else {
addInformationFromPreviewedStage(rv, stageInfo.getExecutionPreview(), resolver, session, opTask, result);
addInformationFromFutureStage(rv, stageInfo.getExecutionPreview(), resolver, session, opTask, result);
}
// computing stage outcome that is to be displayed
if (rv.automatedOutcome != null) {
rv.outcome = rv.automatedOutcome;
} else {
if (stageNumber < currentStageNumber) {
rv.outcome = ApprovalLevelOutcomeType.APPROVE; // no stage before current stage could be manually rejected
rv.outcome = ApprovalLevelOutcomeType.APPROVE; // no stage before current stage could be manually rejected
} else if (stageNumber == currentStageNumber) {
rv.outcome = ApprovalUtils.approvalLevelOutcomeFromUri(ApprovalContextUtil.getOutcome(processInfo));
} else {
Expand All @@ -87,7 +83,7 @@ static ApprovalStageExecutionInformationDto createFrom(ApprovalSchemaExecutionIn
return rv;
}

private static void addInformationFromPreviewedStage(ApprovalStageExecutionInformationDto rv,
private static void addInformationFromFutureStage(ApprovalStageExecutionInformationDto rv,
ApprovalStageExecutionPreviewType executionPreview, ObjectResolver resolver,
ObjectResolver.Session session, Task opTask, OperationResult result) {
if (executionPreview.getExpectedAutomatedCompletionReason() != null) {
Expand All @@ -96,7 +92,7 @@ private static void addInformationFromPreviewedStage(ApprovalStageExecutionInfor
} else {
for (ObjectReferenceType approver : executionPreview.getExpectedApproverRef()) {
resolve(approver, resolver, session, opTask, result);
rv.addApproverEngagement(new ApproverEngagementDto(approver, null));
rv.addApproverEngagement(new ApproverEngagementDto(approver));
}
}
rv.errorMessage = executionPreview.getErrorMessage();
Expand All @@ -109,43 +105,25 @@ private static void resolve(ObjectReferenceType ref, ObjectResolver resolver, Ob
}
}

private static void addInformationFromRecordedStage(ApprovalStageExecutionInformationDto rv,
ApprovalSchemaExecutionInformationType processInfo, ApprovalStageExecutionRecordType executionRecord,
int currentStageNumber, ObjectResolver resolver,
ObjectResolver.Session session, Task opTask, OperationResult result) {
for (CaseEventType event : executionRecord.getEvent()) {
if (event instanceof WorkItemEventType) {
WorkItemEventType workItemEvent = (WorkItemEventType) event;
ObjectReferenceType approver;
if (event instanceof WorkItemDelegationEventType){
List<ObjectReferenceType> delegateToList = ((WorkItemDelegationEventType)event).getDelegatedTo();
approver = CollectionUtils.isNotEmpty(delegateToList) ? delegateToList.get(0) : null;
} else {
approver = workItemEvent.getOriginalAssigneeRef();
}
if (approver == null) {
LOGGER.warn("No original assignee in work item event {} -- ignoring it", workItemEvent);
continue;
}
if (workItemEvent.getExternalWorkItemId() == null) {
LOGGER.warn("No external work item ID in work item event {} -- ignoring it", workItemEvent);
continue;
}
WorkItemId externalWorkItemId = WorkItemId.create(workItemEvent.getExternalWorkItemId());
ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId);
if (engagement == null) {
resolve(approver, resolver, session, opTask, result);
engagement = new ApproverEngagementDto(approver, externalWorkItemId);
rv.addApproverEngagement(engagement);
}
if (event instanceof WorkItemCompletionEventType) {
WorkItemCompletionEventType completionEvent = (WorkItemCompletionEventType) event;
engagement.setCompletedAt(completionEvent.getTimestamp());
resolve(completionEvent.getInitiatorRef(), resolver, session, opTask, result);
engagement.setCompletedBy(completionEvent.getInitiatorRef());
engagement.setAttorney(completionEvent.getAttorneyRef());
engagement.setOutput(completionEvent.getOutput());
}
private static void addInformationFromPastOrCurrentStage(ApprovalStageExecutionInformationDto rv,
ApprovalSchemaExecutionInformationType processInfo, int stageNumber, int currentStageNumber,
ObjectResolver resolver, ObjectResolver.Session session, Task opTask, OperationResult result) {
assert stageNumber <= currentStageNumber;
CaseType aCase = ApprovalSchemaExecutionInformationUtil.getEmbeddedCaseBean(processInfo);

for (CaseEventType event : CaseEventUtil.getEventsForStage(aCase, stageNumber)) {
if (event instanceof WorkItemCompletionEventType) {
WorkItemCompletionEventType completionEvent = (WorkItemCompletionEventType) event;
ObjectReferenceType initiatorRef = completionEvent.getInitiatorRef();
ObjectReferenceType attorneyRef = completionEvent.getAttorneyRef();
resolve(initiatorRef, resolver, session, opTask, result);
resolve(attorneyRef, resolver, session, opTask, result);
ApproverEngagementDto engagement = new ApproverEngagementDto(initiatorRef);
engagement.setCompletedAt(completionEvent.getTimestamp());
engagement.setCompletedBy(initiatorRef);
engagement.setAttorney(attorneyRef);
engagement.setOutput(completionEvent.getOutput());
rv.addApproverEngagement(engagement);
} else if (event instanceof StageCompletionEventType) {
StageCompletionEventType completionEvent = (StageCompletionEventType) event;
if (completionEvent.getAutomatedDecisionReason() != null) {
Expand All @@ -154,23 +132,16 @@ private static void addInformationFromRecordedStage(ApprovalStageExecutionInform
}
}
}
// not needed after "create work item" events will be implemented
for (CaseWorkItemType workItem : executionRecord.getWorkItem()) {
if (workItem.getStageNumber() == null || workItem.getStageNumber() != currentStageNumber){
continue;
}
ObjectReferenceType approver = CollectionUtils.isNotEmpty(workItem.getAssigneeRef()) ?
workItem.getAssigneeRef().get(0) : workItem.getOriginalAssigneeRef();
if (approver == null) {
LOGGER.warn("No original assignee in work item {} -- ignoring it", workItem);
continue;
}
WorkItemId externalWorkItemId = WorkItemId.create(processInfo.getCaseRef().getOid(), workItem.getId());
ApproverEngagementDto engagement = rv.findApproverEngagement(approver, externalWorkItemId);
if (engagement == null) {
resolve(approver, resolver, session, opTask, result);
engagement = new ApproverEngagementDto(approver, externalWorkItemId);
rv.addApproverEngagement(engagement);

// Obtaining information about open work items
if (stageNumber == currentStageNumber) {
for (CaseWorkItemType workItem : CaseWorkItemUtil.getWorkItemsForStage(aCase, stageNumber)) {
if (CaseWorkItemUtil.isCaseWorkItemNotClosed(workItem)) {
for (ObjectReferenceType assigneeRef : workItem.getAssigneeRef()) {
resolve(assigneeRef, resolver, session, opTask, result);
rv.addApproverEngagement(new ApproverEngagementDto(assigneeRef));
}
}
}
}
}
Expand All @@ -179,16 +150,6 @@ private void addApproverEngagement(ApproverEngagementDto engagement) {
approverEngagements.add(engagement);
}

private ApproverEngagementDto findApproverEngagement(ObjectReferenceType approver, WorkItemId externalWorkItemId) {
for (ApproverEngagementDto engagement : approverEngagements) {
if (ObjectTypeUtil.matchOnOid(engagement.getApproverRef(), approver)
&& java.util.Objects.equals(engagement.getExternalWorkItemId(), externalWorkItemId)) {
return engagement;
}
}
return null;
}

public int getStageNumber() {
return stageNumber;
}
Expand All @@ -201,10 +162,6 @@ public String getStageDisplayName() {
return stageDisplayName;
}

public LevelEvaluationStrategyType getEvaluationStrategy() {
return evaluationStrategy;
}

public ApprovalLevelOutcomeType getAutomatedOutcome() {
return automatedOutcome;
}
Expand Down Expand Up @@ -238,7 +195,7 @@ public boolean isReachable() {
return reachable;
}

public void setReachable(boolean reachable) {
void setReachable(boolean reachable) {
this.reachable = reachable;
}
}
Expand Up @@ -7,7 +7,6 @@

package com.evolveum.midpoint.web.page.admin.workflow.dto;

import com.evolveum.midpoint.schema.util.WorkItemId;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AbstractWorkItemOutputType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType;
import org.jetbrains.annotations.NotNull;
Expand All @@ -18,36 +17,27 @@

/**
* GUI-friendly information about an engagement of given approver in a historic, current or future execution of an approval stage.
*
* @author mederly
*/
public class ApproverEngagementDto implements Serializable {

private static final long serialVersionUID = 1L;

@NotNull private final ObjectReferenceType approverRef; // with the whole object, if possible
@Nullable private final WorkItemId externalWorkItemId;
@Nullable private AbstractWorkItemOutputType output;
@Nullable private XMLGregorianCalendar completedAt;
@Nullable private ObjectReferenceType completedBy; // the user that really completed the work item originally assigned to that approver
@Nullable private ObjectReferenceType attorney; // the attorney (of completedBy)
private boolean last;

ApproverEngagementDto(@NotNull ObjectReferenceType approverRef, @Nullable WorkItemId externalWorkItemId) {
ApproverEngagementDto(@NotNull ObjectReferenceType approverRef) {
this.approverRef = approverRef;
this.externalWorkItemId = externalWorkItemId;
}

@NotNull
public ObjectReferenceType getApproverRef() {
return approverRef;
}

@Nullable
public WorkItemId getExternalWorkItemId() {
return externalWorkItemId;
}

@Nullable
public AbstractWorkItemOutputType getOutput() {
return output;
Expand Down
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2020 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.schema.util;

import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalSchemaExecutionInformationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ApprovalStageExecutionInformationType;
import com.evolveum.midpoint.xml.ns._public.common.common_3.CaseType;

import org.jetbrains.annotations.NotNull;

import java.util.Objects;

public class ApprovalSchemaExecutionInformationUtil {
public static ApprovalStageExecutionInformationType getStage(ApprovalSchemaExecutionInformationType executionInfo, int number) {
return executionInfo.getStage().stream()
.filter(i -> Objects.equals(number, i.getNumber()))
.findAny()
.orElse(null);
}

@NotNull
public static PrismObject<CaseType> getEmbeddedCase(ApprovalSchemaExecutionInformationType executionInfo) {
if (executionInfo.getCaseRef() == null) {
throw new IllegalStateException("No caseRef in " + executionInfo);
} else if (executionInfo.getCaseRef().getObject() == null) {
throw new IllegalStateException("No caseRef.object in " + executionInfo);
} else {
//noinspection unchecked
return executionInfo.getCaseRef().getObject();
}
}

@NotNull
public static CaseType getEmbeddedCaseBean(ApprovalSchemaExecutionInformationType executionInfo) {
return getEmbeddedCase(executionInfo).asObjectable();
}
}
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 Evolveum and contributors
*
* This work is dual-licensed under the Apache License 2.0
* and European Union Public License. See LICENSE file for details.
*/

package com.evolveum.midpoint.schema.util;

import java.util.List;
import java.util.stream.Collectors;

import com.evolveum.midpoint.xml.ns._public.common.common_3.*;

public class CaseEventUtil {

public static boolean completedByUserAction(WorkItemEventType event) {
WorkItemEventCauseInformationType cause = event.getCause();
return event.getInitiatorRef() != null &&
(cause == null ||
cause.getType() == null ||
cause.getType() == WorkItemEventCauseTypeType.USER_ACTION);
}

public static List<CaseEventType> getEventsForStage(CaseType aCase, int stageNumber) {
return aCase.getEvent().stream()
.filter(e -> java.util.Objects.equals(e.getStageNumber(), stageNumber))
.collect(Collectors.toList());
}
}
Expand Up @@ -16,7 +16,8 @@
import org.jetbrains.annotations.NotNull;

import java.util.List;

import java.util.Objects;
import java.util.stream.Collectors;

/**
* @author bpowers
Expand Down Expand Up @@ -89,5 +90,9 @@ public static boolean doesAssigneeExist(CaseWorkItemType workItem){
return false;
}


public static List<CaseWorkItemType> getWorkItemsForStage(CaseType aCase, int stageNumber) {
return aCase.getWorkItem().stream()
.filter(wi -> Objects.equals(wi.getStageNumber(), stageNumber))
.collect(Collectors.toList());
}
}

0 comments on commit 80a957c

Please sign in to comment.