This site contains the documentation that is relevant to older WSO2 product versions and offerings.
For the latest WSO2 documentation, visit https://wso2.com/documentation/.
Ticket Management In Sirportly
The second use case in the Sirportly business scenario is ticket management. This page describes the relevant tasks and the operations you use in the Sirportly connector and the other ESB connectors. It contains the following sections:
Overview
The flow for ticket management is illustrated in the following diagram. The ESB connectors for Mandrill and Cashboard will be used to connect to each service.
Assumption
All the projects and its members in Cashboard are available in Sirportly.
Prerequisites
Update the ticket with relevant information. Such as: project, billable or not and the total estimation. This is a manual process.  Project, Estimates, isBillable and Tasks are custom fields in Sirportly. Â
Creating project list and estimates
- Retrieve all the unresolved ticket details on a daily basis from the Sirportly API using the listTicketsByFilter operation.
- Retrieve the employee details from the Cashboard API using the listEmployees operation.
- Retrieve all the contact details from the Cashboard API using the listClientContacts operation.
- Retrieve details of the ticket from the Sirportly API using the getTicket operation.
- Retrieve the user's e-mail address from Sirportly API using the getUser operation.
- Create the employee in the Cashboard API using the createEmployee operation. If the employee is already created under the same e-mail, this will skipped.
- Create the contact in the Cashboard API using the createClientContact operation. If the employee is already created under the same e-mail, this will skipped.
- if the client company exists, then add the client to the company using the addClientToCompany operation in the Cashboard API.
- Create a project list for the ticket in the Cashboard API using the createProjectList operation. If the creation of the project list is successful, then add a task for the newly created list in the Cashboard API using the createLineItem operation.
- Once the task is added to the project list then create an estimation for the project list for the client using the createEstimate operation.
Sirportly operations
Cashboard operations
- listClientContacts
- createEmployee
- createClientContact
- addClientToCompany
- createProjectList
- createLineItem
Prerequisites
- A filter is needed to retrieve unresolved tickets that are created in the current date.
- Filters can be created by following 'Admin'-->'Ticket Settings'-->'Filters'
- Filter must match all the following conditions:
Status -> is not -> Resolved
Last post time(range) -> within -> Today
Brand is <Brand of the company>
Department is <Brand of the company - Helpdesk Support>
Samples
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!--This proxy will create the unresolved tickets of Sirportly in Cashboard as projectLists in a daily basis, add task to the estimate and create initial estimate for the client. --> <proxy xmlns="http://ws.apache.org/ns/synapse" name="sirportly_createProjectListAndEstimates" transports="https" statistics="disable" trace="disable" startOnLoad="true"> <target> <inSequence> <!-- Sirportly Properties. --> <property name="sirportly.apiUrl" expression="json-eval($.sirportly.apiUrl)" /> <property name="sirportly.apiToken" expression="json-eval($.sirportly.apiToken)" /> <property name="sirportly.apiSecret" expression="json-eval($.sirportly.apiSecret)" /> <property name="sirportly.ticketsFilterName" expression="json-eval($.sirportly.ticketsFilterName)" /> <!-- Cashboard Properties. --> <property name="cashboard.apiUrl" expression="json-eval($.cashboard.apiUrl)" /> <property name="cashboard.emailAddress" expression="json-eval($.cashboard.emailAddress)" /> <property name="cashboard.password" expression="json-eval($.cashboard.password)" /> <property name="cashboard.subdomain" expression="json-eval($.cashboard.subdomain)" /> <!-- Operation scoped properties. --> <property name="id.empty" value="{}" /> <property name="responseString" value="" scope="operation" /> <!--Retrieve all the unresolved tickets created/modified on the present day of execution which belongs to the specific Department of a specific Brand. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.listTicketsByFilter> <filter>{$ctx:sirportly.ticketsFilterName}</filter> </sirportly.listTicketsByFilter> <!--Checking the availability of records. --> <property name="sirportly.records" expression="//pagination/total_records/text()" /> <!--Case Error: If there are no any records then handle the failure case.--> <filter source="boolean(get-property('sirportly.records'))" regex="false"> <then> <property name="sirportly.errorResponse" expression="json-eval($)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_listTicketsByFilter" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:sirportly.errorResponse}" /> </call-template> <loopback /> </then> <else> <!--Getting the number of records retrieved. --> <property name="sirportly.recordsCount" expression="count(//records)" scope="operation" /> <property name="sirportly.ticketRecords" expression="//records" scope="operation" /> <property name="sirportly.recordsIndex" expression="0" scope="operation" /> <!--Case Error: If the records count is zero then handle the failure case.--> <filter source="get-property('operation', 'sirportly.recordsCount')" regex="0.0"> <then> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_listTicketsByFilter" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="There are no tickets to process the project list creation." /> </call-template> <loopback /> </then> <else> <!--Call the sequence in order to construct a JSON object containing client company details(store clientCompanyName against clientCompanyId).--> <sequence key="mapClientCompanyDetails" /> <!--Construct a JSON object containing all the employee details(store employeeEmailAddress against employeeId). --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.listEmployees /> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!--Checking the existence of employees --> <property name="cashboard.responseEmployees" expression="//employees" /> <!--Case Skipped: If there occurred an error while listing the employees then handle the error scenario.--> <filter source="boolean(get-property('cashboard.responseEmployees'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="cashboard.httpStatusCode" expression="$axis2:HTTP_SC" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listEmployees" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> <!--If there occurs an authentication failure then loopback.--> <filter source="get-property('cashboard.httpStatusCode')" regex="401"> <then> <loopback /> </then> </filter><!--END of Filter: If there occurs an authentication failure then loopback.--> </then> <else> <property name="messageType" value="application/json" scope="axis2" /> <!--Getting the number of employees retrieved. --> <property name="cashboard.employeeArray" expression="json-eval($.employees.employee)" /> <script language="js"><![CDATA[ //script to construct the JSON object to map all the available employeeEmail against their IDs. var employeeArray= eval("(" + mc.getProperty('cashboard.employeeArray') + ")"); var employeeObject={}; var employeeEmail=""; var employeeId=""; for(i=0; i<employeeArray.length ; i++){ employeeEmail=employeeArray[i].email_address; employeeId= '' + employeeArray[i].id; employeeId=employeeId.split(".")[0]; employeeObject[employeeEmail]=employeeId; } mc.setPayloadJSON(employeeObject); ]]></script> <property name="cashboardEmployeeObject" expression="json-eval($)" /> </else> </filter><!--END of Filter: If there occurred an error while listing the employees then handle the error scenario.--> <!--Constructing a json object containing all the contact details(store contactEmails against contactIds).--> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.listClientContacts /> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!--Checking the existence of client contacts. --> <property name="cashboard.responseClientContacts" expression="//client_contacts" /> <!--Case Error: If there occurred an error while listing the contacts then handle the error scenario.--> <filter source="boolean(get-property('cashboard.responseClientContacts'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listClientContacts" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <!--Getting the number of contacts retrieved. --> <property name="messageType" value="application/json" scope="axis2" /> <property name="cashboard.clientContactArray" expression="json-eval($.client_contacts.client_contact)" /> <script language="js"><![CDATA[ //script to construct the JSON object to map all the available contactEmail against their IDs. var clientContactArray= eval("(" + mc.getProperty('cashboard.clientContactArray') + ")"); var clientContactObject={}; var clientContactEmail=""; var clientContactId=""; for(i=0; i<clientContactArray.length ; i++){ clientContactEmail=clientContactArray[i].email_address; clientContactId= '' + clientContactArray[i].id; clientContactId=clientContactId.split(".")[0]; clientContactObject[clientContactEmail]=clientContactId; } mc.setPayloadJSON(clientContactObject); ]]></script> <property name="cashboardClientContactObject" expression="json-eval($)" /> </else> </filter><!--END of Filter: If there occurred an error while listing the contacts then handle the error scenario.--> <!--Retrieve all the unresolved tickets of the day which belongs to the specific Department of the Brand. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.listTicketsByFilter> <filter>{$ctx:sirportly.ticketsFilterName}</filter> </sirportly.listTicketsByFilter> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!--START LOOP: FOR EACH ticket. --> <iterate continueParent="false" id="ticketIterator" expression="//records" sequential="true"> <target> <sequence> <!--Retrieve the ticket reference. --> <property name="sirportly.ticketReference" expression="//records/reference/text()" /> <property name="sirportly.ticketsFilterName" action="remove" /> <!--Delaying each iteration by 5 seconds to avoid duplicate creation of employees with the same email. --> <script language="js"><![CDATA[ java.lang.Thread.sleep(5000); ]]></script> <!--Get more details of the ticket --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.getTicket> <ticketReference>{$ctx:sirportly.ticketReference}</ticketReference> </sirportly.getTicket> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="sirportly.responseId" expression="json-eval($.id)" /> <!--Case Skipped: If there occurred an error while retrieving the specific ticket details then handle the error scenario.--> <filter source="boolean(get-property('sirportly.responseId'))" regex="false"> <then> <property name="sirportly.errorResponse" expression="json-eval($)" /> <property name="id" expression="fn:concat('sirportly_ticketReference:',get-property('sirportly.ticketReference'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="{$ctx:sirportly.errorResponse}" /> </call-template> </then> <else> <!--Retrieve ticket details.--> <property name="sirportly.ticketSubject" expression="json-eval($.subject)" /> <property name="sirportly.cashboardProjectId" expression="json-eval($.custom_fields.Cashboard-projectId)" /> <!--Retrieve details about the contact of the ticket.--> <property name="sirportly.contactId" expression="json-eval($.contact.id)" /> <property name="sirportly.contactName" expression="json-eval($.contact.name)" /> <property name="sirportly.contactCompany" expression="json-eval($.contact.company)" /> <property name="sirportly.contactEmailAddress" expression="json-eval($.contact_method.data)" /> <!--Retrieve details about assignee of the ticket. --> <property name="sirportly.assigneeId" expression="json-eval($.user.id)" /> <property name="sirportly.assigneeFirstName" expression="json-eval($.user.first_name)" /> <property name="sirportly.assigneeLastName" expression="json-eval($.user.last_name)" /> <!--Retrieve the user's email address. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.getUser> <user>{$ctx:sirportly.assigneeId}</user> </sirportly.getUser> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="sirportly.responseId" expression="json-eval($.id)" /> <!--Case Skipped: If there occurred an error while retrieving the specific user(assignee) details then handle the error scenario.--> <filter source="boolean(get-property('sirportly.responseId'))" regex="false"> <then> <property name="sirportly.errorResponse" expression="json-eval($)" /> <property name="id" expression="fn:concat('sirportly_assigneeId:',get-property('sirportly.assigneeId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getUser" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="{$ctx:sirportly.errorResponse}" /> </call-template> </then> <else> <!--Retrieve the email address of the assignee. --> <property name="sirportly.assigneeEmail" expression="json-eval($.email_address)" /> <property name="cashboard.customField1" expression="fn:concat('Sirportly_AssigneeId-',get-property('sirportly.assigneeId'))" /> <!--Check for the existence of the employee in Cashboard --> <script language="js"><![CDATA[ //script that checks the employeeID of an existing employee. var assigneeEmail=mc.getProperty('sirportly.assigneeEmail'); var cashboardEmployeeObject= eval("(" + mc.getProperty('cashboardEmployeeObject') + ")"); var employeeId=""; if(cashboardEmployeeObject.hasOwnProperty(assigneeEmail)){ employeeId=cashboardEmployeeObject[assigneeEmail]; } mc.setProperty('cashboard.existingEmployeeId',employeeId); ]]></script> <!--If the employee exists then get the employeeId else create a new employee. --> <filter source="boolean(get-property('cashboard.existingEmployeeId'))" regex="true"> <then> <property name="cashboardEmployeeId" expression="get-property('cashboard.existingEmployeeId')" /> </then> <else> <!--Create the employee in Cashboard (if the employee is already created under the same email, this will be automatically skipped). --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.createEmployee> <firstName>{$ctx:sirportly.assigneeFirstName}</firstName> <lastName>{$ctx:sirportly.assigneeLastName}</lastName> <employeeEmailAddress>{$ctx:sirportly.assigneeEmail}</employeeEmailAddress> <custom1>{$ctx:cashboard.customField1}</custom1> <format>json</format> </cashboard.createEmployee> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="cashboardEmployeeId" expression="//employee/id/text()" /> <!--Case Skipped: If there occurred an error while creating the employee then handle the error scenario.--> <filter source="boolean(get-property('cashboardEmployeeId'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="id" expression="fn:concat('sirportly_assigneeId:',get-property('sirportly.assigneeId'))" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createEmployee" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <property name="id" expression="fn:concat('cashboard_employeeId:',get-property('cashboardEmployeeId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createEmployee" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Success" /> <with-param name="message" value="Employee is created successfully." /> </call-template> </else> </filter><!--END of Filter: If there occurred an error while creating the employee then handle the error scenario.--> </else> </filter><!--END of Filter: If the employee exists then get the employeeId else create a new employee. --> </else> </filter><!--END of Filter: If there occurred an error while retrieving the specific user(assignee) details then handle the error scenario.--> <!--Check for the existence of the client contact in Cashboard. --> <script language="js"><![CDATA[ //script that checks the contactID of an existing contact. var contactEmailAddress=mc.getProperty('sirportly.contactEmailAddress'); var cashboardClientContactObject= eval("(" + mc.getProperty('cashboardClientContactObject') + ")"); var contactId=""; if(cashboardClientContactObject.hasOwnProperty(contactEmailAddress)){ contactId=cashboardClientContactObject[contactEmailAddress]; } mc.setProperty('cashboard.existingContactId',contactId); ]]></script> <!--If the contact already exist then get the contactId else create a new contact. --> <filter source="boolean(get-property('cashboard.existingContactId'))" regex="true"> <then> <property name="cashboardContactId" expression="get-property('cashboard.existingContactId')" /> <property name="cashboardContactId>>>" expression="get-property('cashboardContactId')" /> </then> <else> <property name="cashboard.contactCustomField1" expression="fn:concat('Sirportly_ContactId-',get-property('sirportly.contactId'))" /> <property name="uri.var.lastName" action="remove" /> <!--Create the contact in Cashboard (if the contact is already created under the same email, this will be automatically skipped).--> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.createClientContact> <clientEmailAddress>{$ctx:sirportly.contactEmailAddress}</clientEmailAddress> <firstName>{$ctx:sirportly.contactName}</firstName> <custom1>{$ctx:cashboard.contactCustomField1}</custom1> </cashboard.createClientContact> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="cashboardContactId" expression="//client_contact/id/text()" /> <!--Case Error: If there occurred an error while creating the employee then handle the error scenario.--> <filter source="boolean(get-property('cashboardContactId'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="id" expression="fn:concat('sirportly_contactId:',get-property('sirportly.contactId'))" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <property name="id" expression="fn:concat('cashboard_contactId:',get-property('cashboardContactId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Success" /> <with-param name="message" value="Contact is created successfully." /> </call-template> <!--If creation of the contact is successful then add the client to the company in Cashboard.--> <script language="js"><![CDATA[ //The script retrieves the ID of the client company in Cashboard to which the client is belong to. var companyName=mc.getProperty('sirportly.contactCompany'); var cashboardCompaniesObject= eval("(" + mc.getProperty('companiesObject') + ")"); var companyId=""; if(cashboardCompaniesObject.hasOwnProperty(companyName)){ companyId=cashboardCompaniesObject[companyName]; } mc.setProperty('cashboard.existingCompanyId',companyId); ]]></script> <!--If the Client Company exist then add the client to the company. --> <filter source="boolean(get-property('cashboard.existingCompanyId'))" regex="true"> <then> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.addClientToCompany> <personId>{$ctx:cashboardContactId}</personId> <companyId>{$ctx:cashboard.existingCompanyId}</companyId> </cashboard.addClientToCompany> <property name="cashboard.responseAddClientToCompany" expression="//company_membership" /> <!--Case Skipped: If the client is added to the company successfully then display custom success message.--> <filter source="boolean(get-property('cashboard.responseAddClientToCompany'))" regex="false"> <then> <property name="id" expression="fn:concat('cashboard_clientId:',get-property('cashboardContactId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="The client is created successfully. However it is failed to add the client to the relevant company." /> </call-template> </then> </filter><!--END of Filter: If the client is added to the company successfully then display custom success message.--> </then> </filter><!--END of Filter: If the Client Company exist then add the client to the company. --> </else> </filter><!--END of Filter: If there occurred an error while creating the employee then handle the error scenario.--> </else> </filter><!--END of Filter: If the contact already exist then get the contactId else create a new contact. --> <!--Constructing the title for the project List (<sirportly.ticketReference>_<sirportly.ticketSubject>). --> <property name="cashboard.projectListTitle" expression="fn:concat(get-property('sirportly.ticketReference'),'_',get-property('sirportly.ticketSubject'))" /> <!--Create a projectList for the ticket in Cashboard.--> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.createProjectList> <personId>{$ctx:cashboardContactId}</personId> <title>{$ctx:cashboard.projectListTitle}</title> <projectId>{$ctx:sirportly.cashboardProjectId}</projectId> </cashboard.createProjectList> <!--Removing unused headers --> <sequence key="removeResponseHeaders" /> <property name="cashboard.projectListId" expression="//project_list/id/text()" /> <!--Case Error: If there occurred an error while creating the projectList then handle the error scenario.--> <filter source="boolean(get-property('cashboard.projectListId'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="id" expression="fn:concat('sirportly_ticketReference:',get-property('sirportly.ticketReference'))" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createProjectList" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'),',cashboard_projectListId:',get-property('cashboard.projectListId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createProjectList" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Success" /> <with-param name="message" value="Project list is created successfully for the ticket." /> </call-template> <!--Constructing the description for the task.--> <property name="cashboard.description" expression="fn:concat('This task will hold all the updates about the Sirportly ticket ',get-property('sirportly.ticketReference')) " /> <!--Constructing the title for the project task.--> <property name="cashboard.taskTitle" value="Workshop Operations" /> <!--If creation of the projectList is successful then add a task to it titled as 'Workshop Operations'. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.createLineItem> <description>{$ctx:description}</description> <personId>{$ctx:cashboardEmployeeId}</personId> <projectId>{$ctx:sirportly.cashboardProjectId}</projectId> <projectListId>{$ctx:cashboard.projectListId}</projectListId> <title>{$ctx:cashboard.taskTitle}</title> </cashboard.createLineItem> <!--Removing unused headers --> <sequence key="removeResponseHeaders" /> <property name="cashboard.lineItemId" expression="//line_item/id/text()" /> <!--Case Skipped: If there occurred an error while creating the lineItem then handle the error scenario.--> <filter source="boolean(get-property('cashboard.lineItemId'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="id" expression="fn:concat('cashboard_projectListId:',get-property('cashboard.projectListId'))" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createLineItem" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <property name="id" expression="fn:concat('cashboard_projectListLineItemId:',get-property('cashboard.lineItemId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createLineItem" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Success" /> <with-param name="message" value="Task is added and employee is assigned to the project list successfully." /> </call-template> <!--Constructing the Estimation name.--> <property name="cashboard.estimationName" expression="fn:concat(get-property('cashboard.projectListId'),'_',get-property('cashboard.projectListTitle'))" /> <!--If the task is added to the project list then create an estimation for the projectList for the client.--> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.createEstimate> <assignedId>{$ctx:cashboard.projectListId}</assignedId> <clientId>{$ctx:cashboardContactId}</clientId> <clientType>Person</clientType> <isSent>false</isSent> <isArchived>false</isArchived> <name>{$ctx:cashboard.estimationName}</name> </cashboard.createEstimate> <!--Removing unused headers --> <sequence key="removeResponseHeaders" /> <property name="cashboard.estimateId" expression="//estimate/id/text()" /> <!--Case Skipped: If there occurred an error while creating the estimation then handle the error scenario.--> <filter source="boolean(get-property('cashboard.estimateId'))" regex="false"> <then> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <property name="id" expression="fn:concat('cashboard_projectListId:',get-property('cashboard.projectListId'))" /> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorResponse" expression="get-property('decodedResult')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createEstimate" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="{$ctx:cashboard.errorResponse}" /> </call-template> </then> <else> <property name="id" expression="fn:concat('cashboard_estimateId:',get-property('cashboard.estimateId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_createEstimate" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Success" /> <with-param name="message" value="Estimate is created for the client of the project list successfully." /> </call-template> </else> </filter><!--END of Filter: If there occurred an error while creating the estimation then handle the error scenario.--> </else> </filter><!--END of Filter: If there occurred an error while creating the lineItem then handle the error scenario.--> </else> </filter><!--END of Filter: If there occurred an error while creating the projectList then handle the error scenario.--> </else> </filter><!--END of Filter: If there occurred an error while retrieving the specific ticket details then handle the error scenario.--> <property name="sirportly.recordsIndex" expression="get-property('operation', 'sirportly.recordsIndex') + 1" scope="operation" /> <!--FOR EACH Case : END --> <filter xpath="get-property('operation', 'sirportly.recordsCount') = get-property('operation', 'sirportly.recordsIndex')"> <then> <loopback /> </then> </filter> </sequence> </target> </iterate><!--END LOOP: Retrieve details of each ticket array. --> </else> </filter><!--END of Filter: If the records count is zero then handle the failure case.--> </else> </filter><!--END of Filter: If there are no any records then handle the failure case.--> </inSequence> <outSequence> <!-- Send the constructed response to the user. --> <payloadFactory media-type="json"> <format>{ "Response":{ "process":"sirportly_createProjectListAndEstimates", "activityResponse": [$1] } }</format> <args> <arg expression="get-property('operation', 'responseString')" /> </args> </payloadFactory> <send /> </outSequence> </target> </proxy>
{ "sirportly":{ "apiUrl" : "https://barns-electronics-lanka.sirportly.com", "apiToken" : "acc2ce4a-031e-22ec-da45-9ed3d8d4", "apiSecret" : "s0dcji1213zc6zmobyyaww1itzsnzgpgqegwcm057ytfp", "ticketsFilterName":"Daily Unresolved Tickets" }, "cashboard":{ "apiUrl" : "https://api.testing.cashboardapp.com", "emailAddress" : "wso2.connector.001@gmail.com", "password" : "1qaz2x@", "subdomain" : "testing" } }
Note
 The following are the parameter descriptions:
The name of the filter which is used to filter the daily unresolved tickets of a specific department of a brand.sirportly.ticketsFilterName:
Â
 Note that all the rest of the parameters are API URLs and authentication credentials to access each API specified in the request. Refer to the individual connector methods for clarification.
Retrieving estimates and notifying clients
- Retrieve estimates from the Cashboard API using the listEstimates operation.
- Retrieve ticket from the Sirportly API using the getTicket operation.
- Retrieve client details related to the estimation from the Cashboard API using the getClientContact operation.
- Retrieve the estimates needed to be sent to the client from the Cashboard API using the getEstimate operation and mail them to the client through the Mandrill API using the sendMessage operation.
- Update the total estimation in the ticket in the Sirportly API using the updateTicket operation.
Sirportly operations
Cashboard operations
Mandrill operations
Prerequisites
- For each client's estimation, estimation line items should be added by the assignee as an offline process.
- Each estimate's 'Requires agreement' field should be checked in order to make it eligible to be sent to the client.
Assumptions
Estimates will be created for all the clients in each project list but only the billable ones and that require client agreement will be sent to the relevant clients.
Samples
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!--This proxy is used to retrieve estimates and mail them to the client through Mandrill. Also retrieves the total estimate for the ticket from Cashboard and create it as total estimate in the Sirportly. --> <proxy xmlns="http://ws.apache.org/ns/synapse" name="sirportly_retrieveEstimatesAndNotifyClients" transports="https" statistics="disable" trace="disable" startOnLoad="true"> <target> <inSequence onError="faultHandlerSeq"> <!-- Cashboard parameters. --> <property name="cashboard.apiUrl" expression="json-eval($.cashboard.apiUrl)" /> <property name="cashboard.password" expression="json-eval($.cashboard.password)" /> <property name="cashboard.subdomain" expression="json-eval($.cashboard.subdomain)" /> <property name="cashboard.emailAddress" expression="json-eval($.cashboard.emailAddress)" /> <property name="cashboard.updatedSince" expression="json-eval($.cashboard.updatedSince)" /> <!-- Mandrill parameters. --> <property name="mandrill.apiUrl" value="https://mandrillapp.com" /> <property name="mandrill.apiKey" expression="json-eval($.mandrill.apiKey)" /> <property name="mandrill.fromEmail" expression="json-eval($.mandrill.fromEmail)" /> <property name="mandrill.fromName" expression="json-eval($.mandrill.fromName)" /> <!-- Sirportly parameters. --> <property name="sirportly.apiUrl" expression="json-eval($.sirportly.apiUrl)" /> <property name="sirportly.apiToken" expression="json-eval($.sirportly.apiToken)" /> <property name="sirportly.apiSecret" expression="json-eval($.sirportly.apiSecret)" /> <!-- Operation scoped properties --> <property name="id.empty" value="{}" /> <property name="responseString" value="" scope="operation" /> <!-- If the value for updateSince parameter is not provided, set the default value as current date --> <script language="js"> <![CDATA[ var requestUpdatedDate = mc.getProperty('cashboard.updatedSince'); if(requestUpdatedDate != null && requestUpdatedDate != "") { mc.setProperty('cashboard.updatedSince', requestUpdatedDate); }else{ mc.setProperty('cashboard.updatedSince', new java.text.SimpleDateFormat("yyyy-MM-dd '00:00:00'").format(new java.util.Date())); } ]]> </script> <!-- List open Estimates daily basis. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.listEstimates> <clientType>Person</clientType> <isArchived>false</isArchived> <updatedSince>{$ctx:cashboard.updatedSince}</updatedSince> </cashboard.listEstimates> <property name="messageType" value="application/xml" scope="axis2" /> <property name="estimatesCount" expression="count(//estimates/estimate)" scope="operation" /> <property name="index" expression="0" scope="operation" /> <!-- Get the response status. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!-- Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- if any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!-- Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <!-- Set error if no estimates retrieved. --> <filter xpath="0 = get-property('operation', 'estimatesCount')"> <then> <property name="message" value="There are no estimates found." /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listEstimates" /> <with-param name="status" value="Skipped" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> <loopback /> </then> </filter><!--END of Filter: Checking the estimation count. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> <!--START LOOP: Retrieve details of each estimate.--> <iterate continueParent="false" id="estimateIterator" expression="//estimates/estimate" sequential="true"> <target> <sequence> <property name="cashboard.estimationId" expression="//estimate/id" /> <property name="cashboard.clientContactId" expression="//estimate/client_id" /> <property name="cashboard.clientAgreement" expression="//estimate/requires_agreement" /> <property name="ticketId" expression="substring-after(//estimate/name/text(),'_')" /> <property name="sirportly.ticketReference" expression="substring-before(get-property('ticketId'),'_')" /> <property name="sirportly.estimatedCost" expression="//estimate/price_best" /> <!-- Get Sirportly ticket. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.getTicket> <ticketReference>{$ctx:sirportly.ticketReference}</ticketReference> </sirportly.getTicket> <property name="sirportly.subject" expression="json-eval($.subject)" /> <property name="sirportly.isBIllable" expression="json-eval($.custom_fields.IsBIllable)" /> <!-- Set error if no ticket retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check for the unsuccessful response status code.--> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'))" /> <property name="message" expression="json-eval($)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <!-- START of Filter: Check the ticket is billable. --> <filter xpath="get-property('sirportly.isBIllable') = 'true'"> <then> <!-- START of Filter: Check the estimate needs customer agreement. --> <filter xpath="get-property('cashboard.clientAgreement') = 'true'"> <then> <!-- Get client details related to the estimation. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.getClientContact> <clientContactId>{$ctx:cashboard.clientContactId}</clientContactId> </cashboard.getClientContact> <!-- Set error if no contact retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!-- START of Filter: Check for the unsuccessful response code. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!--If any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses.--> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_getClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="cashboardClientFirstName" expression="//first_name" /> <property name="cashboardClientContactEmailAddress" expression="//email_address" /> <!-- Get estimation in html version needed to be sent to client. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>html</format> </cashboard.init> <cashboard.getEstimate> <estimateId>{$ctx:cashboard.estimationId}</estimateId> </cashboard.getEstimate> <!-- Set error if no estimation retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!-- START of Filter: Check for the unsuccessful response code. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'))" /> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- If any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses.--> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_getClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="messageType" value="application/xml" scope="axis2" /> <property name="binaryString" expression="json-eval($.binary)" /> <!--Base64Decode and get the JSON object from Base64Encoded String - Issue caused by Content-Type: text/html response header. --> <script language="js"><![CDATA[ function base64_decode(data) { var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = '', tmp_arr = []; if (data.trim().length == 0) { return data; } else { data += ''; } do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(data.charAt(i++)); h2 = b64.indexOf(data.charAt(i++)); h3 = b64.indexOf(data.charAt(i++)); h4 = b64.indexOf(data.charAt(i++)); bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; o1 = bits >> 16 & 0xff; o2 = bits >> 8 & 0xff; o3 = bits & 0xff; if (h3 == 64) { tmp_arr[ac++] = String.fromCharCode(o1); } else if (h4 == 64) { tmp_arr[ac++] = String.fromCharCode(o1, o2); } else { tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); } } while (i < data.length); dec = tmp_arr.join(''); return dec.replace(/\0+$/, ''); } var binaryString = mc.getProperty('binaryString'); var name = mc.getProperty('cashboardClientFirstName'); var email = mc.getProperty('cashboardClientContactEmailAddress'); if(binaryString != null && binaryString != ""){ var htmlContent = base64_decode(binaryString); mc.setProperty('htmlContent', new java.lang.String(htmlContent).replace("\n", "").replace("\r", "").replace("\"", "\\\\\\\"")); } var mailTo = '[{"email":"' + email + '","name":"' + name + '","type": "to"}]'; mc.setProperty('mandrill.to', mailTo); ]]></script> <payloadFactory media-type="json"> <format>{ "html":"$1" } </format> <args> <arg expression="get-property('htmlContent')" /> </args> </payloadFactory> <property name="mandrill.html" expression="json-eval($.html)" /> <property name="mandrill.to" expression="get-property('mandrill.to')" /> <property name="mandrill.subject" expression="fn:concat('Estimation for the ticket you raised under ', get-property('sirportly.ticketReference'))" /> <!--START of Filter: Check weather the Mandril apiKey is sent by the user. --> <filter source="boolean(get-property('mandrill.apiKey'))" regex="true"> <then> <!-- If Mandril apiKey is sent,send the estimation through Mandril. --> <mandrill.init> <apiKey>{$ctx:mandrill.apiKey}</apiKey> <apiUrl>{$ctx:mandrill.apiUrl}</apiUrl> <format>json</format> </mandrill.init> <mandrill.sendMessage> <html>{$ctx:mandrill.html}</html> <subject>{$ctx:mandrill.subject}</subject> <fromEmail>{$ctx:mandrill.fromEmail}</fromEmail> <fromName>{$ctx:mandrill.fromName}</fromName> <to>{$ctx:mandrill.to}</to> </mandrill.sendMessage> <property name="mandrill.messageId" expression="json-eval($[0]._id)" /> <property name="mandrill.subject" action="remove" /> <!-- Set error if estimation not sent. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!-- START of Filter: Check for the unsuccessful response code. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'))" /> <property name="message" expression="json-eval($.message)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <payloadFactory media-type="json"> <format>{ "customFields" : { "custom[totalEstimation]" : "$1" } } </format> <args> <arg expression="get-property('sirportly.estimatedCost')" /> </args> </payloadFactory> <property name="sirportly.customFields" expression="json-eval($.customFields)" /> <!-- Update the total estimation in Sirportly ticket. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.updateTicket> <subject>{$ctx:sirportly.subject}</subject> <ticketReference>{$ctx:sirportly.ticketReference}</ticketReference> <customFields>{$ctx:sirportly.customFields}</customFields> </sirportly.updateTicket> <property name="sirportly.updatedCustomField" expression="json-eval($.custom_fields.totalEstimation)" /> <!--START of Filter: Check weather the total estimation is successfully updated in Sirportly. --> <filter xpath="get-property('sirportly.updatedCustomField') = get-property('sirportly.estimatedCost')"> <then> <!-- Set the success massage after sending the message. --> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'), ',mandrill_messageId:', get-property('mandrill.messageId'))" /> <property name="message" expression="fn:concat('Estimation is sent to ', get-property('cashboardClientFirstName'), ' successfully.')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <!-- Set the success massage in case of failure to update the total estimation. --> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'), ',mandrill_messageId:', get-property('mandrill.messageId'))" /> <property name="message" expression="fn:concat('Estimation is sent to ', get-property('cashboardClientFirstName'), ' successfully however failed to update the total estimation of the ticket')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </else> </filter><!--END of Filter: Check weather the total estimation is successfully updated in Sirportly. --> </else> </filter><!--END of Filter: Check for the unsuccessful response code.--> </then> <else> <!-- Set error if Mandrill apiKey is not sent by the user. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="message" value="Mandrill api key is not provided in the request." /> </call-template> </else> </filter> <!--END of Filter: Check weather the Mandril apiKey is sent by the user.--> </else> </filter><!--END of Filter: Check for the unsuccessful response code. --> </else> </filter><!--END of Filter: Check for the unsuccessful response code. --> </then> <else> <property name="id" expression="fn:concat('cashboard_estimateId:', get-property('cashboard.estimationId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_getEstimate" /> <with-param name="status" value="Skipped" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="Estimation is not ready to send." /> </call-template> </else> </filter><!--END of Filter: Check the estimate needs customer agreement. --> </then> <else> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="status" value="Skipped" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="Ticket is not billable." /> </call-template> </else> </filter><!--END of Filter: Check the ticket is billable. --> </else> </filter><!--END of Filter: Check for the unsuccessful response status code. --> <!--Increment the index count for each iteration. --> <property name="index" expression="get-property('operation','index') + 1" scope="operation" /> </sequence> </target> </iterate><!--END LOOP: Retrieve details of each estimate.--> <!-- Check for all the iterations has completed. --> <filter xpath="get-property('operation', 'index') = get-property('operation', 'estimatesCount')"> <then> <loopback /> </then> </filter><!--END of Filter: Checking all iterations has completed. --> </inSequence> <outSequence> <!-- Send the constructed response to the user. --> <payloadFactory media-type="json"> <format>{ "Response": { "activity":"sirportly_retrieveEstimatesAndNotifyClients", "activityResponse":[$1] } }</format> <args> <arg expression="get-property('operation','responseString')" /> </args> </payloadFactory> <property name="messageType" value="application/json" scope="axis2" /> <send /> </outSequence> </target> <description /> </proxy>
{ "cashboard":{ "apiUrl":"https://api.testdomain.cashboardapp.com", "password":"1qa@", "subdomain":"testdomain", "emailAddress":"testing1@gmail.com", "updatedSince":"2015-06-14 00:00:00" }, "mandrill":{ "apiKey":"489IH-v85O97o8vxwDmDHA", "fromName":"Chamath", "fromEmail":"testing123@gmail.com" }, "sirportly":{ "apiUrl":"https://barns-electronics-lanka.sirportly.com", "apiToken":"acc2ce4a-031e-22ec-da45-9f3ed3d8d", "apiSecret":"s0dcji1213zc6zmobyyawvijetzsnzgpgqegwcm057ytfp" } }
Note
 The following are the parameter descriptions:
This date will be considered to filter the estimates which are last updated after this date and time.Âcashboard.updatedSince:
Â
Follow the format 'YYYY-MM-DD HH:MM:SS'. If this parameter is not set, the case will consider the beginning of the current day's time by default. e.g.:- YYYY-MM-DD 00:00:00
Use a valid name indicating from whom the Mandrill e-mails are required to be sent (Company name of the account can be used here).mandrill.fromName:
Â
Use a valid e-mail address from which the Mandrill e-mails are required to be sent.mandrill.fromEmail:
Â
Â
Updating tickets with comments
- Retrieve all the project lists of the account using the listProjectLists operation in the Cashboard API.
- Retrieve the line items for the project list from the Cashboard API using the listLineItems operation.
- Retrieve line item details for the line Item ID from the Cashboard API using the getLineItem operation.
- Update the ticket with the comments in the Sirportly API using the addContentToTicket operation.
Sirportly operations
Cashboard operations
Prerequisites
- The timeZone settings should set in the Cashboard account having set the account's TimeZone as same as the user's location.
To set the TimeZone follow :
Settings -> Preferences -> TimeZone value : {TimeZone of the users' location}
Samples
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Retrieve comments for tasks from Cashboard API and update the ticket with the comment in Sirportly API. --> <proxy xmlns="http://ws.apache.org/ns/synapse" name="sirportly_updateTicketsWithComments" transports="https" statistics="disable" trace="disable" startOnLoad="true"> <target> <inSequence onError="faultHandlerSeq"> <!-- Sirportly properties. --> <property name="sirportly.apiUrl" expression="json-eval($.sirportly.apiUrl)" /> <property name="sirportly.apiToken" expression="json-eval($.sirportly.apiToken)" /> <property name="sirportly.apiSecret" expression="json-eval($.sirportly.apiSecret)" /> <!-- Cashboard properties. --> <property name="cashboard.apiUrl" expression="json-eval($.cashboard.apiUrl)" /> <property name="cashboard.emailAddress" expression="json-eval($.cashboard.emailAddress)" /> <property name="cashboard.password" expression="json-eval($.cashboard.password)" /> <property name="cashboard.subdomain" expression="json-eval($.cashboard.subdomain)" /> <property name="cashboard.updatedSince" expression="json-eval($.cashboard.updatedSince)" /> <!-- Common properties. --> <property name="id.empty" value="{}" /> <!-- Retrieving all the projectLists of the account. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.listProjectLists> <isArchived>false</isArchived> </cashboard.listProjectLists> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- START: Checks for the successful execution of listProjectLists method. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <filter xpath="get-property('responseStatus') != 200"> <then> <!-- In case an error response returns retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error returns as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <!-- In case any other response returns it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter> <!-- listProjectLists method execution failure, generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="cashboardProjectListCount" expression="count(//project_list)" scope="operation" /> <property name="cashboardProjectListIndex" expression="0" scope="operation" /> <property name="cashboardCommentCount" expression="0" scope="operation" /> <property name="cashboardCommentIndex" expression="0" scope="operation" /> <!-- START: Checks for the successful retrieval of Project Lists. --> <filter source="get-property('operation', 'cashboardProjectListCount')" regex="0.0"> <then> <!-- If the Project Lists are not available for the account, generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Skipped" /> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="message" value="There are no project lists exist to update comments." /> </call-template> <loopback /> </then> <else> <!-- Iterates over Project Lists and proceed to update Tickets in Sirportly. --> <iterate continueParent="false" id="projectListIterator" expression="//project_list" sequential="true"> <target> <sequence> <property name="cashboard.projectListId" expression="//project_list/id/text()" /> <property name="sirportly.ticketReference" expression="substring-before(//project_list/title/text(),'_')" /> <!-- Checks for the successful retrieval of Sirportly ticket reference. --> <filter source="boolean(get-property('sirportly.ticketReference'))" regex="false"> <then> <property name="cashboardProjectListIndex" expression="get-property('operation', 'cashboardProjectListIndex') + 1" scope="operation" /> </then> <else> <script language="js"> //If the value for updateSince parameter is not provided, set the default value as current date. <![CDATA[ var updatedSinceDate = mc.getProperty('cashboard.updatedSince'); if (updatedSinceDate != null && updatedSinceDate != "") { mc.setProperty('cashboard.updatedSince', updatedSinceDate); } else { mc.setProperty('cashboard.updatedSince', new java.text.SimpleDateFormat("yyyy-MM-dd '00:00:00'").format(new java.util.Date())); } ]]> </script> <!-- List the line items for the project list. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.listLineItems> <projectListId>{$ctx:cashboard.projectListId}</projectListId> <updatedSince>{$ctx:cashboard.updatedSince}</updatedSince> </cashboard.listLineItems> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- START: Checks for the successful execution of listLineItems method. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <filter xpath="get-property('responseStatus') != 200"> <then> <!-- In case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!-- In case the error returns as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <!-- if any other response returns it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> <!-- listLineItems method execution failure, generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </filter> </then> <else> <!-- START: Checks for the successful retrieval of Line Item for the Project List. --> <property name="cashboardLineItemsCount" expression="count(//line_item)" scope="operation" /> <filter source="get-property('operation', 'cashboardLineItemsCount') " regex="0.0"> <then> <property name="cashboardProjectListIndex" expression="get-property('operation', 'cashboardProjectListIndex') + 1" scope="operation" /> </then> <else> <property name="cashboard.lineItemId" expression="//line_item/id/text()" /> <!-- Get Line Item details for the lineItemId. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> </cashboard.init> <cashboard.getLineItem> <lineItemId>{$ctx:cashboard.lineItemId}</lineItemId> </cashboard.getLineItem> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- START: Checks for the successful execution of getLineItem method. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <filter xpath="get-property('responseStatus') != 200"> <then> <!-- In case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!-- In case the error returns as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <!-- In case any other response returns it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> <!-- getLineItem method execution failure, generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </filter> </then> <else> <!-- START: Checks for the successful retrieval of Line Item details. --> <property name="cashboardLineItem" expression="//line_item" /> <filter xpath="get-property('cashboardLineItem') ='[]'"> <then> <property name="id" expression="fn:concat('{"cashboard_projectListId":"',get-property('cashboard.projectListId'), '"}')" /> <!-- Failure in retrieving Line Item details, generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="activity" value="cashboard_getLineItemDetails" /> <with-param name="message" value="Failed to retrieve details of the task of the project list." /> </call-template> <property name="cashboardProjectListIndex" expression="get-property('operation', 'cashboardProjectListIndex') + 1" scope="operation" /> </then> <else> <!-- START: Checks for the successful retrieval of comments for the Line Item. --> <property name="cashboardCommentCount" expression="count(//comment)" /> <filter source="get-property('cashboardCommentCount')" regex="0.0"> <then> <property name="cashboardProjectListIndex" expression="get-property('operation', 'cashboardProjectListIndex') + 1" scope="operation" /> </then> <else> <property name="cashboardCommentCount" expression="get-property('operation','cashboardCommentCount') + get-property('cashboardCommentCount')" scope="operation" /> <property name="cashboardProjectListIndex" expression="get-property('operation', 'cashboardProjectListIndex') + 1" scope="operation" /> <!-- Iterates through the comments of a Line Item. --> <iterate continueParent="false" id="commentIterator" expression="//comment" sequential="true"> <target> <sequence> <property name="cashboard.commentId" expression="//comment/id" /> <property name="cashboard.commentUpdatedAt" expression="//comment/updated_at" /> <script language="js"> //Comparing the updateSince parameter with the comment updated time to retrieve only the comments updated after the given time. <![CDATA[ var updatedSince = mc.getProperty('cashboard.updatedSince'); var updatedAt = mc.getProperty('cashboard.commentUpdatedAt'); var formatter = new java.text.SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); var calUpdatedSince = java.util.Calendar.getInstance(); var calUpdatedAt = java.util.Calendar.getInstance(); if (updatedSince != null && updatedSince != '' && updatedAt != null && updatedAt!='') { calUpdatedSince.setTime(formatter.parse(updatedSince)); calUpdatedAt.setTime(formatter.parse(updatedAt)); if (calUpdatedSince.getTime() <= calUpdatedAt.getTime()) { mc.setProperty('isUpdatedSince', "true"); } } ]]> </script> <!-- START: Checks whether the comments updated after the given time. --> <property name="isUpdatedSince" expression="get-property('isUpdatedSince')" /> <filter xpath="boolean(get-property('isUpdatedSince')) = 'true'"> <then> <property name="cashboard.commentContent" expression="//content" /> <!-- Adding retrieved comment content as message of Ticket created in Sirportly. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.addContentToTicket> <ticketReference>{$ctx:sirportly.ticketReference}</ticketReference> <message>{$ctx:cashboard.commentContent}</message> </sirportly.addContentToTicket> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- START: Checks for the successful execution of Sirportly addContentToTicket method. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <filter xpath="get-property('responseStatus') != 201"> <then> <!-- Generate the error response. --> <property name="id" expression="fn:concat('{"cashboard_projectListId":"', get-property('cashboard.projectListId') , '", "sirportly_ticketReference":"', get-property('sirportly.ticketReference') , '"}')" /> <property name="errInfo" expression="json-eval($.error)" /> <property name="message" expression="fn:concat('Error in adding comment to the ticket, Error:',get-property('errInfo'))" /> <property name="status" value="Skipped" /> </then> <else> <!-- Generate the success response. --> <property name="ticketId" expression="json-eval($.id)" /> <property name="id" expression="fn:concat('{"cashboard_projectListId":"', get-property('cashboard.projectListId'),'", "cashboard_commentId":"', get-property('cashboard.commentId'), '", "sirportly_ticketReference":"', get-property('sirportly.ticketReference'), '"}')" /> <property name="status" value="Success" /> <property name="message" value="Comment is added to the ticket successfully." /> </else> </filter><!-- END of filter: Checks for the successful execution of Sirportly addContentToTicket method. --> <!-- Generate the response for execution of addContentToTicket method. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_addCommentToTicket" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="{$ctx:status}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> <property name="cashboardCommentIndex" expression="get-property('operation','cashboardCommentIndex') + 1" scope="operation" /> <filter xpath="(get-property('operation', 'cashboardProjectListCount') = get-property('operation', 'cashboardProjectListIndex')) and (get-property('operation', 'cashboardCommentCount') = get-property('operation', 'cashboardCommentIndex'))"> <then> <filter source="get-property('operation','cashboardCommentIndex')" regex="0.0"> <then> <!-- In case there are no comments found in any of the project lists , generate the error response. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_retrieveComments" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="There are no comments in any of the project lists to sync." /> </call-template> </then> </filter> <loopback /> </then> </filter> </then> <else> <property name="cashboardCommentIndex" expression="get-property('operation','cashboardCommentIndex') + 1" scope="operation" /> </else> </filter><!-- END of filter: Checks whether the comments updated after the given time. --> </sequence> </target> </iterate> <!--END of LOOP: Iterates over comments. --> </else> </filter><!-- END of filter: Checks for the successful retrieval of comments for the Line Item. --> </else> </filter><!-- END of filter: Checks for the successful retrieval of Line Item details. --> </else> </filter> <!-- END of filter: Checks for the successful execution of getLineItem method. --> </else> </filter> <!-- END of filter: Checks for the successful retrieval of Line Item for the Project List. --> </else> </filter><!-- END of filter: Checks for the successful execution of listLineItems method. --> </else> </filter><!-- END of filter: Checks for the successful retrival of Sirportly Ticket reference. --> <!-- Generate an error response in case there are no comments in any of the project lists. --> <filter xpath="(get-property('operation', 'cashboardProjectListCount') = get-property('operation', 'cashboardProjectListIndex')) and (get-property('operation', 'cashboardCommentCount') = get-property('operation', 'cashboardCommentIndex'))"> <then> <filter source="get-property('operation','cashboardCommentIndex')" regex="0.0"> <then> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_retrieveComments" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Skipped" /> <with-param name="message" value="There are no comments in any of the project lists to sync." /> </call-template> </then> </filter> <loopback /> </then> </filter> </sequence> </target> </iterate> <!--END LOOP: Iterates over Project Lists. --> <!--START: Move to outSequence when all the iterations are done. --> <filter xpath="(get-property('operation', 'cashboardProjectListCount') = get-property('operation', 'cashboardProjectListIndex')) and (get-property('operation', 'cashboardCommentCount') = get-property('operation', 'cashboardCommentIndex'))"> <then> <loopback /> </then> </filter> </else> </filter><!-- END of filter: Checks for the successful retrieval of Project Lists. --> </else> </filter><!-- END of filter: Checks for the successful execution of listProjectLists method. --> </inSequence> <outSequence> <!-- Generate the chained response of all the API calls in updateTicketsWithComments. --> <property name="messageType" value="application/json" scope="axis2" /> <payloadFactory media-type="json"> <format> { "Response": { "process": "sirportly_updateTicketsWithComments", "activityResponse": [$1] } } </format> <args> <arg evaluator="xml" expression="get-property('operation', 'responseString')" /> </args> </payloadFactory> <send /> </outSequence> </target> <description /> </proxy>
{ "cashboard": { "apiUrl": "https://api.testing.cashboardapp.com", "emailAddress": "sample@gmail.com", "password": "1qx@", "subdomain": "testing", "updatedSince":"2015-06-02 00:00:00" }, "sirportly": { "apiUrl": "https://testing.sirportly.com", "apiToken": "ac77fbbc-0647-bf14-d198-799331", "apiSecret": "fxg6fabf7g2izez6ntl7ysxq7v1w8gzed09zlcfi12l8vwj" } }
Note
 The following are the parameter descriptions:
Preferred date from which the comments of the project list task needs to be considered.Âcashboard.updatedSince:
(Strictly follow the date format: YYYY-MM-DD HH:MM:SS, example: 2015-05-06 00:00:00)
Note that all the rest of the parameters are API URLs and authentication credentials to access each API specified in the request. Refer to the individual connector methods for clarification.
If the value for the
parameter is not provided, the beginning time of the current date (YYYY-MM-DD 00:00:00) will be considered as the default value.cashboard.updatedSince
Sending invoices and notifying clients
- Retrieve all invoices from the Cashboard API using the listInvoices operation.
- Retrieve all unarchived project lists which are created under the given project using the listProjectLists operation.
- Retrieve the line item in the project list from the Cashboard API using the getLineItem operation.
- Retrieve client details related to the estimation from the Cashboard API using the getClientContact operation.
- Retrieve ticket from the Sirportly API using the getTicket operation.
- Create an invoice for the estimate which is an offline process (done by the assignee of the ticket).
- Retrieve invoices of tickets which are billable in the Cashboard API using the getInvoice operation and send them to the client using the sendMessage operation in the Mandrill API.
- Update the invoice status as sent in the Cashboard API using the updateInvoice operation.
- Update the ticket status as resolved in the Sirportly API using the updateTicket operation.
Â
Assumptions
 Invoices are created only for the billable tickets.
Sirportly operations
Cashboard operations
Mandrill operations
Prerequisites
- Task of the project list should be completed and closed for the resolved tickets as an offline process.Â
- Invoices should be created from estimates for the billable tickets as an offline process.Â
Samples
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Send the invoice via Mandrill, update invoice has been sent as true via Cashboard and update the status in Sirportly.--> <template name="sirportly_sendInvoiceToClient" xmlns="http://ws.apache.org/ns/synapse"> <!-- Mandrill Properties. --> <parameter name="mandrillApiKey" description="Mandrill API key." /> <parameter name="mandrillApiUrl" description="The API url of mandrill." /> <parameter name="mandrillHtmlContent" description="The html content to be sent." /> <parameter name="mandrillTo" description="The resever details object." /> <parameter name="mandrillSubject" description="The subject of the mail." /> <parameter name="mandrillFromEmail" description="The resever details object." /> <parameter name="mandrillFromName" description="The subject of the mail." /> <!-- Cashboard Properties. --> <parameter name="cashboardApiUrl" description="The URL of the Cashboard API." /> <parameter name="cashboardEmailAddress" description="Email which is used to create the Cashboard account." /> <parameter name="cashboardPassword" description="Password of the Cashboard account." /> <parameter name="cashboardSubdomain" description="Subdomain of the Cashboard account." /> <parameter name="cashboardInvoiceId" description="The unique identifier of the invoice." /> <parameter name="sirportlyApiUrl" description="The URL of the Cashboard API." /> <!-- Sirportly Properties. --> <parameter name="sirportlyApiToken" description="Email which is used to create the Cashboard account." /> <parameter name="sirportlyApiSecret" description="Password of the Cashboard account." /> <parameter name="sirportlySubject" description="Subdomain of the Cashboard account." /> <parameter name="sirportlyTicketReference" description="The unique identifier of the invoice." /> <sequence> <property name="uri.var.mandrillApiKey" expression="$func:mandrillApiKey" /> <property name="uri.var.mandrillApiUrl" expression="$func:mandrillApiUrl" /> <property name="uri.var.mandrillHtmlContent" expression="$func:mandrillHtmlContent" /> <property name="uri.var.mandrillTo" expression="$func:mandrillTo" /> <property name="uri.var.mandrillSubject" expression="$func:mandrillSubject" /> <property name="uri.var.mandrillFromEmail" expression="$func:mandrillFromEmail" /> <property name="uri.var.mandrillFromName" expression="$func:mandrillFromName" /> <property name="uri.var.cashboardApiUrl" expression="$func:cashboardApiUrl" /> <property name="uri.var.cashboardEmailAddress" expression="$func:cashboardEmailAddress" /> <property name="uri.var.cashboardPassword" expression="$func:cashboardPassword" /> <property name="uri.var.cashboardSubdomain" expression="$func:cashboardSubdomain" /> <property name="uri.var.cashboardInvoiceId" expression="$func:cashboardInvoiceId" /> <property name="uri.var.sirportlyApiUrl" expression="$func:sirportlyApiUrl" /> <property name="uri.var.sirportlyApiToken" expression="$func:sirportlyApiToken" /> <property name="uri.var.sirportlyApiSecret" expression="$func:sirportlyApiSecret" /> <property name="uri.var.sirportlySubject" expression="$func:sirportlySubject" /> <property name="uri.var.sirportlyTicketReference" expression="$func:sirportlyTicketReference" /> <!-- Check weather the Mandril apiKey is sent by the user. --> <filter source="boolean(get-property('mandrill.apiKey'))" regex="true"> <then> <!-- If Mandrill apiKey is sent: Send the invoice through Mandril. --> <mandrill.init> <apiKey>{$ctx:uri.var.mandrillApiKey}</apiKey> <apiUrl>{$ctx:uri.var.mandrillApiUrl}</apiUrl> <format>json</format> </mandrill.init> <mandrill.sendMessage> <html>{$ctx:uri.var.mandrillHtmlContent}</html> <subject>{$ctx:uri.var.mandrillSubject}</subject> <fromEmail>{$ctx:uri.var.mandrillFromEmail}</fromEmail> <fromName>{$ctx:uri.var.mandrillFromName}</fromName> <to>{$ctx:uri.var.mandrillTo}</to> </mandrill.sendMessage> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="uri.var.mandrill.messageId" expression="json-eval($[0]._id)" /> <!-- Set error if message not sent in Mandrill. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="uri.var.id" expression="fn:concat('cashboard.invoiceId:', get-property('cashboard.invoiceId'))" /> <property name="uri.var.message" expression="json-eval($.message)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:uri.var.id}" /> <with-param name="message" value="{$ctx:uri.var.message}" /> </call-template> </then> <else> <!-- Update the invoice status as sent. --> <cashboard.init> <apiUrl>{$ctx:uri.var.cashboardApiUrl}</apiUrl> <emailAddress>{$ctx:uri.var.cashboardEmailAddress}</emailAddress> <password>{$ctx:uri.var.cashboardPassword}</password> <subdomain>{$ctx:uri.var.cashboardSubdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.updateInvoice> <invoiceId>{$ctx:uri.var.cashboardInvoiceId}</invoiceId> <hasBeenSent>true</hasBeenSent> </cashboard.updateInvoice> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- Set error if update invoice fails in Cashboard. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="uri.var.cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- If any other response comes it will be caught here. --> <property name="uri.var.cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('uri.var.cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:uri.var.cashboard.errorResponseHtml}" /> </call-template> <property name="uri.var.cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="uri.var.cashboard.errorMessage" expression="get-property('uri.var.cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <property name="id" expression="fn:concat('cashboard_invoiceId:', get-property('uri.var.cashboardInvoiceId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_updateInvoice" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:uri.var.cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <!-- Update the ticket status as resolved. --> <sirportly.init> <apiUrl>{$ctx:uri.var.sirportlyApiUrl}</apiUrl> <apiToken>{$ctx:uri.var.sirportlyApiToken}</apiToken> <apiSecret>{$ctx:uri.var.sirportlyApiSecret}</apiSecret> </sirportly.init> <sirportly.updateTicket> <subject>{$ctx:uri.var.sirportlySubject}</subject> <ticketReference>{$ctx:uri.var.sirportlyTicketReference}</ticketReference> <status>Resolved</status> </sirportly.updateTicket> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="uri.var.sirportly.status" expression="json-eval($.status.name)" /> <!--START of Filter: Check weather the status is successfully updated in Sirportly. --> <filter xpath="get-property('uri.var.sirportly.status') = 'Resolved'"> <then> <!-- Set the success massage after sending the message. --> <property name="id" expression="fn:concat('cashboard_invoiceId:', get-property('uri.var.cashboardInvoiceId'), ',mandrill_messageId:', get-property('uri.var.mandrill.messageId'))" /> <property name="message" expression="fn:concat('Invoice is sent to requester (', get-property('cashboardClientFirstName'), ') successfully.')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <!-- Set the success massage in case of failure to update the status. --> <property name="id" expression="fn:concat('cashboard_invoiceId:', get-property('uri.var.cashboardInvoiceId'), ',mandrill_messageId:', get-property('uri.var.mandrill.messageId'))" /> <property name="message" expression="fn:concat('Invoice is sent to requester (', get-property('cashboardClientFirstName'), ') successfully however failed to update the status of the ticket')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </else> </filter><!--END of Filter: Check weather the status is successfully updated in Sirportly. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </then> <else> <!-- Set error if Mandrill apiKey is not sent by the user. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="message" value="Mandrill api key is not provided in the request." /> </call-template> </else> </filter><!--END of Filter: Check weather the Mandril apiKey is sent by the user. --> </sequence> </template>
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!-- Notify the requester about the ticket's status via Mandrill and update the status in Sirportly.--> <template xmlns="http://ws.apache.org/ns/synapse" name="sirportly_sendMessageToClient"> <!-- Mandrill Properties. --> <parameter name="mandrillApiKey" description="Mandrill API key." /> <parameter name="mandrillApiUrl" description="The API url of mandrill." /> <parameter name="mandrillHtmlContent" description="The html content to be sent." /> <parameter name="mandrillTo" description="The resever details object." /> <parameter name="mandrillSubject" description="The subject of the mail." /> <parameter name="mandrillFromEmail" description="The resever details object." /> <parameter name="mandrillFromName" description="The subject of the mail." /> <!-- Sirportly Properties. --> <parameter name="sirportlyApiUrl" description="The URL of the Cashboard API." /> <parameter name="sirportlyApiToken" description="Email which is used to create the Cashboard account." /> <parameter name="sirportlyApiSecret" description="Password of the Cashboard account." /> <parameter name="sirportlySubject" description="Subdomain of the Cashboard account." /> <parameter name="sirportlyTicketReference" description="The unique identifier of the invoice." /> <sequence> <property name="uri.var.mandrillApiKey" expression="$func:mandrillApiKey" /> <property name="uri.var.mandrillApiUrl" expression="$func:mandrillApiUrl" /> <property name="uri.var.mandrillHtmlContent" expression="$func:mandrillHtmlContent" /> <property name="uri.var.mandrillTo" expression="$func:mandrillTo" /> <property name="uri.var.mandrillSubject" expression="$func:mandrillSubject" /> <property name="uri.var.mandrillFromEmail" expression="$func:mandrillFromEmail" /> <property name="uri.var.mandrillFromName" expression="$func:mandrillFromName" /> <property name="uri.var.sirportlyApiUrl" expression="$func:sirportlyApiUrl" /> <property name="uri.var.sirportlyApiToken" expression="$func:sirportlyApiToken" /> <property name="uri.var.sirportlyApiSecret" expression="$func:sirportlyApiSecret" /> <property name="uri.var.sirportlySubject" expression="$func:sirportlySubject" /> <property name="uri.var.sirportlyTicketReference" expression="$func:sirportlyTicketReference" /> <!--START of Filter: Check weather the mandril apiKey is sent by the user. --> <filter source="boolean(get-property('mandrill.apiKey'))" regex="true"> <then> <!-- If mandrill apiKey is sent: Send the invoice through Mandrill. --> <mandrill.init> <apiKey>{$ctx:uri.var.mandrillApiKey}</apiKey> <apiUrl>{$ctx:uri.var.mandrillApiUrl}</apiUrl> <format>json</format> </mandrill.init> <mandrill.sendMessage> <html>{$ctx:uri.var.mandrillHtmlContent}</html> <subject>{$ctx:uri.var.mandrillSubject}</subject> <fromEmail>{$ctx:uri.var.mandrillFromEmail}</fromEmail> <fromName>{$ctx:uri.var.mandrillFromName}</fromName> <to>{$ctx:uri.var.mandrillTo}</to> </mandrill.sendMessage> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="uri.var.mandrill.messageId" expression="json-eval($[0]._id)" /> <!-- Set error if message not sent in Mandrill. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="uri.var.id" expression="fn:concat('mandrill_messageId:', get-property('uri.var.mandrill.messageId'))" /> <property name="uri.var.message" expression="json-eval($.message)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:uri.var.id}" /> <with-param name="message" value="{$ctx:uri.var.message}" /> </call-template> </then> <else> <!-- Update the ticket status as resolved. --> <sirportly.init> <apiUrl>{$ctx:uri.var.sirportlyApiUrl}</apiUrl> <apiToken>{$ctx:uri.var.sirportlyApiToken}</apiToken> <apiSecret>{$ctx:uri.var.sirportlyApiSecret}</apiSecret> </sirportly.init> <sirportly.updateTicket> <subject>{$ctx:uri.var.sirportlySubject}</subject> <ticketReference>{$ctx:uri.var.sirportlyTicketReference}</ticketReference> <status>Resolved</status> </sirportly.updateTicket> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="uri.var.sirportly.status" expression="json-eval($.status.name)" /> <!--START of Filter: Check weather the status is successfully updated in Sirportly. --> <filter xpath="get-property('uri.var.sirportly.status') = 'Resolved'"> <then> <!-- Set the success massage after sending the message. --> <property name="id" expression="fn:concat('mandrill_messageId:', get-property('uri.var.mandrill.messageId'))" /> <property name="message" expression="fn:concat('Email notification about the closure of the ticket is sent to requester (', get-property('cashboardClientFirstName'), ') successfully.')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <!-- Set the success massage in case of failure to update the status. --> <property name="id" expression="fn:concat('mandrill_messageId:', get-property('uri.var.mandrill.messageId'))" /> <property name="message" expression="fn:concat('Email notification about the closure of the ticket is sent to requester (', get-property('cashboardClientFirstName'), ') successfully. However failed to update the status of the ticket as resolved.')" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Success" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </else> </filter><!--END of Filter: Check weather the status is successfully updated in Sirportly. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </then> <else> <!-- Set error if mandrill apiKey is not sent by the user. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="mandrill_sendMessage" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="message" value="Mandrill api key is not provided in the request." /> </call-template> </else> </filter><!--END of Filter: Check weather the mandrill apiKey is sent by the user. --> </sequence> </template>
<?xml version="1.0" encoding="UTF-8"?> <!-- Copyright (c) 2005-2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. WSO2 Inc. licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> <!--This proxy will retrieve unpaid invoices of projectLists from Cashboard, send them to clients via Mandrill if the ticket is billable. --> <proxy xmlns="http://ws.apache.org/ns/synapse" name="sirportly_sendInvoiceAndNotifyClients" transports="https" statistics="disable" trace="disable" startOnLoad="true"> <target> <inSequence onError="faultHandlerSeq"> <!-- Cashboard Properties. --> <property name="cashboard.apiUrl" expression="json-eval($.cashboard.apiUrl)" /> <property name="cashboard.emailAddress" expression="json-eval($.cashboard.emailAddress)" /> <property name="cashboard.password" expression="json-eval($.cashboard.password)" /> <property name="cashboard.subdomain" expression="json-eval($.cashboard.subdomain)" /> <property name="cashboard.projectId" expression="json-eval($.cashboard.projectId)" /> <property name="cashboard.updatedSince" expression="json-eval($.cashboard.updatedSince)" /> <!-- Sirportly Properties. --> <property name="sirportly.apiUrl" expression="json-eval($.sirportly.apiUrl)" /> <property name="sirportly.apiToken" expression="json-eval($.sirportly.apiToken)" /> <property name="sirportly.apiSecret" expression="json-eval($.sirportly.apiSecret)" /> <!-- Mandrill Properties. --> <property name="mandrill.apiUrl" value="https://mandrillapp.com" /> <property name="mandrill.apiKey" expression="json-eval($.mandrill.apiKey)" /> <property name="mandrill.fromEmail" expression="json-eval($.mandrill.fromEmail)" /> <property name="mandrill.fromName" expression="json-eval($.mandrill.fromName)" /> <!-- Operation scoped properties. --> <property name="id.empty" value="{}" /> <property name="responseString" value="" scope="operation" /> <!-- If the value for updateSince parameter is not provided, set the default value as current date. --> <script language="js"><![CDATA[ //setting the default value of the current date as the beginning of the day. var requestUpdatedDate = mc.getProperty('cashboard.updatedSince'); if(requestUpdatedDate != null && requestUpdatedDate != ""){ mc.setProperty('cashboard.updatedSince', requestUpdatedDate); }else{ mc.setProperty('cashboard.updatedSince', new java.text.SimpleDateFormat("yyyy-MM-dd '00:00:00'").format(new java.util.Date())); } ]]></script> <!-- List all invoices which are not send. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>json</format> </cashboard.init> <cashboard.listInvoices> <hasBeenSent>false</hasBeenSent> </cashboard.listInvoices> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- Get the response status. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- if any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <!--END of Filter: Checking for HTML responses. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="cashboard.invoiceArray" expression="json-eval($)" /> <script language="js"><![CDATA[ //Script to construct the JSON object to map all the available projectList IDs against invoice IDs. var invoiceArray= eval("(" + mc.getProperty('cashboard.invoiceArray') + ")"); var invoiceObject={}; var projectListId=""; var invoiceId=""; for(i=0; i<invoiceArray.length ; i++){ var notes=invoiceArray[i].notes; projectListId = 'ID' + (((notes.split("Created from Estimate ")[1])+"").split('.')[0]); invoiceId= '' + invoiceArray[i].id; invoiceId=invoiceId.split(".")[0]; invoiceObject[projectListId]=invoiceId; } mc.setPayloadJSON(invoiceObject); ]]></script> <property name="cashboardInvoiceObject" expression="json-eval($)" /> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> <!-- List all unarchived projectLists which are created under given project. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.listProjectLists> <isArchived>false</isArchived> <projectId>{$ctx:cashboard.projectId}</projectId> <updatedSince>{$ctx:cashboard.updatedSince}</updatedSince> </cashboard.listProjectLists> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="messageType" value="application/xml" scope="axis2" /> <property name="projectListsCount" expression="count(//project_lists/project_list)" scope="operation" /> <property name="index" expression="0" scope="operation" /> <!-- Get the response status. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- if any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <!--END of Filter: Checking for HTML responses. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <!--START of Filter: Set error if no estimates retrieved. --> <filter xpath="0 = get-property('operation', 'projectListsCount')"> <then> <property name="message" value="There are no projectLists found." /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listEstimates" /> <with-param name="status" value="Skipped" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> <loopback /> </then> </filter> <!--END of Filter: Set error if no estimates retrieved. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> <!--START LOOP: Retrieve details of each projectList. --> <iterate continueParent="false" id="projectListIterator" expression="//project_lists/project_list" sequential="true"> <target> <sequence> <property name="cashboard.lineItemId" expression="//line_item/id" /> <property name="projectListId" expression="//project_list/id" /> <property name="cashboard.projectListId" expression="fn:concat('ID', get-property('projectListId'))" /> <property name="cashboard.clientContactId" expression="//project_list/person_id" /> <property name="sirportly.ticketReference" expression="substring-before(//title/text(),'_')" /> <!-- Get the line item in the projectList. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.getLineItem> <lineItemId>{$ctx:cashboard.lineItemId}</lineItemId> </cashboard.getLineItem> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="cashboard.isCompleted" expression="//line_item/is_complete" /> <!-- Check response status for all possible error responses. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!--If any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <!--END of Filter: Checking for HTML responses. --> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_listProjectLists" /> <with-param name="id" value="{$ctx:id.empty}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <!-- Get client details related to the estimation. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>xml</format> </cashboard.init> <cashboard.getClientContact> <clientContactId>{$ctx:cashboard.clientContactId}</clientContactId> </cashboard.getClientContact> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- Set error if no contact retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!--If any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <!--END of Filter: Checking for HTML responses. --> <property name="id" expression="fn:concat('cashboard_clientContactId:', get-property('cashboard.clientContactId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_getClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="cashboardClientFirstName" expression="//first_name" /> <property name="cashboardClientContactEmailAddress" expression="//email_address" /> <!--START of Filter: Check the projectList is completed. --> <filter xpath="get-property('cashboard.isCompleted') = 'true'"> <then> <!-- Get sirportly ticket. --> <sirportly.init> <apiUrl>{$ctx:sirportly.apiUrl}</apiUrl> <apiToken>{$ctx:sirportly.apiToken}</apiToken> <apiSecret>{$ctx:sirportly.apiSecret}</apiSecret> </sirportly.init> <sirportly.getTicket> <ticketReference>{$ctx:sirportly.ticketReference}</ticketReference> </sirportly.getTicket> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <property name="sirportly.subject" expression="json-eval($.subject)" /> <property name="sirportly.isBIllable" expression="json-eval($.custom_fields.IsBIllable)" /> <property name="sirportly.ticketDepartment" expression="json-eval($.department.name)" /> <property name="sirportly.status" expression="json-eval($.status.name)" /> <!-- Set error if no ticket retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'))" /> <property name="message" expression="json-eval($)" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="status" value="Error" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="message" value="{$ctx:message}" /> </call-template> </then> <else> <!--START of Filter: Check the ticket is billable. --> <filter xpath="get-property('sirportly.isBIllable') = 'true'"> <then> <!-- Getting cashboard invoice id against the projectList id. --> <script language="js"><![CDATA[ //Script that checks the there is invoice for particular projectList. var projectListId=mc.getProperty('cashboard.projectListId'); var cashboardInvoiceObject= eval("(" + mc.getProperty('cashboardInvoiceObject') + ")"); var invoiceId=""; if(cashboardInvoiceObject.hasOwnProperty(projectListId)){ invoiceId=cashboardInvoiceObject[projectListId]; } mc.setProperty('cashboard.invoiceId',invoiceId); ]]></script> <!--START of Filter: Check weather the invoice is already sent. --> <filter source="boolean(get-property('cashboard.invoiceId'))" regex="false"> <then> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="Invoice related to this ticket is already sent to requester." /> </call-template> </then> <else> <!-- Get invoice in html version needed to be sent to client. --> <cashboard.init> <apiUrl>{$ctx:cashboard.apiUrl}</apiUrl> <emailAddress>{$ctx:cashboard.emailAddress}</emailAddress> <password>{$ctx:cashboard.password}</password> <subdomain>{$ctx:cashboard.subdomain}</subdomain> <format>html</format> </cashboard.init> <cashboard.getInvoice> <invoiceId>{$ctx:cashboard.invoiceId}</invoiceId> </cashboard.getInvoice> <!--Removing unused headers. --> <sequence key="removeResponseHeaders" /> <!-- Set error if no invoice retrieved. --> <property name="responseStatus" expression="$axis2:HTTP_SC" /> <!--START of Filter: Check response status for all possible error responses. --> <filter xpath="get-property('responseStatus') != 200"> <then> <property name="id" expression="fn:concat('cashboard_invoiceId:', get-property('cashboard.invoiceId'))" /> <!--If in case the error response returns as html then retrieve the error message accordingly. --> <property name="cashboard.errorResponseHtml" expression="json-eval($.binary)" /> <!-- if any other response comes it will be caught here. --> <property name="cashboard.errorResponse" expression="json-eval($)" /> <!--START of Filter: Checking for HTML responses. --> <filter source="boolean(get-property('cashboard.errorResponseHtml'))" regex="true"> <then> <!--If in case the error comes as an html, then call the 'base64Decoder' template in order to extract the error message. --> <call-template target="base64Decoder"> <with-param name="responseBinaryString" value="{$ctx:cashboard.errorResponseHtml}" /> </call-template> <property name="cashboard.errorMessage" expression="get-property('decodedResult')" /> </then> <else> <property name="cashboard.errorMessage" expression="get-property('cashboard.errorResponse')" /> </else> </filter><!--END of Filter: Checking for HTML responses. --> <property name="id" expression="fn:concat('cashboard_invoiceId:', get-property('cashboard.invoiceId'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="cashboard_getClientContact" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="{$ctx:cashboard.errorMessage}" /> </call-template> <loopback /> </then> <else> <property name="messageType" value="application/xml" scope="axis2" /> <property name="binaryString" expression="json-eval($.binary)" /> <!--Base64Decode and get the JSON object from base64encoded string issue caused by Content-Type: text/html response header. --> <script language="js"><![CDATA[ function base64_decode(data) { var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = '', tmp_arr = []; if (data.trim().length == 0) { return data; } else { data += ''; } do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(data.charAt(i++)); h2 = b64.indexOf(data.charAt(i++)); h3 = b64.indexOf(data.charAt(i++)); h4 = b64.indexOf(data.charAt(i++)); bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; o1 = bits >> 16 & 0xff; o2 = bits >> 8 & 0xff; o3 = bits & 0xff; if (h3 == 64) { tmp_arr[ac++] = String.fromCharCode(o1); } else if (h4 == 64) { tmp_arr[ac++] = String.fromCharCode(o1, o2); } else { tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); } } while (i < data.length); dec = tmp_arr.join(''); return dec.replace(/\0+$/, ''); } var binaryString = mc.getProperty('binaryString'); var name = mc.getProperty('cashboardClientFirstName'); var email = mc.getProperty('cashboardClientContactEmailAddress'); if(binaryString != null && binaryString != ""){ var htmlContent = base64_decode(binaryString); mc.setProperty('htmlContent', new java.lang.String(htmlContent).replace("\n", "").replace("\r", "").replace("\"", "\\\\\\\"")); } var mailTo = '[{"email":"' + email + '","name":"' + name + '","type": "to"}]'; mc.setProperty('mandrill.to', mailTo); ]]></script> <payloadFactory media-type="json"> <format>{ "html":"$1" } </format> <args> <arg expression="get-property('htmlContent')" /> </args> </payloadFactory> <property name="mandrill.html" expression="json-eval($.html)" /> <property name="mandrill.to" expression="get-property('mandrill.to')" /> <property name="mandrill.subject" expression="fn:concat('Invoice for the ticket you raised under ', get-property('sirportly.ticketReference'))" /> <!-- Send invoice, update invoice as sent and update ticket as resolved. --> <call-template target="sirportly_sendInvoiceToClient"> <with-param name="mandrillApiKey" value="{$ctx:mandrill.apiKey}" /> <with-param name="mandrillApiUrl" value="{$ctx:mandrill.apiUrl}" /> <with-param name="mandrillHtmlContent" value="{$ctx:mandrill.html}" /> <with-param name="mandrillTo" value="{$ctx:mandrill.to}" /> <with-param name="mandrillSubject" value="{$ctx:mandrill.subject}" /> <with-param name="mandrillFromEmail" value="{$ctx:mandrill.fromEmail}" /> <with-param name="mandrillFromName" value="{$ctx:mandrill.fromName}" /> <with-param name="cashboardApiUrl" value="{$ctx:cashboard.apiUrl}" /> <with-param name="cashboardEmailAddress" value="{$ctx:cashboard.emailAddress}" /> <with-param name="cashboardPassword" value="{$ctx:cashboard.password}" /> <with-param name="cashboardSubdomain" value="{$ctx:cashboard.subdomain}" /> <with-param name="cashboardInvoiceId" value="{$ctx:cashboard.invoiceId}" /> <with-param name="sirportlyApiUrl" value="{$ctx:sirportly.apiUrl}" /> <with-param name="sirportlyApiToken" value="{$ctx:sirportly.apiToken}" /> <with-param name="sirportlyApiSecret" value="{$ctx:sirportly.apiSecret}" /> <with-param name="sirportlySubject" value="{$ctx:sirportly.subject}" /> <with-param name="sirportlyTicketReference" value="{$ctx:sirportly.ticketReference}" /> </call-template> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </else> </filter><!--END of Filter: Check weather the invoice is already sent. --> </then> <else> <!--START of Filter: Check weather the client already notified about the ticket status. --> <filter xpath="get-property('sirportly.status') = 'Resolved'"> <then> <property name="id" expression="fn:concat('sirportly_ticketReference:', get-property('sirportly.ticketReference'))" /> <call-template target="responseHandlerTemplate"> <with-param name="activity" value="sirportly_getTicket" /> <with-param name="id" value="{$ctx:id}" /> <with-param name="status" value="Error" /> <with-param name="message" value="Status of this ticket is already sent to the requester." /> </call-template> </then> <else> <script language="js"><![CDATA[ //Script that sets the message which has to be sent. var department = mc.getProperty('sirportly.ticketDepartment'); var ticktReference = mc.getProperty('sirportly.ticketReference'); var name = mc.getProperty('cashboardClientFirstName'); var email = mc.getProperty('cashboardClientContactEmailAddress'); var mailContent = "<h3>Dear "+name+",</h3><p>Thank you for reaching the <b>"+department+"</b>. Your Service Request under "+ticktReference+"</b> has been resolved.</p><p>Regards, <br><b>"+department+"</b></p>"; var mailTo = '[{"email":"' + email + '","name":"' + name + '","type": "to"}]'; mc.setProperty('mandrill.to', mailTo); mc.setProperty('html', mailContent); ]]></script> <property name="mandrill.html" expression="fn:concat('<html>', get-property('html'), '</html>')" /> <property name="mandrill.to" expression="get-property('mandrill.to')" /> <property name="mandrill.subject" expression="fn:concat('Your ticket ', get-property('sirportly.ticketReference'), ' is Resolved')" /> <!-- Send message to inform client and update ticket as resolved. --> <call-template target="sirportly_sendMessageToClient"> <with-param name="mandrillApiKey" value="{$ctx:mandrill.apiKey}" /> <with-param name="mandrillApiUrl" value="{$ctx:mandrill.apiUrl}" /> <with-param name="mandrillHtmlContent" value="{$ctx:mandrill.html}" /> <with-param name="mandrillTo" value="{$ctx:mandrill.to}" /> <with-param name="mandrillSubject" value="{$ctx:mandrill.subject}" /> <with-param name="mandrillFromEmail" value="{$ctx:mandrill.fromEmail}" /> <with-param name="mandrillFromName" value="{$ctx:mandrill.fromName}" /> <with-param name="sirportlyApiUrl" value="{$ctx:sirportly.apiUrl}" /> <with-param name="sirportlyApiToken" value="{$ctx:sirportly.apiToken}" /> <with-param name="sirportlyApiSecret" value="{$ctx:sirportly.apiSecret}" /> <with-param name="sirportlySubject" value="{$ctx:sirportly.subject}" /> <with-param name="sirportlyTicketReference" value="{$ctx:sirportly.ticketReference}" /> </call-template> </else> </filter><!--END of Filter: Check weather the client already notified about the ticket status. --> </else> </filter><!--END of Filter: Check the ticket is billable. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </then> </filter><!--END of Filter: Check the projectList is completed. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> </else> </filter><!--END of Filter: Check response status for all possible error responses. --> <!--Increment the index count for each iteration. --> <property name="index" expression="get-property('operation','index') + 1" scope="operation" /> </sequence> </target> </iterate><!--END LOOP: for each projectList. --> <!--START of Filter: Check for all the iterations has completed. --> <filter xpath="get-property('operation', 'index') = get-property('operation', 'projectListsCount')"> <then> <loopback /> </then> </filter><!--END of Filter: Check for all the iterations has completed. --> </inSequence> <outSequence> <payloadFactory media-type="json"> <format> { "Response":{ "process":"sirportly_sendInvoiceAndNotifyClients", "activityResponse":[ $1 ] } } </format> <args> <arg evaluator="xml" expression="get-property('operation','responseString')" /> </args> </payloadFactory> <property name="messageType" value="application/json" scope="axis2" /> <send /> </outSequence> </target> <description /> </proxy>
{ "cashboard":{ "apiUrl":"https://api.barnselectronicslankacom.cashboardapp.com", "emailAddress":"testing@yahoo.com", "password":"1qaz2@", "subdomain":"barnselectronicslankacom", "projectId":"116587", "updatedSince":"2015-07-14 00:00:00" }, "sirportly":{ "apiUrl":"http://barnselectronicslanka.sirportly.com", "apiToken":"8ae425db-709b-abb6-39c1-3164b7fc", "apiSecret":"jjwjk63lcmziniefv674s0zps18t1ufiipogu8eq0g99i4rj2m" }, "mandrill":{ "apiKey":"489IH-v85O97o8vxwDmDH", "fromEmail":"testing@gmail.com", "fromName":"supun" } }
Note
 The following are the parameter descriptions:
The ID of the project.cashboard.projectId:
This date will be considered to filter the project-lists which are last updated after this date and time.Âcashboard.updatedSince:
Follow the format 'YYYY-MM-DD HH:MM:SS'. If this parameter is not set, the case will consider the beginning of the current day's time by default. e.g.:- YYYY-MM-DD 00:00:00
Use a valid e-mail address from which the Mandrill e-mails are required to be sent.mandrill.fromEmail:
Use a valid name indicating from whom the Mandrill e-mails are required to be sent.mandrill.fromName: