From 017c743fd0c45c5082bdb21c55c8c78b623b5a0b Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 18:26:55 +0200 Subject: [PATCH 1/5] change archetype improvements: - changed style and position of the message warning user that the changes are applied immediately - when change assignment requested, modify only archetype assignments, don't apply other changes made in form - a little bit of code cleanup. --- .../gui/api/component/AssignmentPopup.html | 3 +- .../gui/api/component/AssignmentPopup.java | 35 ++--- .../api/component/result/MessagePanel.html | 25 +++ .../api/component/result/MessagePanel.java | 111 ++++++++++++++ .../result/OperationResultPanel.java | 143 ++++++------------ .../result/OperationResultPopupPanel.java | 2 +- .../component/message/FeedbackListView.java | 2 +- .../page/admin/PageAdminObjectDetails.java | 86 +++++++---- .../TestConnectionMessagesPanel.java | 2 +- .../page/admin/server/TaskResultTabPanel.java | 3 +- 10 files changed, 253 insertions(+), 159 deletions(-) create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html create mode 100644 gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html index 5b81f636cc7..fe98c96c213 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.html @@ -6,10 +6,9 @@ --> +
- -

diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java index a4d92cbfa38..8670f75c317 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/AssignmentPopup.java @@ -7,26 +7,23 @@ package com.evolveum.midpoint.gui.api.component; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; - import javax.xml.namespace.QName; -import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; -import com.evolveum.midpoint.web.component.util.SelectableBean; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.extensions.markup.html.tabs.ITab; import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.model.IModel; import org.apache.wicket.model.StringResourceModel; +import com.evolveum.midpoint.gui.api.component.result.MessagePanel; import com.evolveum.midpoint.gui.api.component.tabs.CountablePanelTab; +import com.evolveum.midpoint.gui.api.component.tabs.PanelTab; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; @@ -36,14 +33,9 @@ import com.evolveum.midpoint.web.component.TabbedPanel; import com.evolveum.midpoint.web.component.dialog.Popupable; import com.evolveum.midpoint.web.component.util.EnableBehaviour; -import com.evolveum.midpoint.web.component.util.SelectableBeanImpl; +import com.evolveum.midpoint.web.component.util.SelectableBean; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; -import com.evolveum.midpoint.xml.ns._public.common.common_3.AssignmentType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.OrgType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.RoleType; -import com.evolveum.midpoint.xml.ns._public.common.common_3.ServiceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; /** * Created by honchar. @@ -77,10 +69,10 @@ protected void onInitialize(){ tabPanel.setOutputMarkupPlaceholderTag(true); form.add(tabPanel); - Label warningMessage = new Label(ID_WARNING_MESSAGE, this :: getWarningMessageModel); + MessagePanel warningMessage = new MessagePanel(ID_WARNING_MESSAGE, MessagePanel.MessagePanelType.WARN, getWarningMessageModel()); warningMessage.setOutputMarkupId(true); warningMessage.add(new VisibleBehaviour(() -> getWarningMessageModel() != null)); - form.add(warningMessage); + add(warningMessage); AjaxButton cancelButton = new AjaxButton(ID_CANCEL_BUTTON, createStringResource("userBrowserDialog.button.cancelButton")) { @@ -109,18 +101,18 @@ public void onClick(AjaxRequestTarget target) { if (assignmentPanel == null){ return; } - - (((AbstractAssignmentPopupTabPanel) assignmentPanel).getSelectedAssignmentsMap()).forEach((k, v) -> - selectedAssignmentsMap.putIfAbsent((String)k, (AssignmentType) v)); - - + if (AbstractAssignmentPopupTabPanel.class.isAssignableFrom(assignmentPanel.getClass())) { + Map map = (((AbstractAssignmentPopupTabPanel) assignmentPanel).getSelectedAssignmentsMap()); + map.forEach(selectedAssignmentsMap::putIfAbsent); + } }); List assignments = new ArrayList<>(selectedAssignmentsMap.values()); + getPageBase().hideMainPopup(target); addPerformed(target, assignments); } }; addButton.add(AttributeAppender.append("title", getAddButtonTitleModel())); - addButton.add(new EnableBehaviour(() -> isAssignButtonEnabled())); + addButton.add(new EnableBehaviour(this::isAssignButtonEnabled)); addButton.setOutputMarkupId(true); form.add(addButton); } @@ -401,7 +393,7 @@ protected boolean isEntitlementAssignment(){ } private int getTabPanelSelectedCount(WebMarkupContainer panel){ - if (panel != null && panel instanceof AbstractAssignmentPopupTabPanel){ + if (panel instanceof AbstractAssignmentPopupTabPanel){ return ((AbstractAssignmentPopupTabPanel) panel).getSelectedObjectsList().size(); } return 0; @@ -428,7 +420,6 @@ private TabbedPanel getTabbedPanel(){ } protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { - getPageBase().hideMainPopup(target); } private IModel getAddButtonTitleModel(){ diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html new file mode 100644 index 00000000000..7aa9763b109 --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.html @@ -0,0 +1,25 @@ + + + + +

+ + + diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java new file mode 100644 index 00000000000..91a20a4459b --- /dev/null +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/MessagePanel.java @@ -0,0 +1,111 @@ +/* + * 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.gui.api.component.result; + +import java.io.Serializable; + +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.AjaxLink; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.PropertyModel; + +import com.evolveum.midpoint.gui.api.component.BasePanel; + +public class MessagePanel extends BasePanel { + + private static final String ID_MESSAGE = "message"; + + public enum MessagePanelType {INFO, WARN, SUCCESS, ERROR} + + private MessagePanelType type; + + public MessagePanel(String id, MessagePanelType type, IModel model) { + super(id, model); + this.type = type; + + } + + @Override + protected void onInitialize() { + super.onInitialize(); + initLayout(); + } + + public void initLayout() { + + WebMarkupContainer detailsBox = new WebMarkupContainer("detailsBox"); + detailsBox.setOutputMarkupId(true); + detailsBox.add(AttributeModifier.append("class", createHeaderCss())); + add(detailsBox); + + initHeader(detailsBox); + + } + + private IModel createHeaderCss() { + + return (IModel) () -> { + switch (type) { + case INFO: + return " box-info"; + case SUCCESS: + return " box-success"; + case ERROR: + return " box-danger"; + case WARN: // TODO: + default: + return " box-warning"; + } + }; + } + + private void initHeader(WebMarkupContainer box) { + WebMarkupContainer iconType = new WebMarkupContainer("iconType"); + iconType.setOutputMarkupId(true); + iconType.add(new AttributeAppender("class", (IModel) () -> { + + switch (type) { + case INFO: + return " fa-info"; + case SUCCESS: + return " fa-check"; + case ERROR: + return " fa-ban"; + case WARN: + default: + return " fa-warning"; + } + })); + + box.add(iconType); + + Label message = new Label(ID_MESSAGE, getModel()); + box.add(message); + + AjaxLink close = new AjaxLink("close") { + + private static final long serialVersionUID = 1L; + + @Override + public void onClick(AjaxRequestTarget target) { + close(target); + + } + }; + + box.add(close); + } + + public void close(AjaxRequestTarget target){ + this.setVisible(false); + target.add(this); + } +} diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java index 4d8f5595469..6f65b27b04b 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPanel.java @@ -15,18 +15,13 @@ import java.util.List; import java.util.Locale; -import com.evolveum.midpoint.common.LocalizationService; -import com.evolveum.midpoint.gui.api.page.PageBase; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; -import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.feedback.FeedbackMessage; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.DownloadLink; @@ -37,8 +32,10 @@ import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.StringResourceModel; +import com.evolveum.midpoint.common.LocalizationService; import com.evolveum.midpoint.gui.api.component.BasePanel; import com.evolveum.midpoint.gui.api.model.LoadableModel; +import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.schema.constants.ObjectTypes; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; @@ -70,13 +67,17 @@ public class OperationResultPanel extends BasePanel implements Popupab private static final Trace LOGGER = TraceManager.getTrace(OperationResultPanel.class); - public OperationResultPanel(String id, IModel model, Page parentPage) { + public OperationResultPanel(String id, IModel model) { super(id, model); + } - initLayout(parentPage); + @Override + protected void onInitialize() { + super.onInitialize(); + initLayout(); } - public void initLayout(Page parentPage) { + public void initLayout() { WebMarkupContainer detailsBox = new WebMarkupContainer(ID_DETAILS_BOX); detailsBox.setOutputMarkupId(true); @@ -84,7 +85,7 @@ public void initLayout(Page parentPage) { add(detailsBox); initHeader(detailsBox); - initDetails(detailsBox, parentPage); + initDetails(detailsBox); } private void initHeader(WebMarkupContainer box) { @@ -248,17 +249,11 @@ public void onClick(AjaxRequestTarget target) { public File getObject() { String home = getPageBase().getMidpointConfiguration().getMidpointHome(); File f = new File(home, "result"); - DataOutputStream dos = null; - try { - dos = new DataOutputStream(new FileOutputStream(f)); - + try (DataOutputStream dos = new DataOutputStream(new FileOutputStream(f))){ dos.writeBytes(OperationResultPanel.this.getModel().getObject().getXml()); } catch (IOException e) { LOGGER.error("Could not download result: {}", e.getMessage(), e); - } finally { - IOUtils.closeQuietly(dos); } - return f; } @@ -273,43 +268,39 @@ public void close(AjaxRequestTarget target) { } private Label createMessage() { - Label message = new Label(ID_MESSAGE_LABEL, new IModel() { - - @Override - public String getObject() { - OpResult result = OperationResultPanel.this.getModel().getObject(); + Label message = new Label(ID_MESSAGE_LABEL, (IModel) () -> { + OpResult result = OperationResultPanel.this.getModel().getObject(); - PageBase page = getPageBase(); + PageBase page = getPageBase(); - String msg = null; - if (result.getUserFriendlyMessage() != null) { + String msg = null; + if (result.getUserFriendlyMessage() != null) { - //TODO: unify with WebModelServiceUtil.translateMessage() - LocalizationService service = page.getLocalizationService(); - Locale locale = page.getSession().getLocale(); + //TODO: unify with WebModelServiceUtil.translateMessage() + LocalizationService service = page.getLocalizationService(); + Locale locale = page.getSession().getLocale(); - msg = service.translate(result.getUserFriendlyMessage(), locale); - } - - if (StringUtils.isNotBlank(msg)) { - return msg; - } + msg = service.translate(result.getUserFriendlyMessage(), locale); + } - msg = result.getMessage(); - if (StringUtils.isNotBlank(msg)) { - return msg; - } + if (StringUtils.isNotBlank(msg)) { + return msg; + } - String resourceKey = OPERATION_RESOURCE_KEY_PREFIX + result.getOperation(); - return page.getString(resourceKey, null, resourceKey); + msg = result.getMessage(); + if (StringUtils.isNotBlank(msg)) { + return msg; } + + String resourceKey = OPERATION_RESOURCE_KEY_PREFIX + result.getOperation(); + return page.getString(resourceKey, null, resourceKey); }); message.setOutputMarkupId(true); return message; } - private void initDetails(WebMarkupContainer box, Page parentPage) { + private void initDetails(WebMarkupContainer box) { final WebMarkupContainer details = new WebMarkupContainer("details", getModel()); details.setOutputMarkupId(true); @@ -337,7 +328,7 @@ protected String load() { details.add(operationPanel); Label operationLabel = new Label("operationLabel", - parentPage.getString("FeedbackAlertMessageDetails.operation")); + createStringResource("FeedbackAlertMessageDetails.operation")); operationLabel.setOutputMarkupId(true); operationPanel.add(operationLabel); @@ -355,7 +346,7 @@ protected Object load() { operation.setOutputMarkupId(true); operationPanel.add(operation); - Label count = new Label("countLabel", parentPage.getString("FeedbackAlertMessageDetails.count")); + Label count = new Label("countLabel", createStringResource("FeedbackAlertMessageDetails.count")); count.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -385,7 +376,7 @@ public boolean isVisible() { operationPanel.add(message); - Label messageLabel = new Label("messageLabel", parentPage.getString("FeedbackAlertMessageDetails.message")); + Label messageLabel = new Label("messageLabel", createStringResource("FeedbackAlertMessageDetails.message")); messageLabel.setOutputMarkupId(true); messageLabel.add(new VisibleEnableBehaviour() { @@ -399,14 +390,14 @@ public boolean isVisible() { operationPanel.add(messageLabel); - initParams(operationPanel, getModel(), parentPage); - initContexts(operationPanel, getModel(), parentPage); - initError(operationPanel, getModel(), parentPage); + initParams(operationPanel, getModel()); + initContexts(operationPanel, getModel()); + initError(operationPanel, getModel()); } - private void initParams(WebMarkupContainer operationContent, final IModel model, Page parentPage) { + private void initParams(WebMarkupContainer operationContent, final IModel model) { - Label paramsLabel = new Label("paramsLabel", parentPage.getString("FeedbackAlertMessageDetails.params")); + Label paramsLabel = new Label("paramsLabel", createStringResource("FeedbackAlertMessageDetails.params")); paramsLabel.setOutputMarkupId(true); paramsLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -445,7 +436,7 @@ public boolean isVisible() { @Override protected void populateItem(final ListItem item) { - Panel subresult = new OperationResultPanel("subresult", item.getModel(), getPage()); + Panel subresult = new OperationResultPanel("subresult", item.getModel()); subresult.setOutputMarkupId(true); item.add(subresult); } @@ -464,9 +455,9 @@ public boolean isVisible() { } - private void initContexts(WebMarkupContainer operationContent, final IModel model, Page parentPage) { + private void initContexts(WebMarkupContainer operationContent, final IModel model) { - Label contextsLabel = new Label("contextsLabel", parentPage.getString("FeedbackAlertMessageDetails.contexts")); + Label contextsLabel = new Label("contextsLabel", createStringResource("FeedbackAlertMessageDetails.contexts")); contextsLabel.setOutputMarkupId(true); contextsLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -500,8 +491,8 @@ public boolean isVisible() { operationContent.add(contexts); } - private void initError(WebMarkupContainer operationPanel, final IModel model, Page parentPage) { - Label errorLabel = new Label("errorLabel", parentPage.getString("FeedbackAlertMessageDetails.error")); + private void initError(WebMarkupContainer operationPanel, final IModel model) { + Label errorLabel = new Label("errorLabel", createStringResource("FeedbackAlertMessageDetails.error")); errorLabel.add(new VisibleEnableBehaviour() { private static final long serialVersionUID = 1L; @@ -693,52 +684,6 @@ private String getLabelCss(final IModel model) { } } - private String getIconCss(final IModel model) { - OpResult result = model.getObject(); - - if (result == null || result.getStatus() == null) { - return "fa-warning text-warning"; - } - - switch (result.getStatus()) { - case IN_PROGRESS: - case NOT_APPLICABLE: - return "fa-info-circle text-info"; - case SUCCESS: - return "fa-check-circle-o text-success"; - case FATAL_ERROR: - - return "fa-times-circle-o text-danger"; - case UNKNOWN: - case PARTIAL_ERROR: - case HANDLED_ERROR: // TODO: - case WARNING: - default: - return "fa-warning text-warning"; - } - } - - static String createMessageTooltip(final IModel model) { - FeedbackMessage message = model.getObject(); - switch (message.getLevel()) { - case FeedbackMessage.INFO: - return "info"; - case FeedbackMessage.SUCCESS: - return "success"; - case FeedbackMessage.ERROR: - return "partialError"; - case FeedbackMessage.FATAL: - return "fatalError"; - case FeedbackMessage.UNDEFINED: - return "undefined"; - case FeedbackMessage.DEBUG: - return "debug"; - case FeedbackMessage.WARNING: - default: - return "warn"; - } - } - @Override public int getWidth() { return 900; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java index 477f8f10c1a..cbf85fd2fc0 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/gui/api/component/result/OperationResultPopupPanel.java @@ -32,7 +32,7 @@ protected void onInitialize(){ super.onInitialize(); OperationResultPanel operationResultPanel = new OperationResultPanel(ID_OPERATION_RESULTS_PANEL, - Model.of(OpResult.getOpResult(getPageBase(), getModelObject())), getPageBase()); + Model.of(OpResult.getOpResult(getPageBase(), getModelObject()))); operationResultPanel.setOutputMarkupId(true); add(operationResultPanel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java index f43d3438c49..148c1bbcdae 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/component/message/FeedbackListView.java @@ -40,7 +40,7 @@ protected void populateItem(final ListItem item) { if (message.getMessage() instanceof OpResult) { final OpResult opResult = (OpResult) message.getMessage(); - OperationResultPanel panel = new OperationResultPanel("message", Model.of(opResult), getPage()) { + OperationResultPanel panel = new OperationResultPanel("message", Model.of(opResult)) { private static final long serialVersionUID = 1L; diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java index 4c33bf4775b..1e2a14bab76 100755 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/PageAdminObjectDetails.java @@ -7,6 +7,7 @@ package com.evolveum.midpoint.web.page.admin; import java.util.*; +import java.util.stream.Collectors; import javax.xml.namespace.QName; @@ -15,6 +16,7 @@ import com.evolveum.midpoint.gui.api.prism.PrismContainerWrapper; import com.evolveum.midpoint.gui.api.util.WebPrismUtil; import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismContainerValue; import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; @@ -462,39 +464,40 @@ private void changeArchetypeButtonClicked(AjaxRequestTarget target){ private static final long serialVersionUID = 1L; @Override - protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { - super.addPerformed(target, newAssignmentsList); + protected void addPerformed(AjaxRequestTarget target, List newAssignmentsList) { + OperationResult result = new OperationResult(OPERATION_EXECUTE_ARCHETYPE_CHANGES); + if (newAssignmentsList.size() > 1) { + result.recordWarning(getString("PageAdminObjectDetails.change.archetype.more.than.one.selected")); + showResult(result); + target.add(PageAdminObjectDetails.this.getFeedbackPanel()); + return; + } + + AssignmentType oldArchetypAssignment = getOldArchetypeAssignment(result); + if (oldArchetypAssignment == null) { + showResult(result); + target.add(PageAdminObjectDetails.this.getFeedbackPanel()); + return; + } + try { - PrismContainerWrapper assignmentsWrapper = getObjectWrapper().findContainer(FocusType.F_ASSIGNMENT); - ((List) newAssignmentsList).forEach(assignment -> { - PrismContainerValue newAssignment = assignmentsWrapper.getItem().createNewValue(); - assignmentsWrapper.getValues().forEach(assignmentValue -> { - if (assignmentValue.getRealValue().getTargetRef() != null - && assignmentValue.getRealValue().getTargetRef().getType() != null - && QNameUtil.match(assignmentValue.getRealValue().getTargetRef().getType(), ArchetypeType.COMPLEX_TYPE)){ - assignmentValue.setStatus(ValueStatus.DELETED); - } - }); - AssignmentType assignmentType = newAssignment.asContainerable(); - assignmentType.setTargetRef(assignment.getTargetRef()); - WebPrismUtil.createNewValueWrapper(assignmentsWrapper, newAssignment, PageAdminObjectDetails.this, target); - OperationResult result = new OperationResult(OPERATION_EXECUTE_ARCHETYPE_CHANGES); - Task task = createSimpleTask(OPERATION_EXECUTE_ARCHETYPE_CHANGES); - try { - ObjectDelta archetypeDelta = getObjectWrapper().getObjectDelta(); - if (!archetypeDelta.isEmpty()) { - archetypeDelta.revive(getPrismContext()); - getModelService().executeChanges(MiscUtil.createCollection(archetypeDelta), null, task, result); - result.computeStatus(); - } - } catch (Exception e) { - LOGGER.error("Cannot save archetype assignment changes: {}", e.getMessage()); - } - showResult(result); - }); - } catch (SchemaException e) { - LOGGER.error("Cannot find assignment wrapper: {}", e.getMessage()); + ObjectDelta delta = getPrismContext().deltaFor(getCompileTimeClass()) + .item(AssignmentHolderType.F_ASSIGNMENT) + .delete(oldArchetypAssignment.clone()) + .asObjectDelta(getObjectWrapper().getOid()); + delta.addModificationAddContainer(AssignmentHolderType.F_ASSIGNMENT, newAssignmentsList.iterator().next()); + + Task task = createSimpleTask(OPERATION_EXECUTE_ARCHETYPE_CHANGES); + getModelService().executeChanges(MiscUtil.createCollection(delta), null, task, result); + + + } catch (Exception e) { + LOGGER.error("Cannot find assignment wrapper: {}", e.getMessage(), e); + result.recordFatalError(getString("PageAdminObjectDetails.change.archetype.failed", e.getMessage()), e); + } + result.computeStatusIfUnknown(); + showResult(result); target.add(PageAdminObjectDetails.this.getFeedbackPanel()); refresh(target); } @@ -581,6 +584,27 @@ protected IModel getWarningMessageModel(){ } + private AssignmentType getOldArchetypeAssignment(OperationResult result) { + PrismContainer assignmentContainer = getObjectWrapper().getObjectOld().findContainer(AssignmentHolderType.F_ASSIGNMENT); + if (assignmentContainer == null) { + //should not happen either + result.recordWarning(getString("PageAdminObjectDetails.archetype.change.not.supported")); + return null; + } + + List oldAssignments = assignmentContainer.getRealValues().stream().filter(a -> WebComponentUtil.isArchetypeAssignment(a)).collect(Collectors.toList()); + if (CollectionUtils.isEmpty(oldAssignments)) { + result.recordWarning(getString("PageAdminObjectDetails.archetype.change.not.supported")); + return null; + } + + if (oldAssignments.size() > 1) { + result.recordFatalError(getString("PageAdminObjectDetails.archetype.change.no.single.archetype")); + return null; + } + return oldAssignments.iterator().next(); + } + private List getArchetypeOidsListToAssign(){ List archetypeOidsList = getFilteredArchetypeOidsList(); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java index 9ba5ba5a628..7ac1e05d872 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/resources/component/TestConnectionMessagesPanel.java @@ -145,7 +145,7 @@ protected void populateItem(ListItem item) { public void initResultsPanel(RepeatingView resultView, List opresults, Page parentPage) { for (OpResult result : opresults) { - OperationResultPanel resultPanel = new OperationResultPanel(resultView.newChildId(), new Model<>(result), parentPage); + OperationResultPanel resultPanel = new OperationResultPanel(resultView.newChildId(), new Model<>(result)); resultPanel.setOutputMarkupId(true); resultView.add(resultPanel); } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java index 2400531f627..866e1006c70 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskResultTabPanel.java @@ -84,8 +84,7 @@ public void onClick(Optional optionalTarget) { OperationResult opResult = OperationResult.createOperationResult(taskType.getResult()); OperationResultPanel body = new OperationResultPanel( getPageBase().getMainPopupBodyId(), - new Model<>(OpResult.getOpResult(getPageBase(), opResult)), - getPageBase()); + new Model<>(OpResult.getOpResult(getPageBase(), opResult))); body.setOutputMarkupId(true); getPageBase().showMainPopup(body, target); } From 43e85a9b9ddd907ac799d8e9247cef06d2fae48f Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 18:57:48 +0200 Subject: [PATCH 2/5] avoid phantom changes while deleting task statistics and result --- .../web/page/admin/server/PageTask.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java index e1c46eb4dc9..1608c373593 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTask.java @@ -1,10 +1,7 @@ package com.evolveum.midpoint.web.page.admin.server; import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; - -import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; +import java.util.*; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -29,7 +26,9 @@ import com.evolveum.midpoint.model.api.authentication.GuiProfiledPrincipal; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismProperty; +import com.evolveum.midpoint.prism.PrismValue; import com.evolveum.midpoint.prism.Referencable; +import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.path.ItemName; import com.evolveum.midpoint.prism.path.ItemPath; @@ -54,8 +53,8 @@ import com.evolveum.midpoint.web.component.AjaxDownloadBehaviorFromStream; import com.evolveum.midpoint.web.component.AjaxIconButton; import com.evolveum.midpoint.web.component.ObjectSummaryPanel; +import com.evolveum.midpoint.web.component.dialog.ConfirmationPanel; import com.evolveum.midpoint.web.component.objectdetails.AbstractObjectMainPanel; -import com.evolveum.midpoint.web.component.prism.ValueStatus; import com.evolveum.midpoint.web.component.refresh.Refreshable; import com.evolveum.midpoint.web.component.util.VisibleBehaviour; import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour; @@ -66,8 +65,6 @@ import com.evolveum.midpoint.web.util.TaskOperationUtils; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import static com.evolveum.midpoint.web.component.data.column.ColumnUtils.createStringResource; - @PageDescriptor( urls = { @Url(mountUrl = "/admin/task", matchUrlForSecurity = "/admin/task") @@ -365,11 +362,11 @@ public StringResourceModel getTitle() { @Override public void yesPerformed(AjaxRequestTarget target) { try { - deleteItem(TaskType.F_OPERATION_STATS); - } catch (SchemaException e){ - LOGGER.error("Cannot clear task results: {}", e.getMessage()); + deleteItem(target, TaskType.F_OPERATION_STATS); + } catch (Exception e) { + LOGGER.error("Cannot delete task operation statistics, {}", e.getMessage(), e); + getSession().error(PageTask.this.getString("PageTask.cleanup.operationStatistics.failed")); } - saveTaskChanges(target); } }; showMainPopup(dialog, target); @@ -380,6 +377,26 @@ public void yesPerformed(AjaxRequestTarget target) { repeatingView.add(cleanupPerformance); } + private void deleteItem(AjaxRequestTarget target, ItemName... itemName) throws SchemaException { + List items = Arrays.asList(itemName); + + Collection> itemDeltas = new ArrayList<>(); + for (ItemName item : items) { + ItemDelta delta = createDeleteItemDelta(item); + if (delta == null) { + LOGGER.trace("Nothing to delete for {}", item); + continue; + } + itemDeltas.add(delta); + } + + ObjectDelta taskDelta = getPrismContext().deltaFor(TaskType.class) + .asObjectDelta(getTask().getOid()); + taskDelta.addModifications(itemDeltas); + + saveTaskChanges(target, taskDelta); + } + private void createCleanupResultsButton(RepeatingView repeatingView) { AjaxCompositedIconButton cleanupResults = new AjaxCompositedIconButton(repeatingView.newChildId(), getTaskCleanupCompositedIcon(GuiStyleConstants.CLASS_ICON_TASK_RESULTS), createStringResource("operationalButtonsPanel.cleanupResults")) { @@ -397,12 +414,11 @@ public StringResourceModel getTitle() { @Override public void yesPerformed(AjaxRequestTarget target) { try { - deleteItem(TaskType.F_RESULT); - deleteItem(TaskType.F_RESULT_STATUS); - } catch (SchemaException e){ + deleteItem(target, TaskType.F_RESULT, TaskType.F_RESULT_STATUS); + } catch (Exception e){ LOGGER.error("Cannot clear task results: {}", e.getMessage()); + getSession().error(PageTask.this.getString("PageTask.cleanup.result.failed")); } - saveTaskChanges(target); } }; showMainPopup(dialog, target); @@ -413,32 +429,26 @@ public void yesPerformed(AjaxRequestTarget target) { repeatingView.add(cleanupResults); } - private void deleteItem(ItemName itemName) throws SchemaException { + private ItemDelta createDeleteItemDelta(ItemName itemName) throws SchemaException { ItemWrapper item = getObjectWrapper().findItem(itemName, ItemWrapper.class); if (item == null) { - return; + return null; } PrismValueWrapper itemValue = item.getValue(); if (itemValue == null) { - return; + return null; } - itemValue.setStatus(ValueStatus.DELETED); + PrismValue oldValue = itemValue.getOldValue().clone(); - } + return getPrismContext().deltaFor(TaskType.class) + .item(itemName) + .delete(oldValue) + .asItemDelta(); - private void saveTaskChanges(AjaxRequestTarget target) { - try { - ObjectDelta taskDelta = getObjectWrapper().getObjectDelta(); - saveTaskChanges(target, taskDelta); - } catch (SchemaException e) { - LoggingUtils.logUnexpectedException(LOGGER, "Cannot get task delta.", e); - getSession().error("Cannot save changes, there were problems with computing changes: " + e.getMessage()); - target.add(getFeedbackPanel()); - } } - + private void saveTaskChanges(AjaxRequestTarget target, ObjectDelta taskDelta){ if (taskDelta.isEmpty()) { getSession().warn("Nothing to save, no changes were made."); From e0a27c21d05895ffb6e0096560710466c2dc1881 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Fri, 17 Apr 2020 19:51:47 +0200 Subject: [PATCH 3/5] fixing MID-6209 + cleanup for task list page --- .../web/page/admin/server/PageTasks.java | 48 +++--- .../web/page/admin/server/TaskTablePanel.java | 145 ++++++++++-------- 2 files changed, 104 insertions(+), 89 deletions(-) diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java index 3923803431b..c2e1f0ab21f 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/PageTasks.java @@ -7,9 +7,26 @@ package com.evolveum.midpoint.web.page.admin.server; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import javax.xml.datatype.XMLGregorianCalendar; +import javax.xml.namespace.QName; + +import org.apache.commons.lang.time.DurationFormatUtils; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.request.mapper.parameter.PageParameters; + import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; -import com.evolveum.midpoint.prism.query.ObjectFilter; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.xml.XmlTypeConverter; import com.evolveum.midpoint.schema.GetOperationOptions; @@ -27,25 +44,10 @@ import com.evolveum.midpoint.web.page.admin.PageAdmin; import com.evolveum.midpoint.web.page.admin.server.dto.TaskDtoExecutionStatus; import com.evolveum.midpoint.web.session.UserProfileStorage; -import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.commons.lang.time.DurationFormatUtils; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.apache.wicket.util.string.StringValue; - -import javax.xml.datatype.XMLGregorianCalendar; -import javax.xml.namespace.QName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.List; +import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectReferenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskBindingType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskRecurrenceType; +import com.evolveum.midpoint.xml.ns._public.common.common_3.TaskType; @PageDescriptor( urls = { @@ -110,7 +112,7 @@ protected List, String>> createColumns() { add(tablePanel); } - private Collection, String>> addCustomColumns(List, String>> columns) { + private void addCustomColumns(List, String>> columns) { columns.add(2, new ObjectReferenceColumn>(createStringResource("pageTasks.task.objectRef"), SelectableBeanImpl.F_VALUE+"."+TaskType.F_OBJECT_REF.getLocalPart()){ private static final long serialVersionUID = 1L; @Override @@ -178,7 +180,6 @@ public IModel getDataModel(IModel> rowModel) { return Model.of(createScheduledToRunAgain(rowModel)); } }); - return columns; } private Collection> createOperationOptions() { @@ -190,10 +191,9 @@ private Collection> createOperationOptions( GetOperationOptionsBuilder getOperationOptionsBuilder = getSchemaHelper().getOperationOptionsBuilder(); getOperationOptionsBuilder = getOperationOptionsBuilder.resolveNames(); - Collection> searchOptions = getOperationOptionsBuilder + return getOperationOptionsBuilder .items(propertiesToGet.toArray(new Object[0])).retrieve() .build(); - return searchOptions; } private Date getCurrentRuntime(IModel> taskModel) { diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java index dc24c6bc99c..02e86ac3635 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/server/TaskTablePanel.java @@ -7,9 +7,29 @@ package com.evolveum.midpoint.web.page.admin.server; +import java.util.*; +import java.util.stream.Collectors; +import javax.xml.datatype.XMLGregorianCalendar; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.behavior.AttributeAppender; +import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; +import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.jetbrains.annotations.NotNull; + import com.evolveum.midpoint.gui.api.GuiStyleConstants; import com.evolveum.midpoint.gui.api.component.MainObjectListPanel; -import com.evolveum.midpoint.gui.api.model.ReadOnlyModel; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.model.api.ModelPublicConstants; import com.evolveum.midpoint.model.api.TaskService; @@ -31,7 +51,10 @@ import com.evolveum.midpoint.web.application.PageDescriptor; import com.evolveum.midpoint.web.application.Url; import com.evolveum.midpoint.web.component.AjaxIconButton; -import com.evolveum.midpoint.web.component.data.column.*; +import com.evolveum.midpoint.web.component.data.column.ColumnMenuAction; +import com.evolveum.midpoint.web.component.data.column.EnumPropertyColumn; +import com.evolveum.midpoint.web.component.data.column.IconColumn; +import com.evolveum.midpoint.web.component.data.column.LinkPanel; import com.evolveum.midpoint.web.component.menu.cog.ButtonInlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItem; import com.evolveum.midpoint.web.component.menu.cog.InlineMenuItemAction; @@ -42,27 +65,6 @@ import com.evolveum.midpoint.web.util.OnePageParameterEncoder; import com.evolveum.midpoint.xml.ns._public.common.common_3.*; -import org.apache.wicket.Component; -import org.apache.wicket.ajax.AjaxRequestTarget; -import org.apache.wicket.behavior.AttributeAppender; -import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator; -import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn; -import org.apache.wicket.extensions.markup.html.repeater.data.table.export.AbstractExportableColumn; -import org.apache.wicket.markup.html.WebMarkupContainer; -import org.apache.wicket.markup.html.WebPage; -import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.repeater.Item; -import org.apache.wicket.model.IModel; -import org.apache.wicket.model.Model; -import org.apache.wicket.model.PropertyModel; -import org.apache.wicket.request.mapper.parameter.PageParameters; -import org.jetbrains.annotations.NotNull; - -import javax.xml.datatype.XMLGregorianCalendar; -import java.util.*; -import java.util.stream.Collectors; - @PageDescriptor( urls = { @Url(mountUrl = "/admin/tasks2", matchUrlForSecurity = "/admin/tasks2") @@ -100,7 +102,7 @@ public TaskTablePanel(String id, UserProfileStorage.TableId tableId, Collection< @Override protected void objectDetailsPerformed(AjaxRequestTarget target, TaskType object) { - taskDetailsPerformed(target, object.getOid()); + taskDetailsPerformed(object.getOid()); } @Override @@ -159,10 +161,6 @@ private WebMarkupContainer getFeedbackPanel() { return getPageBase().getFeedbackPanel(); } - private void navigateToNext(Class page, PageParameters parameters) { - getPageBase().navigateToNext(page, parameters); - } - private void synchronizeTasksPerformed(AjaxRequestTarget target) { Task opTask = createSimpleTask(OPERATION_SYNCHRONIZE_TASKS); OperationResult result = opTask.getResult(); @@ -186,10 +184,10 @@ private void synchronizeTasksPerformed(AjaxRequestTarget target) { target.add(getTable()); } - private void taskDetailsPerformed(AjaxRequestTarget target, String oid) { + private void taskDetailsPerformed(String oid) { PageParameters parameters = new PageParameters(); parameters.add(OnePageParameterEncoder.PARAMETER, oid); - navigateToNext(PageTask.class, parameters); + getPageBase().navigateToNext(PageTask.class, parameters); } private List, String>> initTaskColumns() { @@ -227,30 +225,28 @@ protected List, String>> initCustomTaskColumns( columns.add(createTaskExecutionStatusColumn()); - columns.add(createProgressColumn("pageTasks.task.progress")); - columns.add(createErrorsColumn("pageTasks.task.errors")); + columns.add(createProgressColumn()); + columns.add(createErrorsColumn()); columns.add(new IconColumn>(createStringResource("pageTasks.task.status"), TaskType.F_RESULT_STATUS.getLocalPart()) { @Override protected DisplayType getIconDisplayType(final IModel> rowModel) { - String icon = ""; - if (rowModel != null && rowModel.getObject() != null && rowModel.getObject().getValue().getResultStatus() != null) { + String icon; + String title; + + TaskType task = getTask(rowModel, false); + + if (task != null && task.getResultStatus() != null) { icon = OperationResultStatusPresentationProperties - .parseOperationalResultStatus(rowModel.getObject().getValue().getResultStatus()).getIcon() + .parseOperationalResultStatus(task.getResultStatus()).getIcon() + " fa-lg"; + title = createStringResource(task.getResultStatus()).getString(); } else { icon = OperationResultStatusPresentationProperties.UNKNOWN.getIcon() + " fa-lg"; - } - - String title = ""; - TaskType dto = rowModel.getObject().getValue(); - - if (dto != null && dto.getResultStatus() != null) { - title = createStringResource(dto.getResultStatus()).getString(); - } else { title = createStringResource(OperationResultStatusType.UNKNOWN).getString(); } + return WebComponentUtil.createDisplayType(icon, "", title); } }); @@ -269,8 +265,8 @@ protected String translate(Enum en) { }; } - private AbstractExportableColumn, String> createProgressColumn(String titleKey) { - return new AbstractExportableColumn, String>(createStringResource(titleKey)) { + private AbstractExportableColumn, String> createProgressColumn() { + return new AbstractExportableColumn, String>(createStringResource("pageTasks.task.progress")) { @Override public void populateItem(Item>> cellItem, String componentId, final IModel> rowModel) { @@ -284,7 +280,7 @@ public void populateItem(Item>> cellItem public void onClick(AjaxRequestTarget target) { PageParameters pageParams = new PageParameters(); pageParams.add(OnePageParameterEncoder.PARAMETER, rowModel.getObject().getValue().getOid()); - navigateToNext(PageTask.class, pageParams); + getPageBase().navigateToNext(PageTask.class, pageParams); } }); } @@ -298,8 +294,8 @@ public IModel getDataModel(IModel> rowModel) { }; } - private AbstractColumn, String> createErrorsColumn(String titleKey) { - return new AbstractColumn, String>(createStringResource(titleKey)) { + private AbstractColumn, String> createErrorsColumn() { + return new AbstractColumn, String>(createStringResource("pageTasks.task.errors")) { @Override public void populateItem(Item>> cellItem, String componentId, IModel> rowModel) { TaskType task = rowModel.getObject().getValue(); @@ -701,7 +697,7 @@ private void suspendTasksPerformed(AjaxRequestTarget target, IModel plainTasks = selectedTasks.stream().filter(dto -> !isManageableTreeRoot(dto)).collect(Collectors.toList()); - List trees = selectedTasks.stream().filter(dto -> isManageableTreeRoot(dto)).collect(Collectors.toList()); + List trees = selectedTasks.stream().filter(TaskTablePanel::isManageableTreeRoot).collect(Collectors.toList()); boolean suspendedPlain = suspendPlainTasks(plainTasks, result, opTask); boolean suspendedTrees = suspendTrees(trees, result, opTask); result.computeStatus(); @@ -730,7 +726,7 @@ private void resumeTasksPerformed(AjaxRequestTarget target, IModel plainTasks = selectedTasks.stream().filter(dto -> !isManageableTreeRoot(dto)).collect(Collectors.toList()); - List trees = selectedTasks.stream().filter(dto -> isManageableTreeRoot(dto)).collect(Collectors.toList()); + List trees = selectedTasks.stream().filter(TaskTablePanel::isManageableTreeRoot).collect(Collectors.toList()); getTaskService().resumeTasks(getOids(plainTasks), opTask, result); for (TaskType tree : trees) { getTaskService().resumeTaskTree(tree.getOid(), opTask, result); @@ -780,7 +776,6 @@ private List getSelectedTasks(AjaxRequestTarget target, IModel getTaskConfirmationMessageModel(ColumnMenuAction action, String actionName) { - if (action.getRowModel() == null) { - return createStringResource("pageTasks.message.confirmationMessageForMultipleTaskObject", actionName, getSelectedObjects().size()); -// WebComponentUtil.getSelectedData(()).size()); - } else { - String objectName = ((SelectableBean) (action.getRowModel().getObject())).getValue().getName().getOrig(); + private IModel getTaskConfirmationMessageModel(ColumnMenuAction> action, String actionName) { + if (action.getRowModel() != null) { + String objectName = WebComponentUtil.getName(getTask(action.getRowModel(), false)); return createStringResource("pageTasks.message.confirmationMessageForSingleTaskObject", actionName, objectName); } + if (CollectionUtils.isEmpty(getSelectedObjects())) { + getSession().warn(getString("pageTasks.message.confirmationMessageForNoTaskObject", actionName)); + return null; //confirmation popup should not be shown + } + + return createStringResource("pageTasks.message.confirmationMessageForMultipleTaskObject", actionName, getSelectedObjects().size()); } // must be static, otherwise JVM crashes (probably because of some wicket serialization issues) + @SuppressWarnings("unchecked") private static boolean isCoordinator(IModel rowModel, boolean isHeader) { - SelectableBean dto = getDto(rowModel, isHeader); - return dto != null && TaskTypeUtil.isCoordinator(dto.getValue()); + if (isNotTaskModel(rowModel)) { + return false; + } + TaskType task = getTask((IModel>) rowModel, isHeader); + return task != null && TaskTypeUtil.isCoordinator(task); } // must be static, otherwise JVM crashes (probably because of some wicket serialization issues) + @SuppressWarnings("unchecked") private static boolean isManageableTreeRoot(IModel rowModel, boolean isHeader) { - SelectableBean dto = getDto(rowModel, isHeader); - return dto != null && isManageableTreeRoot(dto.getValue()); + if (isNotTaskModel(rowModel)) { + return false; + } + TaskType task = getTask((IModel>) rowModel, isHeader); + return task != null && isManageableTreeRoot(task); + } + + private static boolean isNotTaskModel(IModel rowModel) { + if (rowModel == null) { + return false; + } + return rowModel.getObject() instanceof SelectableBean; } private static boolean isManageableTreeRoot(TaskType taskType) { return TaskTypeUtil.isCoordinator(taskType) || TaskTypeUtil.isPartitionedMaster(taskType); } - private static SelectableBean getDto(IModel rowModel, boolean isHeader) { + private static TaskType getTask(IModel> rowModel, boolean isHeader) { if (rowModel != null && !isHeader) { - Object object = rowModel.getObject(); - if (object instanceof SelectableBean) { - return (SelectableBean) object; + SelectableBean object = rowModel.getObject(); + if (object == null) { + return null; } + + return object.getValue(); } return null; } From 80a957c2ef8e58fdeabf230412aa670db4bec8b0 Mon Sep 17 00:00:00 2001 From: Pavol Mederly Date: Mon, 20 Apr 2020 16:51:54 +0200 Subject: [PATCH 4/5] Fix case approval state visualization 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. --- ...pprovalProcessExecutionInformationDto.java | 2 +- .../ApprovalStageExecutionInformationDto.java | 125 +- .../workflow/dto/ApproverEngagementDto.java | 12 +- ...pprovalSchemaExecutionInformationUtil.java | 43 + .../midpoint/schema/util/CaseEventUtil.java | 30 + .../schema/util/CaseWorkItemUtil.java | 9 +- .../ns/public/common/common-workflows-3.xsd | 3095 +++++++++-------- .../midpoint/wf/api/WorkflowManager.java | 4 +- ...rovalSchemaExecutionInformationHelper.java | 31 +- .../midpoint/wf/impl/WorkflowManagerImpl.java | 4 +- .../midpoint/wf/impl/AbstractWfTest.java | 39 +- .../assignments/TestAssignmentsAdvanced.java | 7 - .../midpoint/wf/impl/other/TestPreview.java | 298 ++ .../resources/preview/role-lab-manager.xml | 40 + .../src/test/resources/preview/user-alice.xml | 11 + .../preview/user-jane-the-lab-owner.xml | 12 + .../preview/user-kate-the-administrator.xml | 12 + .../preview/user-martin-the-dept-head.xml | 12 + .../resources/preview/user-peter-the-dean.xml | 12 + model/workflow-impl/testng-integration.xml | 1 + .../test/AbstractIntegrationTest.java | 20 +- 21 files changed, 2129 insertions(+), 1690 deletions(-) create mode 100644 infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java create mode 100644 infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java create mode 100644 model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java create mode 100644 model/workflow-impl/src/test/resources/preview/role-lab-manager.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-alice.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml create mode 100644 model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java index 60b95f6f409..cf89c9380f1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalProcessExecutionInformationDto.java @@ -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); diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java index 0f65a92cdcc..2adad5e2ed1 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApprovalStageExecutionInformationDto.java @@ -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; @@ -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; @@ -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 { @@ -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) { @@ -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(); @@ -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 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) { @@ -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)); + } + } } } } @@ -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; } @@ -201,10 +162,6 @@ public String getStageDisplayName() { return stageDisplayName; } - public LevelEvaluationStrategyType getEvaluationStrategy() { - return evaluationStrategy; - } - public ApprovalLevelOutcomeType getAutomatedOutcome() { return automatedOutcome; } @@ -238,7 +195,7 @@ public boolean isReachable() { return reachable; } - public void setReachable(boolean reachable) { + void setReachable(boolean reachable) { this.reachable = reachable; } } diff --git a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java index 2fb1483ac5e..f6a30ab0526 100644 --- a/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java +++ b/gui/admin-gui/src/main/java/com/evolveum/midpoint/web/page/admin/workflow/dto/ApproverEngagementDto.java @@ -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; @@ -18,24 +17,20 @@ /** * 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 @@ -43,11 +38,6 @@ public ObjectReferenceType getApproverRef() { return approverRef; } - @Nullable - public WorkItemId getExternalWorkItemId() { - return externalWorkItemId; - } - @Nullable public AbstractWorkItemOutputType getOutput() { return output; diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java new file mode 100644 index 00000000000..fa3dbe205fc --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/ApprovalSchemaExecutionInformationUtil.java @@ -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 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(); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java new file mode 100644 index 00000000000..26ba5be2087 --- /dev/null +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseEventUtil.java @@ -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 getEventsForStage(CaseType aCase, int stageNumber) { + return aCase.getEvent().stream() + .filter(e -> java.util.Objects.equals(e.getStageNumber(), stageNumber)) + .collect(Collectors.toList()); + } +} diff --git a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java index 7aa3cb016fa..35e462c590c 100644 --- a/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java +++ b/infra/schema/src/main/java/com/evolveum/midpoint/schema/util/CaseWorkItemUtil.java @@ -16,7 +16,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; - +import java.util.Objects; +import java.util.stream.Collectors; /** * @author bpowers @@ -89,5 +90,9 @@ public static boolean doesAssigneeExist(CaseWorkItemType workItem){ return false; } - + public static List getWorkItemsForStage(CaseType aCase, int stageNumber) { + return aCase.getWorkItem().stream() + .filter(wi -> Objects.equals(wi.getStageNumber(), stageNumber)) + .collect(Collectors.toList()); + } } diff --git a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd index 2b010d16674..69cf8ce75f5 100644 --- a/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd +++ b/infra/schema/src/main/resources/xml/ns/public/common/common-workflows-3.xsd @@ -1,1545 +1,1550 @@ - - - - - - - - - TODO - - - - - - - - - - - - - - - Rules for approving something (e.g. assignment of a role to a user). - Specifies the structure of approvers and their relations. - - - - - tns:level - 4.0 - removed - - - - - - - - ApprovalSchemaType.name - - - - - - - ApprovalSchemaType.description - - - - - - - - Levels, or stages, of the approval process. - - - 3.6 - ApprovalSchemaType.stage - - - - - - - - - - - One "level" (or stage) in the approval process. - - - - - tns:order - 4.0 - removed - number - - - tns:automaticallyApproved - 4.0 - removed - automaticallyCompleted - - - - - - - - Number of this approval stage. These should go from 1 to N. - - - 3.6 - ApprovalStageDefinitionType.number - - - - - - - ApprovalStageDefinitionType.name - - - - - - - ApprovalStageDefinitionType.displayName - - - - - - - ApprovalStageDefinitionType.description - - - - - - - - Instruction to approve something, by a user (if this points to a User object) or - by someone from a group of users (if this points to a Org object; representing - all users that belong to that organization). - - - ApprovalStageDefinitionType.approverRef - - - - - - -

- What relation(s) to use when determining approvers? E.g. "approver", "owner", - "securityApprover", and so on. -

-
- - 3.5 - ApprovalStageDefinitionType.approverRelation - -
-
- - - - Dynamically specifies approver(s). If specified, the expression(s) are evaluated and the result - is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). - May be used with approverRef element(s). - - - ApprovalStageDefinitionType.approverExpression - - - - - - - Must all approvers at this stage approve the thing (allMustApprove), - or first decision is taken as authoritative (firstDecides)? - - - ApprovalStageDefinitionType.evaluationStrategy - - - - - - - What is the outcome (of this stage) if there are no approvers? E.g. there are no users that have - been assigned a role as an approver; or a user has no managers, etc. - - - 3.6 - ApprovalStageDefinitionType.outcomeIfNoApprovers - - - - - - - How should be "groups" (orgs, roles) expanded? Default is "byClaimingWorkItem", i.e. they are not - expanded at all - their members can claim corresponding work items. - - - 3.6 - ApprovalStageDefinitionType.groupExpansion - - - - - - - Form to be displayed e.g. to present or request additional information. - EXPERIMENTAL - - - 3.6 - ApprovalStageDefinitionType.formRef - - - - - - - - Additional information for approver. Will be displayed when work item will be worked on. - - - 3.6 - ApprovalStageDefinitionType.additionalInformation - - - - - - - Expression specifying that this stage should be automatically processed (approved, rejected, skipped). - If the expression returns null, standard processing by human actors is carried out. - - - 3.6 - ApprovalStageDefinitionType.automaticallyCompleted - - - - - - - Duration of work items created at this stage. - TODO other time units, like business days? - - - 3.6 - ApprovalStageDefinitionType.duration - - - - - - - What actions are to be applied to work items when given timer(s) occur. - EXPERIMENTAL - - - 3.6 - ApprovalStageDefinitionType.timedActions - - - -
- -
- - - - - Information on actual or expected execution of an approval schema. - Contains information on the approval schema stages as defined, as executed, and as estimated to be executed. - - - 3.7 - - - - - - - - The case that was or would be created to approve give operation. This object reference should - contain the actual object (TODO). - - - c:CaseType - 4.0 - - - - - - - Current stage, if any. Null usually means that the approval process has not started yet. - - - - - - - Information related to a given stage of the approval process. - - - - - - - - - - - - Information of an actual or expected execution of an approval stage. - - - - 3.7 - - - - - - - Number of this approval stage. May be missing if the stage definition and its number is provided. - If both numbers are present, they must be equal. - - - - - - - Definition of this approval stage. - - - - - - - Approvers that are expected to be part of this stage. - (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) - - - - - - - TODO - - - - - - - - - - - Preview of an execution of an approval stage. - - Contains estimation of what approvers there would be; also - if applicable - any result - that is expected to be automatically determined, and so on. - - - - 3.7 - - - - - - - Approvers that are expected to be part of this stage. - (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) - - - - - - - Expected outcome that would occur automatically, i.e. not based on the decision of human approvers. - E.g. in case of no approvers, or because of automaticallyApproved/automaticallyCompleted feature. - - - - - - - Reason of expected automated completion. - - - - - - - Error message if preview couldn't be computed. (TODO) - - - - - - - - - - - Record of an actual execution of an approval stage. - - - - 3.7 - - - - - - - Events related to this stage. Currently might contain WorkItemEventType and StageCompletionEventType instances. - - - - - - - Currently active work items. - - - - - - - - - - - Result (outcome) of an approval process stage. - - - - - - - - - - Operation was approved at this stage. The approval process will continue at the next stage. - - - - - - - - - - Operation was rejected at this stage. The approval process will stop. - - - - - - - - - - This stage is silently skipped. This is useful for situations where we don't even want to start - an approval process if there are no approvers in it. - - Skipping whole approval process is currently supported only partly: when using approver relations. - For approver expressions, these are always evaluated within context of a workflow process. - - - - - - - - - - - - - Overall output from a work item: outcome (approve/reject/...), comment, additional delta, - and probably other things in the future. - - TODO devise an extension mechanism for this - - - - 3.6 - - - - - - - - - Additional delta(s) resulting from this action. - Typically if the user filled-in some information into - custom form. - - - - - - - - - - - - Result (outcome) of a work item. - - - - - - - - - - TODO - - - - - - - - - - TODO - - - - - - - - - - - - - - How should be "groups" (orgs, roles) expanded? - - - - - - - - - - Groups are not expanded at all - their members can claim corresponding work items. - - - - - - - - - - Groups are expanded on work item creation. One work item is created for each member of given org/role. - - - - - - - - - - - - - Enumeration of approval strategies at a particular stage. - - - - - - - - - - All approvers at a particular stage must approve the operation. - - - - - - - - - - First approver that votes will decide the whole stage (either by approving or by rejecting). - - - - - - - - - - - - - Configuration for workflows - for those parts not stored in the system config file. - - - - - - - - - - This property controls the workflow model hook - i.e. whether each request going - through the model subsystem should be processed by the workflow hook. So, if disabled, - all requests are executed immediately, without being processed by workflows. - - - WfConfigurationType.modelHookEnabled - 100 - - - - - - - How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, - automaticallyApproved items in AbstractRoleType? The default is "ifNoExplicitApprovalPolicyAction", that - means these items are applied only if no explicit approval policy action is encountered. - - - WfConfigurationType.useLegacyApproversSpecification - 120 - - - - - - - Whether to use default approval policy rules. The default is "ifNoApprovalPolicyAction", that means - these rules are applied only if no other approval policy action is encountered. - - - WfConfigurationType.useDefaultApprovalPolicyRules - 110 - - - - - - - Configuration related to tasks in which model operations are executed. - EXPERIMENTAL - - - true - 3.6.1 - WfConfigurationType.executionTasks - 500 - - - - - - - Instructions how to format approvers comments before storing them into metadata. - EXPERIMENTAL - - - true - 3.7.1 - WfConfigurationType.approverCommentsFormatting - - - - - - - WfConfigurationType.primaryChangeProcessor - 510 - - - - - - - WfConfigurationType.generalChangeProcessor - - - - - - - - - - - Instructions how to format approvers/reviewers comments before storing them into metadata. - Normally midPoint stores comments as they were entered by performers. However, each deployment can - tailor these e.g. by including performer name along with the comment. - EXPERIMENTAL - - - true - 3.7.1 - - - - - - - How to construct the comment. For example: performer.fullName + ': ' + output.comment. - - Available variables: - - performer (i.e. reviewer or approver), - - output (of AbstractWorkItemOutputType), - - workItem (of AbstractWorkItemType - only for certification), - - event (of WorkItemCompletionEventType - only for approvals). - - - - - - - Whether to include the particular output in comments. For example: output.comment != null. - Null or empty values are skipped regardless of the condition. - - - - - - - - - - Configuration related to tasks in which model operations are executed. - EXPERIMENTAL - - - true - true - 3.6.1 - - - - - - - Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). - - - 100 - WfExecutionTasksConfigurationType.serialization - - - - - - - TODO - - - 3.7 - 110 - WfExecutionTasksConfigurationType.executionConstraints - - - - - - - - - - - Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). - EXPERIMENTAL - - - true - true - 3.6.1 - - - - - - - Whether this feature is enabled. Default is true if "serialization" element is present; false otherwise. - - - WfExecutionTasksSerializationType.enabled - 100 - - - - - - - Scope of serialization. The default is "object". If multiple scopes are defined, serialization occurs on each one. - - - WfExecutionTasksSerializationType.scope - 110 - - - - - - - Interval after which the execution task is to be rescheduled in case of conflict. Default is 10 seconds. - - - WfExecutionTasksSerializationType.retryAfter - 120 - - - - - - - TODO - - - WfExecutionTasksSerializationType.groupPrefix - 130 - - - - - - - - - - - Scope of execution task serialization. - - - - - - - - - - No two workflow execution tasks from a single operation are allowed to execute at once. - - - - - - - - - - No two workflow execution tasks on a given object are allowed to execute at once. - - - - - - - - - - No two workflow execution tasks related to give target are allowed to execute at once. - Note that the information on target is not always available (e.g. when executing changes - that do not require approval), so this may not be absolutely reliable. - - - - - - - - - - No two workflow execution tasks are allowed to execute at once. - - - - - - - - - - - - - How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, - automaticallyApproved items in AbstractRoleType? - - - - - - - - - - The legacy approvers specification is never used. - - - - - - - - - - The legacy approvers specification is always used. It is used before any other (policy-based) approval actions. - - - - - - - - - - The legacy approvers specification is used if there's no explicit approval policy applicable to a given - target. - - - - - - - - - - - - - Whether to use default approval policy rules. - - - - - - - - - - Default approval policy rules are never used. - - - - - - - - - - Default approval policy rules are used if there are no applicable approval policy actions. - - - - - - - - - - - - - Configuration for workflow change processor. - - - true - - - - - - - WfChangeProcessorConfigurationType.enabled - 100 - - - - - - - Order in which the change processor should be invoked. (Unspecified means "at the end".) - NOT IMPLEMENTED YET. - - - - - - - - - - - Configuration for GeneralChangeProcessor. - - - true - - - - - - - - - - - - - - - A scenario for GeneralChangeProcessor. - - - - - - - Is this scenario enabled? - - - - - - - A human-readable name of the scenario (e.g. "Approving assignments of roles R1001-R1999 to users in XYZ organization"). - - - - - - - A condition controlling whether this scenario applies, i.e. whether a defined approval process should be started. - - - - - - - A name of the approval process. When the above condition is met, this process is started. It has to evaluate the situation, - seek user's (or users') approval(s), modifying the situation if necessary. - - - - - - - The name of the Spring bean used for customizations. It provides e.g. a method for externalizing process state, - a method for providing work item contents, and so on. - - - - - - - - - - Configuration for PrimaryChangeProcessor. - - - - - - - - - - - - PrimaryChangeProcessorConfigurationType.policyRuleBasedAspect - 200 - - - - - - - PrimaryChangeProcessorConfigurationType.addAssociationAspect - 210 - - - - - - - - - - - - Configuration for a primary change processor aspect. - - Some aspects do not require any configuration - for example, role and resource assignment ones. - They take all the approver information directly from the object (role or resource) being assigned. - However, there are some others (namely, role/resource/user/whatever add/modify aspects) that need - the explicit information about approver(s) in order to know where to route the request. - - For the former aspects, the approver information specified here takes precedence over - approver information derived from the objects being used (e.g. role or resource). - More specifically, if any approver information is here, no approver information is - taken from the objects. This could be changed (e.g. by allowing to tune this behavior) - in the future. - - - - - - - - - - Whether the aspect is enabled or not. - If not specified (but if aspect configuration is present), it is assumed to be true. - However, if the whole aspect configuration is absent, only aspects marked as "enabled-by-default" are enabled. - - - PcpAspectConfigurationType.enabled - 100 - - - - - - - Approvers for this aspect. The approver is a person (or group) that approves carrying out - action(s) relevant to this aspect. This reference may point to object of type UserType of OrgType. - - - PcpAspectConfigurationType.approverRef - 110 - - - - - - - Approvers for this aspect. If specified, the expression(s) are evaluated and the result - is used as a set of approvers (UserType, OrgType, or any combination of them). - May be used with approverRef element(s). - - - PcpAspectConfigurationType.approverExpression - 120 - - - - - - - More complex (multi-stage) approval schema. If used, it overrides both - approverRef and approverExpression elements. - - - PcpAspectConfigurationType.approvalSchema - 130 - - - - - - - Name of custom approval process. If used, it overrides - approverRef, approverExpression, and approvalSchema elements. - - For explicitness, only one of approverRef(s)/approverExpression(s), - approvalSchema and approvalProcess should be specified. - - THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. - - - - - - - Condition specifying when the item is automatically approved (e.g. "user is - from Board of Directors"). This is an expression that should yield a boolean value. - - - PcpAspectConfigurationType.automaticallyApproved - 140 - - - - - - - Condition specifying if the workflow should be started in the first place. - This is an expression that should yield a boolean value. It gets 'itemToApprove' parameter - that contains item to be approved - it is aspect-specific: might be e.g. an assignment, - an association + resource shadow discriminator, etc. - - The difference between applicabilityCondition and automaticallyApproved is that if the - former yields false, workflow is not even started. If it yields true, workflow is started, - and then 'automaticallyApproved' is evaluated. If it yields false, manual approval is - required. If true, item is automatically approved. - - CURRENTLY IMPLEMENTED ONLY IN AddAssociationAspect. - - - PcpAspectConfigurationType.applicabilityCondition - 150 - - - - - - - - - - - A generic configuration for a wf aspect. - - It is meant for non-standard aspects. (Standard aspects use named properties in - PrimaryChangeProcessorConfigurationType container.) - - - - - - - - - - - - Name of the aspect bean. - - - GenericPcpAspectConfigurationType.name - - - - - - - - - - - - Container for association-to-be-added in the context: resource shadow discriminator. - - - - - - - - - - Association to be added. - - - - - - - To which resource/kind/intent to add it. - - - - - - - - - - - - Describes the approval context, i.e. what has to be approved, the approval schema, and so on. - - - - - - - - - - - - - - - - - - - - - - - - DEPRECATED - We need to decide what to do with this. - - - - - - - - - - - - - - TODO Replace by forms eventually - TEMPORARY - - - - - - - - 3.7 - - - - - - - true - 3.7 - - - - - - - - - - - TODO Replace by forms eventually. - TEMPORARY - - - - - - - 3.7 - - - - - - - true - 3.7 - - - - - - - - - - - Why was this process started? For processes based on policy rules we define it via relevant policy rules. - (For legacy processes we don't provide this kind of information.) - - EXPERIMENTAL - - - - - - - - - - - TODO - - Note that the rule should be triggered. All irrelevant (non-approval) action types should be removed. - - - - - - - - - - - - - TODO - EXPERIMENTAL - - By default (when the base is not specified), positive time intervals are meant "after work item start". - Negative time intervals are meant "before work item deadline". - - - 3.6 - - - - - - - - - - - - - TODO - - - - 3.6 - - - - - - - Time will be taken relative to the deadline. (This is the default for zero or negative values.) - - - - - - - - - - Time will be taken relative to the work item creation timestamp. (This is the default for positive values.) - - - - - - - - - - - - - - Kind of operation. - - - 3.6 - - - - - - - - Complete (approve/reject) operation. (Explicit or automated.) - - - - - - - - - - Delegate operation. (Explicit or automated.) - - - - - - - - - - Escalate operation. (Explicit or automated.) - - - - - - - - - - Claim operation. - - - - - - - - - - Claim operation. - - - - - - - - - - Cancel operation. Work item was cancelled as a result of other action. (E.g. another work item - was completed, resulting in process or stage completion. Or the process was cancelled/deleted - externally.) - - - - - - - - - -
+ + + + + + + + + TODO + + + + + + + + + + + + + + + Rules for approving something (e.g. assignment of a role to a user). + Specifies the structure of approvers and their relations. + + + + + tns:level + 4.0 + removed + + + + + + + + ApprovalSchemaType.name + + + + + + + ApprovalSchemaType.description + + + + + + + + Levels, or stages, of the approval process. + + + 3.6 + ApprovalSchemaType.stage + + + + + + + + + + + One "level" (or stage) in the approval process. + + + + + tns:order + 4.0 + removed + number + + + tns:automaticallyApproved + 4.0 + removed + automaticallyCompleted + + + + + + + + Number of this approval stage. These should go from 1 to N. + + + 3.6 + ApprovalStageDefinitionType.number + + + + + + + ApprovalStageDefinitionType.name + + + + + + + ApprovalStageDefinitionType.displayName + + + + + + + ApprovalStageDefinitionType.description + + + + + + + + Instruction to approve something, by a user (if this points to a User object) or + by someone from a group of users (if this points to a Org object; representing + all users that belong to that organization). + + + ApprovalStageDefinitionType.approverRef + + + + + + +

+ What relation(s) to use when determining approvers? E.g. "approver", "owner", + "securityApprover", and so on. +

+
+ + 3.5 + ApprovalStageDefinitionType.approverRelation + +
+
+ + + + Dynamically specifies approver(s). If specified, the expression(s) are evaluated and the result + is used as a set of approvers (UserType, OrgType, RoleType, or any combination of them). + May be used with approverRef element(s). + + + ApprovalStageDefinitionType.approverExpression + + + + + + + Must all approvers at this stage approve the thing (allMustApprove), + or first decision is taken as authoritative (firstDecides)? + + + ApprovalStageDefinitionType.evaluationStrategy + + + + + + + What is the outcome (of this stage) if there are no approvers? E.g. there are no users that have + been assigned a role as an approver; or a user has no managers, etc. + + + 3.6 + ApprovalStageDefinitionType.outcomeIfNoApprovers + + + + + + + How should be "groups" (orgs, roles) expanded? Default is "byClaimingWorkItem", i.e. they are not + expanded at all - their members can claim corresponding work items. + + + 3.6 + ApprovalStageDefinitionType.groupExpansion + + + + + + + Form to be displayed e.g. to present or request additional information. + EXPERIMENTAL + + + 3.6 + ApprovalStageDefinitionType.formRef + + + + + + + + Additional information for approver. Will be displayed when work item will be worked on. + + + 3.6 + ApprovalStageDefinitionType.additionalInformation + + + + + + + Expression specifying that this stage should be automatically processed (approved, rejected, skipped). + If the expression returns null, standard processing by human actors is carried out. + + + 3.6 + ApprovalStageDefinitionType.automaticallyCompleted + + + + + + + Duration of work items created at this stage. + TODO other time units, like business days? + + + 3.6 + ApprovalStageDefinitionType.duration + + + + + + + What actions are to be applied to work items when given timer(s) occur. + EXPERIMENTAL + + + 3.6 + ApprovalStageDefinitionType.timedActions + + + +
+ +
+ + + + + Information on actual or expected execution of an approval schema. + Contains information on the approval schema stages as defined, as executed, and as estimated to be executed. + + + 3.7 + + + + + + + + The case that was or would be created to approve give operation. This object reference should + contain the actual object (TODO). + + + c:CaseType + 4.0 + + + + + + + Current stage, if any. Null usually means that the approval process has not started yet. + + + + + + + Information related to a given stage of the approval process. + + + + + + + + + + + + + Information of an actual or expected execution of an approval stage. + + + + 3.7 + + + + + + + Number of this approval stage. May be missing if the stage definition and its number is provided. + If both numbers are present, they must be equal. + + + + + + + Definition of this approval stage. + + + + + + + Approvers that are expected to be part of this stage. + (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) + + + + + + + TODO + + + + + + + + + + + Preview of an execution of an approval stage. + + Contains estimation of what approvers there would be; also - if applicable - any result + that is expected to be automatically determined, and so on. + + + + 3.7 + + + + + + + Approvers that are expected to be part of this stage. + (Some might be "for sure" and some might be "expected", but currently we do not distinguish between these.) + + + + + + + Expected outcome that would occur automatically, i.e. not based on the decision of human approvers. + E.g. in case of no approvers, or because of automaticallyApproved/automaticallyCompleted feature. + + + + + + + Reason of expected automated completion. + + + + + + + Error message if preview couldn't be computed. (TODO) + + + + + + + + + + + Record of an actual execution of an approval stage. + Note that this structure is deprecated. Everything we need can be found in the case object + attached to (enclosing) ApprovalSchemaExecutionInformationType. + + + + 3.7 + true + 4.1 + + + + + + + Events related to this stage. Currently might contain WorkItemEventType and StageCompletionEventType instances. + + + + + + + Currently active work items. + + + + + + + + + + + Result (outcome) of an approval process stage. + + + + + + + + + + Operation was approved at this stage. The approval process will continue at the next stage. + + + + + + + + + + Operation was rejected at this stage. The approval process will stop. + + + + + + + + + + This stage is silently skipped. This is useful for situations where we don't even want to start + an approval process if there are no approvers in it. + + Skipping whole approval process is currently supported only partly: when using approver relations. + For approver expressions, these are always evaluated within context of a workflow process. + + + + + + + + + + + + + Overall output from a work item: outcome (approve/reject/...), comment, additional delta, + and probably other things in the future. + + TODO devise an extension mechanism for this + + + + 3.6 + + + + + + + + + Additional delta(s) resulting from this action. + Typically if the user filled-in some information into + custom form. + + + + + + + + + + + + Result (outcome) of a work item. + + + + + + + + + + TODO + + + + + + + + + + TODO + + + + + + + + + + + + + + How should be "groups" (orgs, roles) expanded? + + + + + + + + + + Groups are not expanded at all - their members can claim corresponding work items. + + + + + + + + + + Groups are expanded on work item creation. One work item is created for each member of given org/role. + + + + + + + + + + + + + Enumeration of approval strategies at a particular stage. + + + + + + + + + + All approvers at a particular stage must approve the operation. + + + + + + + + + + First approver that votes will decide the whole stage (either by approving or by rejecting). + + + + + + + + + + + + + Configuration for workflows - for those parts not stored in the system config file. + + + + + + + + + + This property controls the workflow model hook - i.e. whether each request going + through the model subsystem should be processed by the workflow hook. So, if disabled, + all requests are executed immediately, without being processed by workflows. + + + WfConfigurationType.modelHookEnabled + 100 + + + + + + + How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, + automaticallyApproved items in AbstractRoleType? The default is "ifNoExplicitApprovalPolicyAction", that + means these items are applied only if no explicit approval policy action is encountered. + + + WfConfigurationType.useLegacyApproversSpecification + 120 + + + + + + + Whether to use default approval policy rules. The default is "ifNoApprovalPolicyAction", that means + these rules are applied only if no other approval policy action is encountered. + + + WfConfigurationType.useDefaultApprovalPolicyRules + 110 + + + + + + + Configuration related to tasks in which model operations are executed. + EXPERIMENTAL + + + true + 3.6.1 + WfConfigurationType.executionTasks + 500 + + + + + + + Instructions how to format approvers comments before storing them into metadata. + EXPERIMENTAL + + + true + 3.7.1 + WfConfigurationType.approverCommentsFormatting + + + + + + + WfConfigurationType.primaryChangeProcessor + 510 + + + + + + + WfConfigurationType.generalChangeProcessor + + + + + + + + + + + Instructions how to format approvers/reviewers comments before storing them into metadata. + Normally midPoint stores comments as they were entered by performers. However, each deployment can + tailor these e.g. by including performer name along with the comment. + EXPERIMENTAL + + + true + 3.7.1 + + + + + + + How to construct the comment. For example: performer.fullName + ': ' + output.comment. + + Available variables: + - performer (i.e. reviewer or approver), + - output (of AbstractWorkItemOutputType), + - workItem (of AbstractWorkItemType - only for certification), + - event (of WorkItemCompletionEventType - only for approvals). + + + + + + + Whether to include the particular output in comments. For example: output.comment != null. + Null or empty values are skipped regardless of the condition. + + + + + + + + + + Configuration related to tasks in which model operations are executed. + EXPERIMENTAL + + + true + true + 3.6.1 + + + + + + + Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). + + + 100 + WfExecutionTasksConfigurationType.serialization + + + + + + + TODO + + + 3.7 + 110 + WfExecutionTasksConfigurationType.executionConstraints + + + + + + + + + + + Whether and how to serialize execution tasks (if "execute after all approvals" is set to false). + EXPERIMENTAL + + + true + true + 3.6.1 + + + + + + + Whether this feature is enabled. Default is true if "serialization" element is present; false otherwise. + + + WfExecutionTasksSerializationType.enabled + 100 + + + + + + + Scope of serialization. The default is "object". If multiple scopes are defined, serialization occurs on each one. + + + WfExecutionTasksSerializationType.scope + 110 + + + + + + + Interval after which the execution task is to be rescheduled in case of conflict. Default is 10 seconds. + + + WfExecutionTasksSerializationType.retryAfter + 120 + + + + + + + TODO + + + WfExecutionTasksSerializationType.groupPrefix + 130 + + + + + + + + + + + Scope of execution task serialization. + + + + + + + + + + No two workflow execution tasks from a single operation are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks on a given object are allowed to execute at once. + + + + + + + + + + No two workflow execution tasks related to give target are allowed to execute at once. + Note that the information on target is not always available (e.g. when executing changes + that do not require approval), so this may not be absolutely reliable. + + + + + + + + + + No two workflow execution tasks are allowed to execute at once. + + + + + + + + + + + + + How to deal with legacy approvers specifications, i.e. approvalRef, approvalExpression, approvalSchema, + automaticallyApproved items in AbstractRoleType? + + + + + + + + + + The legacy approvers specification is never used. + + + + + + + + + + The legacy approvers specification is always used. It is used before any other (policy-based) approval actions. + + + + + + + + + + The legacy approvers specification is used if there's no explicit approval policy applicable to a given + target. + + + + + + + + + + + + + Whether to use default approval policy rules. + + + + + + + + + + Default approval policy rules are never used. + + + + + + + + + + Default approval policy rules are used if there are no applicable approval policy actions. + + + + + + + + + + + + + Configuration for workflow change processor. + + + true + + + + + + + WfChangeProcessorConfigurationType.enabled + 100 + + + + + + + Order in which the change processor should be invoked. (Unspecified means "at the end".) + NOT IMPLEMENTED YET. + + + + + + + + + + + Configuration for GeneralChangeProcessor. + + + true + + + + + + + + + + + + + + + A scenario for GeneralChangeProcessor. + + + + + + + Is this scenario enabled? + + + + + + + A human-readable name of the scenario (e.g. "Approving assignments of roles R1001-R1999 to users in XYZ organization"). + + + + + + + A condition controlling whether this scenario applies, i.e. whether a defined approval process should be started. + + + + + + + A name of the approval process. When the above condition is met, this process is started. It has to evaluate the situation, + seek user's (or users') approval(s), modifying the situation if necessary. + + + + + + + The name of the Spring bean used for customizations. It provides e.g. a method for externalizing process state, + a method for providing work item contents, and so on. + + + + + + + + + + Configuration for PrimaryChangeProcessor. + + + + + + + + + + + + PrimaryChangeProcessorConfigurationType.policyRuleBasedAspect + 200 + + + + + + + PrimaryChangeProcessorConfigurationType.addAssociationAspect + 210 + + + + + + + + + + + + Configuration for a primary change processor aspect. + + Some aspects do not require any configuration - for example, role and resource assignment ones. + They take all the approver information directly from the object (role or resource) being assigned. + However, there are some others (namely, role/resource/user/whatever add/modify aspects) that need + the explicit information about approver(s) in order to know where to route the request. + + For the former aspects, the approver information specified here takes precedence over + approver information derived from the objects being used (e.g. role or resource). + More specifically, if any approver information is here, no approver information is + taken from the objects. This could be changed (e.g. by allowing to tune this behavior) + in the future. + + + + + + + + + + Whether the aspect is enabled or not. + If not specified (but if aspect configuration is present), it is assumed to be true. + However, if the whole aspect configuration is absent, only aspects marked as "enabled-by-default" are enabled. + + + PcpAspectConfigurationType.enabled + 100 + + + + + + + Approvers for this aspect. The approver is a person (or group) that approves carrying out + action(s) relevant to this aspect. This reference may point to object of type UserType of OrgType. + + + PcpAspectConfigurationType.approverRef + 110 + + + + + + + Approvers for this aspect. If specified, the expression(s) are evaluated and the result + is used as a set of approvers (UserType, OrgType, or any combination of them). + May be used with approverRef element(s). + + + PcpAspectConfigurationType.approverExpression + 120 + + + + + + + More complex (multi-stage) approval schema. If used, it overrides both + approverRef and approverExpression elements. + + + PcpAspectConfigurationType.approvalSchema + 130 + + + + + + + Name of custom approval process. If used, it overrides + approverRef, approverExpression, and approvalSchema elements. + + For explicitness, only one of approverRef(s)/approverExpression(s), + approvalSchema and approvalProcess should be specified. + + THIS PROPERTY (approvalProcess) IS NOT SUPPORTED YET. + + + + + + + Condition specifying when the item is automatically approved (e.g. "user is + from Board of Directors"). This is an expression that should yield a boolean value. + + + PcpAspectConfigurationType.automaticallyApproved + 140 + + + + + + + Condition specifying if the workflow should be started in the first place. + This is an expression that should yield a boolean value. It gets 'itemToApprove' parameter + that contains item to be approved - it is aspect-specific: might be e.g. an assignment, + an association + resource shadow discriminator, etc. + + The difference between applicabilityCondition and automaticallyApproved is that if the + former yields false, workflow is not even started. If it yields true, workflow is started, + and then 'automaticallyApproved' is evaluated. If it yields false, manual approval is + required. If true, item is automatically approved. + + CURRENTLY IMPLEMENTED ONLY IN AddAssociationAspect. + + + PcpAspectConfigurationType.applicabilityCondition + 150 + + + + + + + + + + + A generic configuration for a wf aspect. + + It is meant for non-standard aspects. (Standard aspects use named properties in + PrimaryChangeProcessorConfigurationType container.) + + + + + + + + + + + + Name of the aspect bean. + + + GenericPcpAspectConfigurationType.name + + + + + + + + + + + + Container for association-to-be-added in the context: resource shadow discriminator. + + + + + + + + + + Association to be added. + + + + + + + To which resource/kind/intent to add it. + + + + + + + + + + + + Describes the approval context, i.e. what has to be approved, the approval schema, and so on. + + + + + + + + + + + + + + + + + + + + + + + + DEPRECATED + We need to decide what to do with this. + + + + + + + + + + + + + + TODO Replace by forms eventually + TEMPORARY + + + + + + + + 3.7 + + + + + + + true + 3.7 + + + + + + + + + + + TODO Replace by forms eventually. + TEMPORARY + + + + + + + 3.7 + + + + + + + true + 3.7 + + + + + + + + + + + Why was this process started? For processes based on policy rules we define it via relevant policy rules. + (For legacy processes we don't provide this kind of information.) + + EXPERIMENTAL + + + + + + + + + + + TODO + + Note that the rule should be triggered. All irrelevant (non-approval) action types should be removed. + + + + + + + + + + + + + TODO + EXPERIMENTAL + + By default (when the base is not specified), positive time intervals are meant "after work item start". + Negative time intervals are meant "before work item deadline". + + + 3.6 + + + + + + + + + + + + + TODO + + + + 3.6 + + + + + + + Time will be taken relative to the deadline. (This is the default for zero or negative values.) + + + + + + + + + + Time will be taken relative to the work item creation timestamp. (This is the default for positive values.) + + + + + + + + + + + + + + Kind of operation. + + + 3.6 + + + + + + + + Complete (approve/reject) operation. (Explicit or automated.) + + + + + + + + + + Delegate operation. (Explicit or automated.) + + + + + + + + + + Escalate operation. (Explicit or automated.) + + + + + + + + + + Claim operation. + + + + + + + + + + Claim operation. + + + + + + + + + + Cancel operation. Work item was cancelled as a result of other action. (E.g. another work item + was completed, resulting in process or stage completion. Or the process was cancelled/deleted + externally.) + + + + + + + + + +
diff --git a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java index ae9d1825b7e..eb7dbc36522 100644 --- a/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java +++ b/model/workflow-api/src/main/java/com/evolveum/midpoint/wf/api/WorkflowManager.java @@ -96,11 +96,11 @@ ChangesByState getChangesByState(CaseType approvalCase, CaseType rootCase, Model * * Does not need authorization checks before execution; it uses model calls in order to gather any information needed. * - * @param taskOid OID of an approval task that should be analyzed + * @param caseOid OID of an approval case that should be analyzed * @param opTask task under which this operation is carried out * @param parentResult operation result */ - ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String taskOid, Task opTask, OperationResult parentResult) + ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String caseOid, Task opTask, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException; diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java index aad55ec9abd..4f48af01c91 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/ApprovalSchemaExecutionInformationHelper.java @@ -95,19 +95,23 @@ private ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInforma return rv; } rv.setCurrentStageNumber(aCase.getStageNumber()); - Integer currentStageNumber = !purePreview ? aCase.getStageNumber() : 0; - if (currentStageNumber == null) { - result.recordFatalError("Information on current stage number in " + aCase + " is missing or not accessible."); - return rv; + int currentStageNumber; + if (purePreview) { + currentStageNumber = 0; + } else { + if (aCase.getStageNumber() != null) { + currentStageNumber = aCase.getStageNumber(); + } else { + result.recordFatalError("Information on current stage number in " + aCase + " is missing or not accessible."); + return rv; + } } List stagesDef = ApprovalContextUtil.sortAndCheckStages(approvalSchema); for (ApprovalStageDefinitionType stageDef : stagesDef) { ApprovalStageExecutionInformationType stageExecution = new ApprovalStageExecutionInformationType(prismContext); stageExecution.setNumber(stageDef.getNumber()); stageExecution.setDefinition(stageDef); - if (stageDef.getNumber() <= currentStageNumber) { - stageExecution.setExecutionRecord(createStageExecutionRecord(aCase, stageDef.getNumber(), currentStageNumber)); - } else { + if (stageDef.getNumber() > currentStageNumber) { stageExecution.setExecutionPreview(createStageExecutionPreview(aCase, opTask.getChannel(), stageDef, opTask, result)); } rv.getStage().add(stageExecution); @@ -136,17 +140,4 @@ private ApprovalStageExecutionPreviewType createStageExecutionPreview(CaseType a } return rv; } - - private ApprovalStageExecutionRecordType createStageExecutionRecord(CaseType aCase, Integer stageNumberObject, - int currentStageNumber) { - int stageNumber = stageNumberObject; - ApprovalStageExecutionRecordType rv = new ApprovalStageExecutionRecordType(prismContext); - aCase.getEvent().stream() - .filter(e -> e.getStageNumber() != null && e.getStageNumber() == stageNumber) - .forEach(e -> rv.getEvent().add(e.clone())); - if (stageNumber == currentStageNumber) { - rv.getWorkItem().addAll(CloneUtil.cloneCollectionMembers(aCase.getWorkItem())); - } - return rv; - } } diff --git a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java index a98b6d72f9a..1d925f7c9fc 100644 --- a/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java +++ b/model/workflow-impl/src/main/java/com/evolveum/midpoint/wf/impl/WorkflowManagerImpl.java @@ -169,13 +169,13 @@ public ChangesByState getChangesByState(CaseType approvalCase, CaseType rootCase } @Override - public ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String taskOid, Task opTask, + public ApprovalSchemaExecutionInformationType getApprovalSchemaExecutionInformation(String caseOid, Task opTask, OperationResult parentResult) throws SchemaException, ConfigurationException, ObjectNotFoundException, CommunicationException, SecurityViolationException, ExpressionEvaluationException { OperationResult result = parentResult.createSubresult(DOT_INTERFACE + "getApprovalSchemaExecutionInformation"); try { - return approvalSchemaExecutionInformationHelper.getApprovalSchemaExecutionInformation(taskOid, opTask, result); + return approvalSchemaExecutionInformationHelper.getApprovalSchemaExecutionInformation(caseOid, opTask, result); } catch (Throwable t) { result.recordFatalError("Couldn't determine schema execution information: " + t.getMessage(), t); throw t; diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java index 359389a2a65..f52ca58601a 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/AbstractWfTest.java @@ -7,13 +7,11 @@ package com.evolveum.midpoint.wf.impl; +import com.evolveum.midpoint.model.api.WorkflowService; import com.evolveum.midpoint.model.common.SystemObjectCache; import com.evolveum.midpoint.model.impl.AbstractModelImplementationIntegrationTest; import com.evolveum.midpoint.model.impl.lens.Clockwork; -import com.evolveum.midpoint.prism.Item; -import com.evolveum.midpoint.prism.PrismObject; -import com.evolveum.midpoint.prism.PrismReference; -import com.evolveum.midpoint.prism.PrismReferenceValue; +import com.evolveum.midpoint.prism.*; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.query.ObjectQuery; import com.evolveum.midpoint.prism.query.builder.S_AtomicFilterExit; @@ -21,9 +19,11 @@ import com.evolveum.midpoint.schema.RelationRegistry; import com.evolveum.midpoint.schema.SearchResultList; import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.constants.SchemaConstants; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.CaseWorkItemUtil; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; +import com.evolveum.midpoint.schema.util.WorkItemId; import com.evolveum.midpoint.security.api.SecurityUtil; import com.evolveum.midpoint.task.api.Task; import com.evolveum.midpoint.task.api.TaskExecutionStatus; @@ -86,6 +86,7 @@ public abstract class AbstractWfTest extends AbstractModelImplementationIntegrat @Autowired protected WorkflowManager workflowManager; @Autowired protected WorkflowEngine workflowEngine; @Autowired protected WorkItemManager workItemManager; + @Autowired protected WorkflowService workflowService; @Autowired protected PrimaryChangeProcessor primaryChangeProcessor; @Autowired protected GeneralChangeProcessor generalChangeProcessor; @Autowired protected SystemObjectCache systemObjectCache; @@ -330,6 +331,22 @@ protected ObjectQuery getOpenItemsQuery() { .build(); } + protected void approveWorkItem(CaseWorkItemType workItem, Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, ObjectAlreadyExistsException, PolicyViolationException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + workflowService.completeWorkItem(WorkItemId.of(workItem), + new AbstractWorkItemOutputType(prismContext).outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_APPROVE), + task, result); + } + + protected void rejectWorkItem(CaseWorkItemType workItem, Task task, OperationResult result) throws CommunicationException, + ObjectNotFoundException, ObjectAlreadyExistsException, PolicyViolationException, SchemaException, + SecurityViolationException, ConfigurationException, ExpressionEvaluationException { + workflowService.completeWorkItem(WorkItemId.of(workItem), + new AbstractWorkItemOutputType(prismContext).outcome(SchemaConstants.MODEL_APPROVAL_OUTCOME_REJECT), + task, result); + } + public class RelatedCases { private CaseType approvalCase; private CaseType requestCase; @@ -367,4 +384,18 @@ protected CaseAsserter assertCase(OperationResult result, String message) assertThat(caseOid).as("No background case OID").isNotNull(); return assertCase(caseOid, message); } + + /** + * Takes case from the work item (via parent reference). + */ + protected CaseAsserter assertCase(CaseWorkItemType workItem, String message) throws ObjectNotFoundException, SchemaException, SecurityViolationException, CommunicationException, ConfigurationException, ExpressionEvaluationException { + PrismContainerable parent = workItem.asPrismContainerValue().getParent(); + assertThat(parent).isNotNull(); + //noinspection unchecked + PrismContainerValue grandParent = ((PrismContainer) parent).getParent(); + assertThat(grandParent).isNotNull(); + String approvalCaseOid = ((PrismObjectValue) grandParent).getOid(); + assertThat(approvalCaseOid).as("No parent case OID").isNotNull(); + return assertCase(approvalCaseOid, message); + } } diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java index 8c10493ab98..1db88ec04d4 100644 --- a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/assignments/TestAssignmentsAdvanced.java @@ -146,13 +146,6 @@ public void initSystem(Task initTask, OperationResult initResult) throws Excepti DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_JSON); } - @Override - protected TracingProfileType getTestMethodTracingProfile() { - return null; -// return createModelAndWorkflowLoggingTracingProfile() -// .fileNamePattern(TEST_METHOD_TRACING_FILENAME_PATTERN); - } - @Test public void test102AddRoles123AssignmentYYYYDeputy() throws Exception { login(userAdministrator); diff --git a/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java new file mode 100644 index 00000000000..8fb882b1cec --- /dev/null +++ b/model/workflow-impl/src/test/java/com/evolveum/midpoint/wf/impl/other/TestPreview.java @@ -0,0 +1,298 @@ +/* + * Copyright (c) 2010-2019 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.wf.impl.other; + +import com.evolveum.midpoint.model.api.ModelExecuteOptions; +import com.evolveum.midpoint.model.api.context.ModelContext; +import com.evolveum.midpoint.prism.PrismContext; +import com.evolveum.midpoint.prism.PrismObject; +import com.evolveum.midpoint.prism.SerializationOptions; +import com.evolveum.midpoint.prism.delta.ObjectDelta; +import com.evolveum.midpoint.schema.SchemaConstantsGenerated; +import com.evolveum.midpoint.schema.constants.ObjectTypes; +import com.evolveum.midpoint.schema.result.OperationResult; +import com.evolveum.midpoint.schema.util.*; +import com.evolveum.midpoint.task.api.Task; +import com.evolveum.midpoint.test.TestResource; +import com.evolveum.midpoint.util.DebugUtil; +import com.evolveum.midpoint.util.MiscUtil; +import com.evolveum.midpoint.util.exception.*; +import com.evolveum.midpoint.wf.impl.AbstractWfTestPolicy; +import com.evolveum.midpoint.xml.ns._public.common.common_3.*; + +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ContextConfiguration; +import org.testng.annotations.Test; + +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import static com.evolveum.midpoint.schema.util.ApprovalSchemaExecutionInformationUtil.getEmbeddedCaseBean; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests the preview feature: + * 1) before operation is executed, + * 2) in various stages of approval process as well. + */ +@ContextConfiguration(locations = {"classpath:ctx-workflow-test-main.xml"}) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public class TestPreview extends AbstractWfTestPolicy { + + private static final File TEST_RESOURCE_DIR = new File("src/test/resources/preview"); + + /* + * Alice is given lab-manager role. This assignment should be approved in three stages: + * 1. by Jane the Lab Owner + * 2. by Martin the Department Head + * 3. By Peter the Dean *OR* Kate the Administrator + */ + private static final TestResource ROLE_LAB_MANAGER = new TestResource<>(TEST_RESOURCE_DIR, "role-lab-manager.xml", "e4b5d89a-cb7f-4d26-b31f-e86556e2a4ca"); + private static final TestResource USER_ALICE = new TestResource<>(TEST_RESOURCE_DIR, "user-alice.xml", "0b728b21-1649-40d2-80dd-566a5faaeb86"); + private static final TestResource USER_JANE = new TestResource<>(TEST_RESOURCE_DIR, "user-jane-the-lab-owner.xml", "feb34927-7671-401e-9f5b-8f7ec94f3112"); + private static final TestResource USER_MARTIN = new TestResource<>(TEST_RESOURCE_DIR, "user-martin-the-dept-head.xml", "072bf16a-e424-456c-a212-7996f34c3c5c"); + private static final TestResource USER_PETER = new TestResource<>(TEST_RESOURCE_DIR, "user-peter-the-dean.xml", "408beff8-c988-4c77-ac5e-ed26697d6982"); + private static final TestResource USER_KATE = new TestResource<>(TEST_RESOURCE_DIR, "user-kate-the-administrator.xml", "4aab211b-5faf-45e2-acaf-a17a89d39fd1"); + + @Override + protected PrismObject getDefaultActor() { + return userAdministrator; + } + + @Override + public void initSystem(Task initTask, OperationResult initResult) throws Exception { + super.initSystem(initTask, initResult); + + repoAdd(USER_ALICE, initResult); + repoAdd(USER_JANE, initResult); + repoAdd(USER_MARTIN, initResult); + repoAdd(USER_PETER, initResult); + repoAdd(USER_KATE, initResult); + repoAdd(ROLE_LAB_MANAGER, initResult); + + DebugUtil.setPrettyPrintBeansAs(PrismContext.LANG_YAML); + } + + /** + * Use pure "previewChanges" and check the approval schema execution information. + */ + @Test + public void test100PurePreview() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + ModelExecuteOptions options = new ModelExecuteOptions(); + options.getOrCreatePartialProcessing().setApprovals(PartialProcessingTypeType.PROCESS); + + when(); + ModelContext modelContext = + modelInteractionService.previewChanges(getAssignmentDeltas(), options, task, result); + + then(); + ApprovalSchemaExecutionInformationType execInfo = getSingleExecutionInformation(modelContext); + displayExecutionInformation(execInfo); + + // Everything is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_JANE.oid); + assertThat(getFutureStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Start the execution (i.e. case will be created) and check the approval schema execution information + * just after that. + */ + @Test + public void test110InfoAfterStart() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + executeChanges(getAssignmentDeltas(), null, task, result); + + then(); + CaseType approvalCase = assertCase(result, "after") + .display() + .subcases() + .single() + .display() + .assertStageNumber(1) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (1) approvers").containsExactlyInAnyOrder(USER_JANE.oid); + + // Everything after stage 1 is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve the work item by administrator; this will move the case into second stage. + */ + @Test + public void test120InfoAfterStageOneApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + CaseWorkItemType workItem = getWorkItem(task, result); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(2) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (2) approvers").containsExactlyInAnyOrder(USER_MARTIN.oid); + // Everything after stage 2 is "in the future", so we have to obtain approvers information from the execInfo.stage list + assertThat(getFutureStageApprovers(execInfo, 3)).as("stage 3 approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve the work item again by administrator; this will move the case into the third stage. + */ + @Test + public void test130InfoAfterStageTwoApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + CaseWorkItemType workItem = getWorkItem(task, result); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(3) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getPastStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (3) approvers").containsExactlyInAnyOrder(USER_PETER.oid, USER_KATE.oid); + } + + /** + * Approve one of the work items again by administrator. The other work item will be still open. + */ + @Test + public void test140InfoAfterStageThreeFirstApproval() throws Exception { + given(); + login(userAdministrator); + Task task = getTestTask(); + OperationResult result = getTestOperationResult(); + + when(); + List workItems = getWorkItems(task, result); + CaseWorkItemType workItem = workItems.stream() + .filter(wi -> MiscUtil.extractSingleton(wi.getAssigneeRef()).getOid().equals(USER_PETER.oid)) + .findFirst().orElseThrow(() -> new AssertionError("No Peter's work item")); + approveWorkItem(workItem, task, result); + + then(); + CaseType approvalCase = assertCase(workItem, "after") + .display() + .assertStageNumber(3) + .getObject().asObjectable(); + + ApprovalSchemaExecutionInformationType execInfo = + workflowManager.getApprovalSchemaExecutionInformation(approvalCase.getOid(), task, result); + displayExecutionInformation(execInfo); + + assertThat(getPastStageApprovers(execInfo, 1)).as("stage 1 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getPastStageApprovers(execInfo, 2)).as("stage 2 approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID); + assertThat(getCurrentStageApprovers(execInfo)).as("current stage (3) approvers").containsExactlyInAnyOrder(USER_ADMINISTRATOR_OID, USER_KATE.oid); + assertThat(getOpenWorkItemsApprovers(execInfo)).as("open work items approvers").containsExactlyInAnyOrder(USER_KATE.oid); + } + + private Collection getPastStageApprovers(ApprovalSchemaExecutionInformationType execInfo, int stageNumber) { + return getApproversFromCompletionEvents(execInfo, stageNumber); + } + + private Collection getCurrentStageApprovers(ApprovalSchemaExecutionInformationType execInfo) { + Set approvers = new HashSet<>(getOpenWorkItemsApprovers(execInfo)); + approvers.addAll(getApproversFromCompletionEvents(execInfo, execInfo.getCurrentStageNumber())); + return approvers; + } + + private Collection getFutureStageApprovers(ApprovalSchemaExecutionInformationType executionInfo, int stageNumber) { + ApprovalStageExecutionInformationType stage = ApprovalSchemaExecutionInformationUtil.getStage(executionInfo, stageNumber); + assertThat(stage).isNotNull(); + assertThat(stage.getExecutionPreview()).isNotNull(); + return stage.getExecutionPreview().getExpectedApproverRef().stream() + .map(ObjectReferenceType::getOid) + .collect(Collectors.toSet()); + } + + private Collection getOpenWorkItemsApprovers(ApprovalSchemaExecutionInformationType execInfo) { + CaseType aCase = getEmbeddedCaseBean(execInfo); + return aCase.getWorkItem().stream() + .filter(wi -> java.util.Objects.equals(wi.getStageNumber(), aCase.getStageNumber())) + .filter(CaseWorkItemUtil::isCaseWorkItemNotClosed) + .flatMap(wi -> wi.getAssigneeRef().stream()) + .map(ObjectReferenceType::getOid) + .collect(Collectors.toSet()); + } + + // We could use either (closed) work items or WorkItemCompletionEventType events. The latter is better because we can also + // attorney information there. + private Collection getApproversFromCompletionEvents(ApprovalSchemaExecutionInformationType executionInfo, int stageNumber) { + CaseType aCase = getEmbeddedCaseBean(executionInfo); + return aCase.getEvent().stream() + .filter(event -> event instanceof WorkItemCompletionEventType) + .map(event -> (WorkItemCompletionEventType) event) + .filter(event -> java.util.Objects.equals(event.getStageNumber(), stageNumber)) + .filter(CaseEventUtil::completedByUserAction) + .map(event -> event.getInitiatorRef().getOid()) + .collect(Collectors.toSet()); + } + + private ApprovalSchemaExecutionInformationType getSingleExecutionInformation(ModelContext modelContext) { + List approvalsExecutionList = + modelContext.getHookPreviewResults(ApprovalSchemaExecutionInformationType.class); + assertThat(approvalsExecutionList).size().as("approvals execution list").isEqualTo(1); + return approvalsExecutionList.get(0); + } + + private void displayExecutionInformation(ApprovalSchemaExecutionInformationType execution) throws SchemaException { + String xml = prismContext.xmlSerializer().options(SerializationOptions.createSerializeCompositeObjects()) + .root(SchemaConstantsGenerated.C_APPROVAL_SCHEMA_EXECUTION_INFORMATION).serialize(execution.asPrismContainerValue()); + displayValue("execution information", xml); + } + + private List> getAssignmentDeltas() throws SchemaException { + return Collections.singletonList( + deltaFor(UserType.class) + .item(UserType.F_ASSIGNMENT) + .add(ObjectTypeUtil.createAssignmentTo(ROLE_LAB_MANAGER.oid, ObjectTypes.ROLE, prismContext)) + .asObjectDeltaCast(USER_ALICE.oid)); + } +} diff --git a/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml b/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml new file mode 100644 index 00000000000..aaed05871a0 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/role-lab-manager.xml @@ -0,0 +1,40 @@ + + + + lab-manager + + + + + add + + + + + + + 1 + + + + 2 + + + + 3 + + + allMustApprove + + + + + + + diff --git a/model/workflow-impl/src/test/resources/preview/user-alice.xml b/model/workflow-impl/src/test/resources/preview/user-alice.xml new file mode 100644 index 00000000000..59c9b063aaa --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-alice.xml @@ -0,0 +1,11 @@ + + + + alice + diff --git a/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml b/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml new file mode 100644 index 00000000000..8c805f5e9d1 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-jane-the-lab-owner.xml @@ -0,0 +1,12 @@ + + + + jane + Jane the Lab Owner + diff --git a/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml b/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml new file mode 100644 index 00000000000..d751088f59a --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-kate-the-administrator.xml @@ -0,0 +1,12 @@ + + + + kate + Kate the Administrator + diff --git a/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml b/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml new file mode 100644 index 00000000000..8542f134b00 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-martin-the-dept-head.xml @@ -0,0 +1,12 @@ + + + + martin + Martin the Dept Head + diff --git a/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml b/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml new file mode 100644 index 00000000000..f89cfbb8103 --- /dev/null +++ b/model/workflow-impl/src/test/resources/preview/user-peter-the-dean.xml @@ -0,0 +1,12 @@ + + + + peter + Peter the Dean + diff --git a/model/workflow-impl/testng-integration.xml b/model/workflow-impl/testng-integration.xml index a5988623f9a..d53f44f0519 100644 --- a/model/workflow-impl/testng-integration.xml +++ b/model/workflow-impl/testng-integration.xml @@ -35,6 +35,7 @@ + diff --git a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java index 8a7b1a268c4..4934230d87d 100644 --- a/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java +++ b/repo/repo-test-util/src/main/java/com/evolveum/midpoint/test/AbstractIntegrationTest.java @@ -35,6 +35,9 @@ import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.LoggerContext; + +import com.evolveum.midpoint.schema.result.CompiledTracingProfile; + import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -264,18 +267,11 @@ public void startTestContext(ITestResult testResult) throws SchemaException { displayTestTitle(testName); Task task = createTask(testMethodName); - // TODO inttest: add tracing facility - ideally without the need to create subresult -// OperationResult rootResult = task.getResult(); -// TracingProfileType tracingProfile = getTestMethodTracingProfile(); -// CompiledTracingProfile compiledTracingProfile = tracingProfile != null ? -// tracer.compileProfile(tracingProfile, rootResult) : null; -// OperationResult result = rootResult.subresult(task.getName() + "Run") -// .tracingProfile(compiledTracingProfile) -// .build(); - - // This is quite a hack. We need to provide traced result to all clients that need to access it via the task. - // (I.e. not via the test context.) -// task.setResult(result); + TracingProfileType tracingProfile = getTestMethodTracingProfile(); + if (tracingProfile != null) { + CompiledTracingProfile compiledTracingProfile = tracer.compileProfile(tracingProfile, task.getResult()); + task.getResult().tracingProfile(compiledTracingProfile); + } MidpointTestContextWithTask.create(testClass, testMethodName, task, task.getResult()); tracer.setTemplateParametersCustomizer(params -> { From fc386727e0412716a88d45c0a0ab61c914ebb602 Mon Sep 17 00:00:00 2001 From: Katarina Valalikova Date: Mon, 20 Apr 2020 19:28:46 +0200 Subject: [PATCH 5/5] removing needless println --- .../evolveum/midpoint/prism/impl/PrismContainerValueImpl.java | 1 - 1 file changed, 1 deletion(-) diff --git a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java index 6e9cbe6e012..dc4ff64faa0 100644 --- a/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java +++ b/infra/prism-impl/src/main/java/com/evolveum/midpoint/prism/impl/PrismContainerValueImpl.java @@ -1649,7 +1649,6 @@ public void keepPaths(List keep) throws SchemaException { ItemPath itemPath = item.getPath().removeIds(); if (!ItemPathCollectionsUtil.containsSuperpathOrEquivalent(keep, itemPath) && !ItemPathCollectionsUtil.containsSubpathOrEquivalent(keep, itemPath)) { - System.out.println("Removing " + itemPath + " because not in " + keep); removeItem(ItemName.fromQName(itemName), Item.class); } else { if (item instanceof PrismContainer) {