Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(edit-content) : Allowing Users to add separators between workflow actions #26390

Merged
merged 30 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
f830a05
feat(Workflows) : Allowing Users to add separators between actions in…
jcastro-dotcms Oct 6, 2023
91dbc46
Code refactoring. Adding Upgrade Task to create a new column: `metada…
jcastro-dotcms Oct 9, 2023
7f29b7d
Adding some Javadoc.
jcastro-dotcms Oct 9, 2023
45c88f5
Merge remote-tracking branch 'origin/master' into issue-26389-create-…
jcastro-dotcms Oct 10, 2023
6bdd0f6
Adding Integration Test and minor changes.
jcastro-dotcms Oct 10, 2023
0e1cd70
Implementing SonarQube feedback.
jcastro-dotcms Oct 10, 2023
1e26510
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Oct 10, 2023
e57bbe2
Implementing SonarQube feedback.
jcastro-dotcms Oct 11, 2023
4c9f37e
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Oct 11, 2023
3060fbf
Implementing SonarQube feedback.
jcastro-dotcms Oct 11, 2023
0e958f4
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Oct 11, 2023
c3cbc91
Implementing SonarQube feedback.
jcastro-dotcms Oct 11, 2023
56014d6
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Oct 11, 2023
4421e4a
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Oct 12, 2023
a56682d
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 7, 2023
4e0eec6
Fixing Upgrade Task's name so that it will get executed correctly.
jcastro-dotcms Dec 7, 2023
7f55da1
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 8, 2023
fc13007
Missing Java import statement.
jcastro-dotcms Dec 8, 2023
f4cf0a2
Implementing SonarQube feedback.
jcastro-dotcms Dec 8, 2023
29715e5
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 8, 2023
f2b36b3
Implementing SonarQube feedback.
jcastro-dotcms Dec 11, 2023
79666b1
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 11, 2023
f31ba93
dev: fix divider button
rjvelazco Dec 11, 2023
977aab4
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
fmontes Dec 11, 2023
6fbde92
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 12, 2023
1c67029
dev: prevent the SEPARATOR action from being displayed and editing
rjvelazco Dec 12, 2023
f5fef64
Style: set default cursor when action is not editable (SEPARATOR)
rjvelazco Dec 12, 2023
870e385
Merge branch 'issue-26389-create-separator-for-Workflow-Actions' of h…
rjvelazco Dec 12, 2023
5bc7a94
Avoid displaying the `Separator` action in the LISTING rendering mode…
jcastro-dotcms Dec 12, 2023
bfa2adb
Merge branch 'master' into issue-26389-create-separator-for-Workflow-…
jcastro-dotcms Dec 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.dotcms.contenttype.model.field.Field;
import com.dotcms.contenttype.model.type.ContentType;
import com.dotcms.contenttype.transform.field.LegacyFieldTransformer;
import com.dotcms.exception.ExceptionUtil;
import com.dotcms.mock.response.MockHttpResponse;
import com.dotcms.repackage.com.google.common.annotations.VisibleForTesting;
import com.dotcms.repackage.javax.validation.constraints.NotNull;
Expand Down Expand Up @@ -418,15 +419,20 @@ public final Response findStepsByScheme(@Context final HttpServletRequest reques
} // findSteps.

/**
* Finds the available actions for an inode
* Finds the Workflow Actions that are available for a specific Contentlet Inode. Here's an
* example of how you can use this method:
* <pre>
* GET http://localhost:8080/api/v1/workflow/contentlet/{CONTENTLET-INODE}/actions?renderMode={editing|listing}
* </pre>
*
* @param request HttpServletRequest
* @param inode String
* @param renderMode String, this is an uncase sensitive query string (?renderMode=) optional parameter.
* By default the findAvailableAction will run on WorkflowAPI.RenderMode.EDITING, if you want to run for instance on WorkflowAPI.RenderMode.LISTING
* you can send the renderMode parameter as ?renderMode=listing
* This will be used to filter the action based on the show on configuration for each action.
* @return Response
* @param request The current instance of the {@link HttpServletRequest}.
* @param inode The Inode of the Contentlet.
* @param renderMode This is a case-insensitive optional parameter. By default, this method will
* run EDITING rendering mode. The available modes are specified via the
* {{@link #validRenderModeSet}} variable.
*
* @return Response A {@link Response} object that contains the available actions for the
* specified Contentlet.
*/
@GET
@Path("/contentlet/{inode}/actions")
Expand All @@ -446,13 +452,15 @@ public final Response findAvailableActions(@Context final HttpServletRequest req
this.workflowHelper.checkRenderMode (renderMode, initDataObject.getUser(), this.validRenderModeSet);

final List<WorkflowAction> actions = this.workflowHelper.findAvailableActions(inode, initDataObject.getUser(),
LISTING.equalsIgnoreCase(renderMode)?WorkflowAPI.RenderMode.LISTING:WorkflowAPI.RenderMode.EDITING);
LISTING.equalsIgnoreCase(renderMode)
? WorkflowAPI.RenderMode.LISTING
: WorkflowAPI.RenderMode.EDITING);
return Response.ok(new ResponseEntityView<>(actions.stream()
.map(this::toWorkflowActionView).collect(Collectors.toList()))).build(); // 200
} catch (Exception e) {
Logger.error(this.getClass(),
"Exception on findAvailableActions, contentlet inode: " + inode +
", exception message: " + e.getMessage(), e);
.map(this::toWorkflowActionView).collect(Collectors.toList()))).build();
} catch (final Exception e) {
Logger.error(this.getClass(), String.format("An error occurred when finding available" +
" Workflow Actions for Contentlet Inode '%s' in mode '%s': %s", inode, renderMode,
ExceptionUtil.getErrorMessage(e)), e);
return ResponseUtil.mapExceptionResponse(e);
}
} // findAvailableActions.
Expand All @@ -462,10 +470,18 @@ private WorkflowActionView toWorkflowActionView(final WorkflowAction workflowAct
return convertToWorkflowActionView(workflowAction);
}

/**
* Takes the information from a Workflow Action and transforms it into a View object that can
* display it in JSON notation appropriately. Keep in mind that any new property you add to the
* Workflow Action class will need to be added here as well.
*
* @param workflowAction The {@link WorkflowAction} that will be transformed.
*
* @return The {@link WorkflowActionView} that contains the information from the Workflow
* Action.
*/
public static WorkflowActionView convertToWorkflowActionView(final WorkflowAction workflowAction) {

final WorkflowActionView workflowActionView = new WorkflowActionView();

workflowActionView.setId(workflowAction.getId());
workflowActionView.setName(workflowAction.getName());
workflowActionView.setStepId(workflowAction.getSchemeId());
Expand All @@ -489,7 +505,7 @@ public static WorkflowActionView convertToWorkflowActionView(final WorkflowActio
workflowActionView.setDestroyActionlet(workflowAction.hasDestroyActionlet());
workflowActionView.setShowOn(workflowAction.getShowOn());
workflowActionView.setActionInputs(createActionInputViews(workflowAction));

workflowActionView.setMetadata(workflowAction.getMetadata());
return workflowActionView;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,29 @@

import com.dotcms.repackage.javax.validation.constraints.NotNull;
import com.dotcms.rest.api.Validated;
import com.dotmarketing.business.APILocator;
import com.dotmarketing.business.Role;
import com.dotmarketing.exception.DotRuntimeException;
import com.dotmarketing.portlets.workflows.model.WorkflowAction;
import com.dotmarketing.portlets.workflows.model.WorkflowState;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.vavr.control.Try;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
* This class represents a Workflow Action Form used by different parts of the system, such as REST
* Endpoints. It is used to create, update and delete Workflow Actions in dotCMS.
*
* @author Jonathan Sanchez
* @since Dec 6th, 2017
*/
@JsonDeserialize(builder = WorkflowActionForm.Builder.class)
public class WorkflowActionForm extends Validated {

Expand All @@ -17,7 +33,7 @@ public class WorkflowActionForm extends Validated {
@NotNull
private final String schemeId;

// you can send an optional stepId for a new Action when you want to associated the action to the step in the same transaction.
// You can send an optional stepId for a new Action when you want to associate it to the step in the same transaction.
private final String stepId;

@NotNull
Expand All @@ -39,6 +55,8 @@ public class WorkflowActionForm extends Validated {
private final String actionNextStep;
private final String actionNextAssign;
private final String actionCondition;
private static final String METADATA_SUBTYPE_ATTR = "subtype";
private final Map<String, Object> metadata;

public String getStepId() {
return stepId;
Expand Down Expand Up @@ -100,6 +118,10 @@ public String getActionCondition() {
return actionCondition;
}

public Map<String, Object> getMetadata() {
return this.metadata;
}

@Override
public String toString() {
return "WorkflowActionForm{" +
Expand All @@ -118,6 +140,7 @@ public String toString() {
", actionNextStep='" + actionNextStep + '\'' +
", actionNextAssign='" + actionNextAssign + '\'' +
", actionCondition='" + actionCondition + '\'' +
", metadata='" + metadata + '\'' +
'}';
}

Expand All @@ -138,10 +161,11 @@ public WorkflowActionForm(final Builder builder) {
this.actionAssignable = builder.actionAssignable;
this.actionRoleHierarchyForAssign = builder.actionRoleHierarchyForAssign;
this.roleHierarchyForAssign = (actionAssignable && actionRoleHierarchyForAssign);
this.metadata = builder.metadata;
this.checkValid();
}

public static final class Builder {
public static final class Builder {

@JsonProperty()
private String actionId;
Expand All @@ -163,9 +187,14 @@ public static final class Builder {
@JsonProperty(required = true)
private boolean actionCommentable;

/**
* @deprecated This attribute is not necessary as a single workflow action can be available
* for locked and/or unlocked content now. See
* <a href="https://github.com/dotCMS/core/issues/13287">#13287</a>
*/
@Deprecated
@JsonProperty(required = true)
private boolean requiresCheckout;
private boolean requiresCheckout = false;
@JsonProperty(required = true)
private boolean actionRoleHierarchyForAssign;
@JsonProperty(required = true)
Expand All @@ -177,7 +206,8 @@ public static final class Builder {

@JsonProperty(required = true)
private Set<WorkflowState> showOn;

@JsonProperty()
private Map<String, Object> metadata;

public Builder showOn(Set<WorkflowState> showOn) {
this.showOn = showOn;
Expand Down Expand Up @@ -250,8 +280,53 @@ public Builder actionCondition(String actionCondition) {
return this;
}

/**
* Sets the metadata for this Workflow Action. This is a Map of key/value pairs that may
* include different custom properties that define the behavior of an action.
*
* @param metadata Different custom properties for this action.
*
* @return The current {@link Builder} instance.
*/
public Builder metadata(final Map<String, Object> metadata) {
this.metadata = metadata;
return this;
}

/**
* Marks this Workflow Action as a Separator. This is a special type of action that does
* not execute any sub-actions at all, as it simply groups X number of actions together
* in the UI. The result of this may be seen as the differentiation between Primary and
* Secondary Actions.
*
* @param schemeId The ID of the Workflow Scheme that this action belongs to.
* @param stepId The ID of the Workflow Step that this action belongs to.
*
* @return The current {@link Builder} instance.
*/
public Builder separator(final String schemeId, final String stepId) {
this.schemeId(schemeId);
this.stepId(stepId);
this.actionName(WorkflowAction.SEPARATOR);
this.actionAssignable(false);
this.actionCommentable(false);
this.actionRoleHierarchyForAssign(false);
this.actionNextStep(WorkflowAction.CURRENT_STEP);
this.actionNextAssign(Try.of(() -> APILocator.getRoleAPI().loadRoleByKey(Role.CMS_ANONYMOUS_ROLE).getId())
.getOrElseThrow(e -> new DotRuntimeException("Anonymous Role ID not found in the database", e)));
this.actionCondition(WorkflowAction.SEPARATOR);
this.showOn(Arrays.stream(WorkflowState.values()).filter(state -> state != WorkflowState.LISTING).collect(java.util.stream.Collectors.toSet()));
if (null == this.metadata) {
this.metadata = new HashMap<>();
}
this.metadata.put(METADATA_SUBTYPE_ATTR, WorkflowAction.SEPARATOR);
return this;
}

public WorkflowActionForm build() {
return new WorkflowActionForm(this);
}

}

}
35 changes: 21 additions & 14 deletions dotCMS/src/main/java/com/dotcms/workflow/helper/WorkflowHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -1536,11 +1536,16 @@ private ContentType findContentType (final String variable, final User user)
}

/**
* Save a WorkflowActionForm returning the WorkflowAction created.
* A WorkflowActionForm can send a stepId in that case the Action will be associated to the Step in the same transaction.
* @param actionId When present an update operation takes place otherwise an insert is executed
* @param workflowActionForm WorkflowActionForm
* @return WorkflowAction (workflow action created)
* Saves a Workflow Action. A {@link WorkflowActionForm} object can send a Step ID, in which
* case the Action will be associated to the Step in the same transaction.
*
* @param actionId If present, an update operation takes place. Otherwise, an insert
* is executed.
* @param workflowActionForm The {@link WorkflowActionForm} object with the Workflow Action data
* that will be saved.
* @param user The {@link User} that is performing this action.
*
* @return The {@link WorkflowAction} object that was created.
*/
@WrapInTransaction
public WorkflowAction saveAction(final String actionId, final WorkflowActionForm workflowActionForm, final User user) {
Expand All @@ -1564,7 +1569,7 @@ public WorkflowAction saveAction(final String actionId, final WorkflowActionForm
newAction.setRequiresCheckout(false);
newAction.setShowOn(workflowActionForm.getShowOn());
newAction.setRoleHierarchyForAssign(workflowActionForm.isRoleHierarchyForAssign());

newAction.setMetadata(workflowActionForm.getMetadata());
try {

newAction.setNextAssign(this.resolveRole(actionNextAssign).getId());
Expand Down Expand Up @@ -1607,17 +1612,19 @@ public WorkflowAction saveAction(final String actionId, final WorkflowActionForm
workflowActionClass.setName(NotifyAssigneeActionlet.class.getDeclaredConstructor().newInstance().getName());
workflowActionClass.setOrder(0);
this.workflowAPI.saveActionClass(workflowActionClass, user);
} catch (Exception e) {
Logger.error(this.getClass(), e.getMessage());
Logger.debug(this, e.getMessage(), e);
throw new DotWorkflowException(e.getMessage(), e);
} catch (final Exception e) {
final String errorMsg = String.format("Failed to save Workflow Action Class with ID '%s': %s", newAction.getId(), ExceptionUtil.getErrorMessage(e));
Logger.error(this.getClass(), errorMsg);
Logger.debug(this, errorMsg, e);
throw new DotWorkflowException(errorMsg, e);
}
});
}
} catch (Exception e) {
Logger.error(this.getClass(), e.getMessage());
Logger.debug(this, e.getMessage(), e);
throw new DotWorkflowException(e.getMessage(), e);
} catch (final Exception e) {
final String errorMsg = String.format("Failed to save Workflow Action '%s': %s", actionId, ExceptionUtil.getErrorMessage(e));
Logger.error(this.getClass(), errorMsg);
Logger.debug(this, errorMsg, e);
throw new DotWorkflowException(errorMsg, e);
}

return newAction;
Expand Down
Loading
Loading