Goal
If AEM Inbox (http://localhost:4502/aem/inbox) is not enough for the customer's Task Management needs and would like to integrate with a more complex Task management system like Atlassian JIRA you can try the following solution..
Here the scenario is, gather a team (years of experience using JIRA) for review, approve and publish a bunch of Sites pages. The users are good with JIRA and would like to continue using JIRA, rather than getting trained in a new task management software (AEM Inbox) for assigning and completing the respective tasks.
The solution discussed below uses AEM Projects and Workflow processes for setting up a team, assign tasks for approving and publishing the pages using otb Request for Activation workflow
Check this Adobe tutorial for creating projects
For downloading jira check this page, and this one for REST api
Demo | Package Install | Github
AEM JIRA Integration Project Setup & Execution
1) Select the template Experience AEM Approve Pages and Assets Project (available in Package Install) to create a new project for reviewing the pages
2) Add the users (team) responsible for completing the review and publish page task
3) In the Workflows pod click on Start Workflow (project template is setup with workflow models for projects created using this template)
4) Select the workflow Experience AEM Request for Activation
5) Select the page path and user responsible for reviewing, approving and publishing the page (steps in workflow process)
6) A Task is created in JIRA and assigned to the respective user (assuming the user has same id in AEM and JIRA; additionally a task is created in AEM Inbox). The expectation is, user clicks on AEM page link in ticket description, publishes the page and resolves ticket in JIRA....
Solution
1) Create the AEM project template Experience AEM Approve Pages and Assets Project /apps/eaem-review-assets-jira-tasks/projects/templates/approve-assets-project (available in Package Install) with the pods for Team, Tasks, Workflows, Project Info...
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:description="A project to approve and publish Assets" jcr:primaryType="cq:Template" jcr:title="Experience AEM Approve Pages and Assets Project" ranking="{Long}1" wizard="/libs/cq/core/content/projects/wizard/steps/defaultproject.html"> <jcr:content jcr:primaryType="nt:unstructured" detailsHref="/projects/details.html"/> <gadgets jcr:primaryType="nt:unstructured"> <team jcr:primaryType="nt:unstructured" jcr:title="Team" sling:resourceType="cq/gui/components/projects/admin/pod/teampod" cardWeight="60"/> <tasks jcr:primaryType="nt:unstructured" jcr:title="Tasks" sling:resourceType="cq/gui/components/projects/admin/pod/taskpod" cardWeight="100"/> <work jcr:primaryType="nt:unstructured" jcr:title="Workflows" sling:resourceType="cq/gui/components/projects/admin/pod/workpod" cardWeight="80"/> <projectinfo jcr:primaryType="nt:unstructured" jcr:title="Project Info" sling:resourceType="cq/gui/components/projects/admin/pod/projectinfopod" cardWeight="100"/> </gadgets> <roles jcr:primaryType="nt:unstructured"> <approvers jcr:primaryType="nt:unstructured" jcr:title="EAEM Approvers" roleclass="owner" roleid="approvers"/> </roles> <workflows jcr:primaryType="nt:unstructured" tags="[]"> <models jcr:primaryType="nt:unstructured"> <assets-approval jcr:primaryType="nt:unstructured" modelId="/var/workflow/models/request_for_activation" wizard="/apps/eaem-review-assets-jira-tasks/projects/wizards/assets-approval-start.html"/> </models> </workflows> </jcr:root>
2) Line #46 with workflow model /var/workflow/models/request_for_activation specifies projects created using this template have access to workflow Experience AEM Request for Activation from the workflow pod
3) The workflow wizard /apps/eaem-review-assets-jira-tasks/projects/wizards/assets-approval-start.html in Line # 47 provides the necessary form for entering workflow specific information like payload, assignee...
4) Create a workflow process step apps.experienceaem.assets.EAEMJiraWFProcess for executing REST calls and create tasks / tickets in JIRA (in the demo a JIRA instance is running locally on http://localhost:8080)
package apps.experienceaem.assets; import com.day.cq.commons.Externalizer; import com.day.cq.wcm.api.Page; import com.day.cq.workflow.WorkflowException; import com.day.cq.workflow.WorkflowSession; import com.day.cq.workflow.exec.WorkItem; import com.day.cq.workflow.exec.WorkflowData; import com.day.cq.workflow.exec.WorkflowProcess; import com.day.cq.workflow.metadata.MetaDataMap; import org.apache.commons.codec.binary.Base64; import org.apache.http.Header; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpHeaders; import org.apache.http.message.BasicHeader; import org.apache.sling.jcr.resource.api.JcrResourceConstants; import org.json.JSONObject; import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; import org.osgi.service.metatype.annotations.AttributeDefinition; import org.osgi.service.metatype.annotations.AttributeType; import org.osgi.service.metatype.annotations.Designate; import org.osgi.service.metatype.annotations.ObjectClassDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.config.SocketConfig; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import javax.jcr.Node; import javax.jcr.Session; import java.nio.charset.StandardCharsets; import java.util.Collections; @Component( immediate = true, service = {WorkflowProcess.class}, property = { "process.label = Experience AEM JIRA Integration Workflow Step" } ) @Designate(ocd = EAEMJiraWFProcess.Configuration.class) public class EAEMJiraWFProcess implements WorkflowProcess { private static final Logger log = LoggerFactory.getLogger(EAEMJiraWFProcess.class); private static final String JIRA_REST_API = "http://localhost:8080/rest/api/2/issue/"; @Reference private ResourceResolverFactory resourceResolverFactory; @Reference private Externalizer externalizer; private String jiraProjectKey , jiraUserName, jiraPassword; @Activate protected void activate(EAEMJiraWFProcess.Configuration configuration) { jiraProjectKey = configuration.jiraProjectKey(); jiraUserName = configuration.jiraUserName(); jiraPassword = configuration.jiraPassword(); } public void execute(WorkItem workItem, WorkflowSession wfSession, MetaDataMap mapArgs) throws WorkflowException { try { if(StringUtils.isEmpty(jiraProjectKey) || StringUtils.isEmpty(jiraPassword) || StringUtils.isEmpty(jiraUserName)){ throw new RuntimeException("Required jira details missing from configuration jiraProjectKey, jiraUserName, jiraPassword"); } Session session = wfSession.getSession(); WorkflowData wfData = workItem.getWorkflowData(); String pagePath = null; String payLoadType = wfData.getPayloadType(); if(payLoadType.equals("JCR_PATH") && wfData.getPayload() != null) { if(session.itemExists((String)wfData.getPayload())) { pagePath = (String)wfData.getPayload(); } } else if( (wfData.getPayload() != null) && payLoadType.equals("JCR_UUID")) { Node metaDataMap = session.getNodeByUUID((String)wfData.getPayload()); pagePath = metaDataMap.getPath(); } if(StringUtils.isEmpty(pagePath)){ log.warn("Page path - " + wfData.getPayload() + ", does not exist"); return; } String assignee = (String)workItem.getWorkflow().getWorkflowData().getMetaDataMap().get("assignee").toString(); ResourceResolver resolver = getResourceResolver(session); Resource pageResource = resolver.getResource(pagePath); createTicket(createTicketBody(resolver, pageResource.adaptTo(Page.class), assignee)); } catch (Exception e) { log.error("Error while creating JIRA ticket", e); } } private String createTicketBody(ResourceResolver resolver, Page page, String user) throws Exception{ JSONObject request = new JSONObject(); JSONObject fields = new JSONObject(); JSONObject project = new JSONObject(); JSONObject issueType = new JSONObject(); String authorLink = externalizer.externalLink(resolver, Externalizer.AUTHOR, "/editor.html" + page.getPath() + ".html"); project.put("key", jiraProjectKey); issueType.put("name", "Task"); fields.put("project", project); fields.put("issuetype", issueType); fields.put("summary", "Publish page : " + page.getTitle()); fields.put("description", "Review Approve and Publish page : " + page.getTitle() + "\n\n" + authorLink); if(StringUtils.isNotEmpty(user)){ JSONObject assignee = new JSONObject(); assignee.put("name", user); fields.put("assignee", assignee); } request.put("fields", fields); return request.toString(); } private void createTicket(String requestBody) throws Exception{ CloseableHttpClient client = null; String responseBody = ""; try { SocketConfig sc = SocketConfig.custom().setSoTimeout(180000).build(); client = HttpClients.custom().setDefaultSocketConfig(sc).build(); HttpPost post = new HttpPost(JIRA_REST_API); StringEntity entity = new StringEntity(requestBody, "UTF-8"); post.addHeader("Content-Type", "application/json"); post.setEntity(entity); post.setHeader(getAuthorizationHeader()); HttpResponse response = client.execute(post); HttpEntity responseEntity = response.getEntity(); responseBody = IOUtils.toString(responseEntity.getContent(), "UTF-8"); log.debug("JIRA create ticket response - " + responseBody); }finally{ if(client != null){ client.close(); } } } private Header getAuthorizationHeader(){ String auth = jiraUserName + ":" + jiraPassword; byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.ISO_8859_1)); String authHeader = "Basic " + new String(encodedAuth); return new BasicHeader(HttpHeaders.AUTHORIZATION, authHeader); } private ResourceResolver getResourceResolver(final Session session) throws LoginException { return resourceResolverFactory.getResourceResolver( Collections.<String, Object> singletonMap(JcrResourceConstants.AUTHENTICATION_INFO_SESSION, session)); } @ObjectClassDefinition( name = "Experience AEM JIRA Integration", description = "Experience AEM JIRA Integration" ) public @interface Configuration { @AttributeDefinition( name = "JIRA Project key", description = "JIRA project key found in http://<jira server>/secure/BrowseProjects.jspa?selectedCategory=all&selectedProjectType=all", type = AttributeType.STRING ) String jiraProjectKey() default ""; @AttributeDefinition( name = "JIRA User name", description = "JIRA User name", type = AttributeType.STRING ) String jiraUserName() default ""; @AttributeDefinition( name = "JIRA Password", description = "JIRA Password", type = AttributeType.PASSWORD ) String jiraPassword() default ""; } }
5) Add a workflow step Experience AEM - Create Ticket in JIRA with above process in the workflow Experience AEM Request for Activation
http://localhost:4502/editor.html/conf/global/settings/workflow/models/request_for_activation.html
6) Add the JIRA Project key, admin Username and Password in workflow step OSGI configuration
http://localhost:4502/system/console/configMgr/apps.experienceaem.assets.EAEMJiraWFProcess
No comments:
Post a Comment