Expand my Community achievements bar.

Workflow notification sent to one user

Avatar

Level 2

Hi guys

I have the following use case:

I created a custom workflow for publishing content into production, but only if the reviewer approves the changes that are made by the requester.

Now, the problem is that, when the workflow is created, the notification is sent to all the members of the group where is reviewer is member( I know that this is OTB functionality) and what I need to do is: only the reviewer to be notified about this workflow (because he was assigned to check the content before publishing it to production).

How do you think I can solve this one.Have you solved something like this so far?

Thanks!

12 Replies

Avatar

Community Advisor

Hi @puscasd22398856, I think you could consider to use Dynamic participant step to achieve your goal. It is quite flexible i.e you could define your own dialog and write custom ecma script to handle your case. Here you can find some Adobe documentation https://experienceleague.adobe.com/docs/experience-manager-65/developing/extending-aem/extending-wor...

 

The option I can see which will be simpler, if reviewer is always the same user you can simply assign this user in participant step instead of assigning entire group.

 

Last option will be an extension of previous case. Create dedicated reviewers group and use it in participant step. Assign all users that are responsible for review to that group.

Avatar

Level 2

Hi @lukasz-m thanks for your response.

I'm using Dynamic Participant Step (also I created a class that's implementing ParticipantStepChooser, where I'm overwriting the getParticipant method).The result is following: custom dynamic participant step is working fine...the workflow is assigned correctly, but the problem is that, not only the assigned user is being notified, but all group participants (all members can see the workflow from inbox : /aem/inbox and all of them can take action on that workflow, which is not correct.The only user that should be able to make action on workflow should be the reviewer).

 

@Override
public String getParticipant(WorkItem workItem,
WorkflowSession workflowSession,
MetaDataMap metaDataMap) {
final String reviewer = workItem.getWorkflow()
.getMetaDataMap().get(Constants.REVIEWER.value(), String.class);
if(reviewer != null) {
logger.debug("Workflow assigned to {}", reviewer);
return reviewer;
}else{
logger.debug("Reviewer could not be set");
return null;
}
}

 

Avatar

Employee Advisor

1. How are you planning to select the specific reviewer in your case?

2. Are you planning to provide a dropdown with list of reviewer names to requester(content authors) after selecting your custom workflow? 

Avatar

Level 2

Hi @DEBAL_DAS 

1.the reviewer are fetched from the specific group ("reviewers") and displayed on the page as dropdown.

2.after choosing the desired reviewer, the workflow will start (form with submit btn)

Avatar

Employee Advisor

From point no:1 , it means content author has ability to select desired reviewer. 

1. I have created list of users (reviewer) and assigned it to a specific group called reviewers as shown below  -

DEBAL_DAS_0-1643205613311.png

 

DEBAL_DAS_1-1643205717799.png

 

2. Created a page property called: Content Reviewer and populating same list of reviewer created in step-1

 

DEBAL_DAS_2-1643205868394.png

3. After selecting a desired reviewer , author can start the below custom workflow -

   

DEBAL_DAS_3-1643206005412.png

 

4. Here is the custom workflow process logic -

 

package com.aem.demo.core.workflows;

import java.util.Objects;

import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adobe.granite.taskmanagement.Task;
import com.adobe.granite.taskmanagement.TaskManager;
import com.adobe.granite.taskmanagement.TaskManagerException;
import com.adobe.granite.taskmanagement.TaskManagerFactory;
import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.InboxItem;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import com.drew.lang.annotations.Nullable;
import com.google.common.base.Strings;

@component(property = {
Constants.SERVICE_DESCRIPTION + "=This workflow step is responsible to set Inbox Notification for Reviewer",
Constants.SERVICE_VENDOR + "=AEM Demo Debal", "process.label" + "=Content Reviewer Notification" })
public class ContentReviewerNotification implements WorkflowProcess {

private final Logger logger = LoggerFactory.getLogger(ContentReviewerNotification.class);

public static final String NOTIFICATION_TASK_TYPE = "Review Notification";

@Override
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metadataMap)
throws WorkflowException {

ResourceResolver resourceResolver = workflowSession.adaptTo(ResourceResolver.class);
try {
TaskManager taskManager = resourceResolver.adaptTo(TaskManager.class);
TaskManagerFactory taskManagerFactory = taskManager.getTaskManagerFactory();
String payloadPath = workItem.getWorkflowData().getPayload().toString();
@nullable
Resource payloadResource = resourceResolver.getResource(payloadPath);

if (Objects.nonNull(payloadResource) && payloadResource.isResourceType("cq:Page")) {
Resource metadataresource = payloadResource.getChild("jcr:content");
ModifiableValueMap modifiableValueMap = metadataresource.adaptTo(ModifiableValueMap.class);

@nullable
String reviewer = modifiableValueMap.get("reviewer", String.class);

if (!Strings.isNullOrEmpty(reviewer)) {

Task newTask = taskManagerFactory.newTask(NOTIFICATION_TASK_TYPE);
newTask.setName("Content Expiry Notification");
newTask.setContentPath(payloadPath);
// Optionally set priority (High, Medium, Low)
newTask.setPriority(InboxItem.Priority.HIGH);
newTask.setDescription("Content Review Notification");
newTask.setInstructions("Content Review Notification");
newTask.setCurrentAssignee(reviewer);
taskManager.createTask(newTask);
}

}

} catch (TaskManagerException te) {
logger.error("Could not create task {} ", te.getMessage());
}

}

}

 

Here , you could see using import com.adobe.granite.taskmanagement.Task;
import com.adobe.granite.taskmanagement.TaskManager; I am able to set inbox notification for selected desired reviewer only -

 

Notification details available for admin as shown below -

 

DEBAL_DAS_4-1643206251242.png

 

But the same notification isn't available for Debal Das and Ram Sharma (admin is impersonating)

 

DEBAL_DAS_5-1643206343048.png

 

DEBAL_DAS_6-1643206377765.png

 

But it is available for debaldasone as it is assigned to him -

DEBAL_DAS_7-1643206470686.pngDEBAL_DAS_8-1643206556503.png

 

Hope this will help at some extent. Correct me if I am wrong at any point.

 

 

Avatar

Level 2

The idea is that I should implement this functionality without creating tasks ( tasks does not permit me to select a next step).

Here is the wf model.As you can see after assigning the wf, the reviewer needs to be able to choose reject or approve (using task, I can not do this)

 

Screenshot 2022-01-26 at 18.52.41.png

Avatar

Employee Advisor

Here is the workflow model -

 

DEBAL_DAS_0-1643382546639.png

 

Content Review Notification process step -

 

package com.aem.demo.core.workflows;

import java.util.Objects;

import org.apache.sling.api.resource.ModifiableValueMap;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.exec.WorkflowProcess;
import com.adobe.granite.workflow.metadata.MetaDataMap;
import com.drew.lang.annotations.Nullable;

@component(property = {
Constants.SERVICE_DESCRIPTION
+ "=This workflow process step is responsible to set Inbox Notification for Reviewer",
Constants.SERVICE_VENDOR + "=AEM Demo Debal", "process.label" + "=Content Review Notification" })
public class ContentReviewNotification implements WorkflowProcess {

@Override
public void execute(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metadataMap)
throws WorkflowException {
ResourceResolver resourceResolver = workflowSession.adaptTo(ResourceResolver.class);
String payloadPath = workItem.getWorkflowData().getPayload().toString();
@nullable
Resource payloadResource = resourceResolver.getResource(payloadPath);

if (Objects.nonNull(payloadResource) && payloadResource.isResourceType("cq:Page")) {
Resource metadataresource = payloadResource.getChild("jcr:content");
ModifiableValueMap modifiableValueMap = metadataresource.adaptTo(ModifiableValueMap.class);

@nullable
String reviewer = modifiableValueMap.get("reviewer", String.class);

MetaDataMap map = workItem.getWorkflow().getWorkflowData().getMetaDataMap();

// Putting some values in the map
map.put("Reviewer", reviewer);

}

}

}

 

DynamicParticipantStep , here task is assigning to specific reviewer -

 

package com.aem.demo.core.workflows;

import org.osgi.service.component.annotations.Component;

import com.adobe.granite.workflow.WorkflowException;
import com.adobe.granite.workflow.WorkflowSession;
import com.adobe.granite.workflow.exec.ParticipantStepChooser;
import com.adobe.granite.workflow.exec.WorkItem;
import com.adobe.granite.workflow.metadata.MetaDataMap;

@component(service = ParticipantStepChooser.class, immediate = true, property = { "chooser.label=" + "Dynamic Reviewe Step Selection"})
public class DynamicParticipantStep implements ParticipantStepChooser {

@Override
public String getParticipant(WorkItem workItem, WorkflowSession workflowSession, MetaDataMap metaDataMap)
throws WorkflowException {
String participant = "";

MetaDataMap map = workItem.getWorkflow().getWorkflowData().getMetaDataMap();
String reviewer = (String) map.get("Reviewer");

if (!reviewer.isEmpty()) {

participant = reviewer;
} else {
participant = "reviewers";
}

return participant;
}
}

 

After starting the workflow on the page -

 

DEBAL_DAS_1-1643382695598.png

 

Notification is available for Ram Sharma only as shown below -

DEBAL_DAS_3-1643382859707.png

 

Notification isn't available for Debal Das and Debal One Das as shown below -

DEBAL_DAS_4-1643382904885.png

DEBAL_DAS_5-1643382941529.png

 

They are part of same group -

 

DEBAL_DAS_6-1643383011482.png

 

Please review

Avatar

Level 2

@DEBAL_DAS thanks for you response.

I have same implementation and I don't get it...why is not working as it should.

This impl is before opening this thread.For me is same as yours.

Here is my impl:



/**
* Assigns the workflow to REVIEWER
*/
@Component(
service = ParticipantStepChooser.class,
immediate = true,
property = {
ParticipantStepChooser.SERVICE_PROPERTY_LABEL + "=Assign workflow to reviewer"}
)
public class DynamicallyParticipantChooserReviewer implements ParticipantStepChooser {
private static final ESAPILogger logger = ESAPILogger.getLogger(DynamicallyParticipantChooserReviewer.class);
/**
* Assign the workflow to the determined user
* @param workItem
* @param workflowSession
* @param metaDataMap
* @return the found user that needs to get the workflow
*/
@Override
public String getParticipant(WorkItem workItem,
WorkflowSession workflowSession,
MetaDataMap metaDataMap) {
final String reviewer = workItem.getWorkflow()
.getMetaDataMap().get(Constants.REVIEWER.value(), String.class);
if(reviewer != null) {
logger.debug("Workflow assigned to {}", reviewer);
return reviewer;
}else{
logger.debug("Reviewer could not be set");
return null;
}
}
}




@Component(
service = WorkflowProcess.class,
immediate = true,
property = {"process.label = Send e-mail notification"}
)
public class SendEmailNotificationWorkflowProcessImpl implements WorkflowProcess{
@Reference
private EmailService emailService;
@Reference
private WorkflowUtility workflowUtility;

private static final ESAPILogger logger = ESAPILogger.getLogger(SendEmailNotificationWorkflowProcessImpl.class);
private static final String PATH = "path";
private static final String TITLE = "title";
private static final String COMMENTS = "comments";
private static final String REVIEWER = "reviewer";
private static final String REQUESTER = "requester";
private static final String DATE = "date";
private static final String ENV_HOST_URL = "envHostUrl";
private static final String PROFILE_EMAIL = "./profile/email";
private static final String PRIORITY = "priority";
private static final String DEPENDENCY = "dependency";
private static final String LOG_ERR = "ERROR";
private static final String COMPLETED = "completed";
private static final String EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template.txt";
private static final String REFUSED_EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template_refused.txt";
private static final String COMPLETED_EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template_completed.txt";

@Override
public void execute(WorkItem workItem,
WorkflowSession workflowSession,
MetaDataMap metaDataMap){
logger.info("Send email");
final ResourceResolver rr = workflowSession.adaptTo(ResourceResolver.class);
final MetaDataMap data = workItem.getWorkflow().getMetaDataMap();
final Map<String, String> emailDetails = getEmailDetails(data);
final String destinationOrStatus = metaDataMap.get("PROCESS_ARGS", String.class);

if(destinationOrStatus.equals(REQUESTER)) {
final Workflow workflow = workItem.getWorkflow();
final String comment = getLastStepComment(workflowSession, workflow);
emailDetails.replace(COMMENTS, comment);
}

sendEmail(rr, emailDetails, destinationOrStatus);
}

private void sendEmail(ResourceResolver rr, Map<String, String> emailDetails,
String destinationOrStatus){
final UserManager userManager = rr.adaptTo(UserManager.class);
if(userManager != null) {
final String requesterId = emailDetails.get(REQUESTER);
final String reviewerId = emailDetails.get(REVIEWER);
List<String> failListRecipient;

try {
Map<String, String> details = getReviewerAndRequesterMails(userManager, requesterId, reviewerId);
details.put(PATH, emailDetails.get(PATH));
details.put(TITLE, emailDetails.get(TITLE));
details.put(COMMENTS, emailDetails.get(COMMENTS));
details.put(DATE, emailDetails.get(DATE));
details.put(PRIORITY, emailDetails.get(PRIORITY));
details.put(ENV_HOST_URL, workflowUtility.getEnvironmentUrl(rr) + "aem/inbox");

if(destinationOrStatus.equals(REVIEWER)) {
failListRecipient =
emailService.sendEmail(EMAIL_TEMPLATE_ADDR, details, details.get(REVIEWER));
}else if(destinationOrStatus.equals(COMPLETED)){
failListRecipient =
emailService.sendEmail(COMPLETED_EMAIL_TEMPLATE_ADDR, details, details.get(REQUESTER));
}else{
failListRecipient =
emailService.sendEmail(REFUSED_EMAIL_TEMPLATE_ADDR, details, details.get(REQUESTER));
}
if (!failListRecipient.isEmpty()) {
throw new RepositoryException("Email could not be sent");
}
} catch (RepositoryException e) {
logger.error(LOG_ERR, e);
logger.error(e.getMessage());
} catch (Exception e) {
logger.error(LOG_ERR, e);
}
}
}

private static Map<String, String> getReviewerAndRequesterMails(
UserManager userManager, String requesterId, String reviewerId)
throws RepositoryException {
Map<String, String> principals = new HashMap<>();
Authorizable requesterAuthorizable = userManager.getAuthorizable(requesterId);
Authorizable reviewerAuthorizable = userManager.getAuthorizable(reviewerId);

if(!requesterAuthorizable.hasProperty(PROFILE_EMAIL) ||
!reviewerAuthorizable.hasProperty(PROFILE_EMAIL)) {
throw new RepositoryException("Mails could not be obtained");
}else {
String requesterMail = requesterAuthorizable.getProperty(PROFILE_EMAIL)[0].getString();
String reviewerMail = reviewerAuthorizable.getProperty(PROFILE_EMAIL)[0].getString();

principals.put(REVIEWER, reviewerMail);
principals.put(REQUESTER, requesterMail);
}
return principals;
}

private static Map<String, String> getEmailDetails(MetaDataMap data){
final Map<String, String> result = new HashMap<>();
for(Map.Entry<String, Object> elem : data.entrySet()){
String elemKey = elem.getKey();
if(!elemKey.contains(DEPENDENCY)) {
result.put(elemKey, elem.getValue().toString());
}
}
return result;
}

private static String getLastStepComment(WorkflowSession workflowSession, Workflow workflow){
try {
List<HistoryItem> historyItems = workflowSession.getHistory(workflow);
if(!historyItems.isEmpty()) {
final String lastComment = historyItems.get(historyItems.size() - 1)
.getComment();
if(!lastComment.isEmpty()) {
return lastComment;
}
}
} catch (WorkflowException e) {
logger.debug(LOG_ERR, e);
}
return "No comment provided";
}
}





 

Avatar

Level 2

@DEBAL_DAS thanks for replying.

Maybe the problem is the create group.Please let me know how your group is configured.

Here is my impl before opening the thread and is not working.

For me the code is the same.Correct me if I'm wrong:



@Component(
service = WorkflowProcess.class,
immediate = true,
property = {"process.label = Send e-mail notification"}
)
public class SendEmailNotificationWorkflowProcessImpl implements WorkflowProcess{
@Reference
private EmailService emailService;
@Reference
private WorkflowUtility workflowUtility;

private static final ESAPILogger logger = ESAPILogger.getLogger(SendEmailNotificationWorkflowProcessImpl.class);
private static final String PATH = "path";
private static final String TITLE = "title";
private static final String COMMENTS = "comments";
private static final String REVIEWER = "reviewer";
private static final String REQUESTER = "requester";
private static final String DATE = "date";
private static final String ENV_HOST_URL = "envHostUrl";
private static final String PROFILE_EMAIL = "./profile/email";
private static final String PRIORITY = "priority";
private static final String DEPENDENCY = "dependency";
private static final String LOG_ERR = "ERROR";
private static final String COMPLETED = "completed";
private static final String EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template.txt";
private static final String REFUSED_EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template_refused.txt";
private static final String COMPLETED_EMAIL_TEMPLATE_ADDR =
"/etc/notification/email" +
"/acs-commons/publish_notification_email_template_completed.txt";

@Override
public void execute(WorkItem workItem,
WorkflowSession workflowSession,
MetaDataMap metaDataMap){
logger.info("Send email");
final ResourceResolver rr = workflowSession.adaptTo(ResourceResolver.class);
final MetaDataMap data = workItem.getWorkflow().getMetaDataMap();
final Map<String, String> emailDetails = getEmailDetails(data);
final String destinationOrStatus = metaDataMap.get("PROCESS_ARGS", String.class);

if(destinationOrStatus.equals(REQUESTER)) {
final Workflow workflow = workItem.getWorkflow();
final String comment = getLastStepComment(workflowSession, workflow);
emailDetails.replace(COMMENTS, comment);
}

sendEmail(rr, emailDetails, destinationOrStatus);
}

private void sendEmail(ResourceResolver rr, Map<String, String> emailDetails,
String destinationOrStatus){
final UserManager userManager = rr.adaptTo(UserManager.class);
if(userManager != null) {
final String requesterId = emailDetails.get(REQUESTER);
final String reviewerId = emailDetails.get(REVIEWER);
List<String> failListRecipient;

try {
Map<String, String> details = getReviewerAndRequesterMails(userManager, requesterId, reviewerId);
details.put(PATH, emailDetails.get(PATH));
details.put(TITLE, emailDetails.get(TITLE));
details.put(COMMENTS, emailDetails.get(COMMENTS));
details.put(DATE, emailDetails.get(DATE));
details.put(PRIORITY, emailDetails.get(PRIORITY));
details.put(ENV_HOST_URL, workflowUtility.getEnvironmentUrl(rr) + "aem/inbox");

if(destinationOrStatus.equals(REVIEWER)) {
failListRecipient =
emailService.sendEmail(EMAIL_TEMPLATE_ADDR, details, details.get(REVIEWER));
}else if(destinationOrStatus.equals(COMPLETED)){
failListRecipient =
emailService.sendEmail(COMPLETED_EMAIL_TEMPLATE_ADDR, details, details.get(REQUESTER));
}else{
failListRecipient =
emailService.sendEmail(REFUSED_EMAIL_TEMPLATE_ADDR, details, details.get(REQUESTER));
}
if (!failListRecipient.isEmpty()) {
throw new RepositoryException("Email could not be sent");
}
} catch (RepositoryException e) {
logger.error(LOG_ERR, e);
logger.error(e.getMessage());
} catch (Exception e) {
logger.error(LOG_ERR, e);
}
}
}

private static Map<String, String> getReviewerAndRequesterMails(
UserManager userManager, String requesterId, String reviewerId)
throws RepositoryException {
Map<String, String> principals = new HashMap<>();
Authorizable requesterAuthorizable = userManager.getAuthorizable(requesterId);
Authorizable reviewerAuthorizable = userManager.getAuthorizable(reviewerId);

if(!requesterAuthorizable.hasProperty(PROFILE_EMAIL) ||
!reviewerAuthorizable.hasProperty(PROFILE_EMAIL)) {
throw new RepositoryException("Mails could not be obtained");
}else {
String requesterMail = requesterAuthorizable.getProperty(PROFILE_EMAIL)[0].getString();
String reviewerMail = reviewerAuthorizable.getProperty(PROFILE_EMAIL)[0].getString();

principals.put(REVIEWER, reviewerMail);
principals.put(REQUESTER, requesterMail);
}
return principals;
}

private static Map<String, String> getEmailDetails(MetaDataMap data){
final Map<String, String> result = new HashMap<>();
for(Map.Entry<String, Object> elem : data.entrySet()){
String elemKey = elem.getKey();
if(!elemKey.contains(DEPENDENCY)) {
result.put(elemKey, elem.getValue().toString());
}
}
return result;
}

private static String getLastStepComment(WorkflowSession workflowSession, Workflow workflow){
try {
List<HistoryItem> historyItems = workflowSession.getHistory(workflow);
if(!historyItems.isEmpty()) {
final String lastComment = historyItems.get(historyItems.size() - 1)
.getComment();
if(!lastComment.isEmpty()) {
return lastComment;
}
}
} catch (WorkflowException e) {
logger.debug(LOG_ERR, e);
}
return "No comment provided";
}
}



/**
* Assigns the workflow to REVIEWER
*/
@Component(
service = ParticipantStepChooser.class,
immediate = true,
property = {
ParticipantStepChooser.SERVICE_PROPERTY_LABEL + "=Assign workflow to reviewer"}
)
public class DynamicallyParticipantChooserReviewer implements ParticipantStepChooser {
private static final ESAPILogger logger = ESAPILogger.getLogger(DynamicallyParticipantChooserReviewer.class);
/**
* Assign the workflow to the determined user
* @param workItem
* @param workflowSession
* @param metaDataMap
* @return the found user that needs to get the workflow
*/
@Override
public String getParticipant(WorkItem workItem,
WorkflowSession workflowSession,
MetaDataMap metaDataMap) {
final String reviewer = workItem.getWorkflow()
.getMetaDataMap().get(Constants.REVIEWER.value(), String.class);
if(reviewer != null) {
logger.debug("Workflow assigned to {}", reviewer);
return reviewer;
}else{
logger.debug("Reviewer could not be set");
return null;
}
}
}





 

 

Avatar

Employee Advisor

Reviewers group detail like members and permissions-

 

DEBAL_DAS_0-1643724766157.png

 

DEBAL_DAS_1-1643724797307.png

 

DEBAL_DAS_2-1643724809334.png

DEBAL_DAS_3-1643724821394.png

 

I have created this sample user group just to understand your requirement.

Avatar

Employee Advisor

 I believe WorkflowUtility is your project specific utility (OSGi service), that's why  I am unable to use your process step.

Avatar

Level 2

@DEBAL_DAS I found the problem.

The problem was that I did not used the right group.

The group that I was using was created before me, so I did not payed much attention there.It seems that the users group was composed of "workflow-administrators" group (which grants access to all active workflows - basically as workflow-administrators group you can control all active workflows on the instance).

The solution was to remove this group from the costum created group, and add "workflow-users" group instead (this group permits user to control all active workflows assigned to it)

Thanks a lot guys for helping me!