atlascamp 2015: jira service desk: scale your team with build-it-yourself automation rules
TRANSCRIPT
JIRA Service Desk Scale your team with
build-it-yourself automation rules
ADAM HYNES & CLEMENT CAPIAUX • DEVELOPERS • ATLASSIAN
A U TO M AT I O N
J I R A S E RV I C E D E S K
Scale your team with build-it-yourself automation rules
D E M O
L E T ’ S E X T E N D !
JIRA Service Desk key featuresC O L U M N T I T L E C O L U M N T I T L E C O L U M N T I T L E
Customer portal
Queues
SLAs
Reports
Knowledge Base
Service desks agents are overwhelmed:
But wait… there’s a problem
• doing repetitive tasks• forgetting important stuff
All this time lost…
Doing repetitive tasks
Making sure things don’t slip through the cracks
Updating other applications
Commenting on tickets
Granting access to a system
Closing tickets
Alerting support when SLAs are getting critical
Escalating inactive issues
Pinging customers for input
A U TO M AT I O N
J S D : PA S T, P R E S E N T, F U T U R E
Scale your team with build-it-yourself automation rules
D E M O
L E T ’ S D E V !
A U TO M AT I O N
J S D : PA S T, P R E S E N T, F U T U R E
Scale your team with build-it-yourself automation rules
D E M O
L E T ’ S D E V !
What can we extend?
• Any JIRA event• Issue events• User events
• Any other event• Webhook
• Custom field value
• User property• Webhook URL
param
• REST API call• Close a bunch of
issues• Send an email• Send an SMS
atlassian-plugin.xml<atlassian-plugin key="com.atlassian.plugins.atlascamp.sdautomation.atlascamp-sdautomation-demo" name="${project.name}" plugins-version="2"> <plugin-info> <description>${project.description}</description> <version>${project.version}</version> <vendor name="${project.organization.name}" url="${project.organization.url}" /> <param name="plugin-icon">images/pluginIcon.png</param> <param name="plugin-logo">images/pluginLogo.png</param> </plugin-info>
<automation-rule-then-action key="call-number-then-action" class="com.atlassian.plugins.atlascamp.sdautomation.CallMeMaybe" name="Call number" name-i18n-key="call.number.name"> <icon-class>user-status</icon-class> <requires> <require>issue</require> </requires> <visualiser class="com.atlassian.plugins.atlascamp.sdautomation.CallNumberVisualiser"/> <validator class="com.atlassian.plugins.atlascamp.sdautomation.PhoneNumberValidator"/> <web-form-module>atlascamp/automation/demo/modules/call-number-form</web-form-module> </automation-rule-then-action>
<!-- Specify the location of our client resources --> <client-resource key="atlascamp-automation-resources"> <context>sd.agent.view</context> <directory location="atlascamp/automation/demo/modules" /> </client-resource> <!-- Add our i18n resource --> <resource type="i18n" name="i18n" location="i18n/atlascamp-sdautomation"/></atlassian-plugin>
Define the module
<automation-rule-then-action key="call-number-then-action" class="com.atlassian.plugins.atlascamp.sdautomation.CallMeMaybe" name="Call number" name-i18n-key="call.number.name"> <icon-class>user-status</icon-class> <requires> <require>issue</require> </requires> <visualiser class="com.atlassian.plugins.atlascamp.sdautomation.CallNumberVisualiser"/> <validator class="com.atlassian.plugins.atlascamp.sdautomation.PhoneNumberValidator"/> <web-form-module>atlascamp/automation/demo/modules/call-number-form</web-form-module> </automation-rule-then-action>
Implement the actionpublic final class CallMeMaybe implements ThenAction{ // Variables and constructor
@Overridepublic Either<ThenActionError, RuleMessage> invoke(final ThenActionParam thenActionParam){ //TODO: Implement}
private PhoneCallTranscript generateTranscriptForIssue(final Issue issue) { final String reporter = issue.getReporter().getDisplayName(); final String priority = issue.getPriorityObject().getName(); final String summary = issue.getSummary(); final String transcriptLine1 = "Welcome to Service Desk"; final String transcriptLine2 = new StringBuilder() .append(reporter) .append(" has raised a ").append(priority).append(" issue with the following summary ") .toString(); final String transcriptLine3 = summary; return new PhoneCallTranscript( newArrayList( transcriptLine1, transcriptLine2, transcriptLine3 ) ); } }
public interface ThenAction{ Either<ThenActionError, RuleMessage> invoke(ThenActionParam thenActionParam); public static interface ThenActionParam { ApplicationUser getUser(); ThenActionConfiguration getConfiguration(); RuleMessage getMessage(); } }
@Overridepublic Either<ThenActionError, RuleMessage> invoke(final ThenActionParam thenActionParam){ //TODO: Implement}
@Overridepublic Either<ThenActionError, RuleMessage> invoke(final ThenActionParam thenActionParam){ }
Implement invoke()
// Get the phone number we want to callfinal Option<String> phoneNumberOpt = thenActionParam.getConfiguration().getData().getValue(PHONE_NUMBER_KEY);if (phoneNumberOpt.isEmpty()){ return thenActionErrorHelper.error("No " + PHONE_NUMBER_KEY + " property in config data"); } final String numberToCall = phoneNumberOpt.get();
// Get the issue for which we want make a callfinal Either<AnError, Issue> issueEither = issueMessageHelper.getIssue(thenActionParam.getMessage());if (issueEither.isLeft()){ // We don't perform any task if we can't get the issue from the rule message return thenActionErrorHelper.error(issueEither.left().get());} final Issue issueToMakeCallFor = issueEither.right().get();
// Make the callphoneCaller.call( new InternationalPhoneNumber(numberToCall), generateTranscriptForIssue(issueToMakeCallFor));
return right(thenActionParam.getMessage());
Define the module
<automation-rule-then-action key="call-number-then-action" class="com.atlassian.plugins.atlascamp.sdautomation.CallMeMaybe" name="Call number" name-i18n-key="call.number.name"> <icon-class>user-status</icon-class> <requires> <require>issue</require> </requires> <visualiser class="com.atlassian.plugins.atlascamp.sdautomation.CallNumberVisualiser"/> <validator class="com.atlassian.plugins.atlascamp.sdautomation.PhoneNumberValidator"/> <web-form-module>atlascamp/automation/demo/modules/call-number-form</web-form-module> </automation-rule-then-action>
Provides -> Requires
Issue commented
Issue
User
Comment
Issue created
Issue
User
SLA time remaining Issue
Issue matches
Issue
Comment visibility
Comment
Issue
User typeUser
Comment contains
Comment
Transition issue
Issue
Add comment
Issue
Alert userIssue
When If Then
Define the module
<automation-rule-then-action key="call-number-then-action" class="com.atlassian.plugins.atlascamp.sdautomation.CallMeMaybe" name="Call number" name-i18n-key="call.number.name"> <icon-class>user-status</icon-class> <requires> <require>issue</require> </requires> <visualiser class="com.atlassian.plugins.atlascamp.sdautomation.CallNumberVisualiser"/> <validator class="com.atlassian.plugins.atlascamp.sdautomation.PhoneNumberValidator"/> <web-form-module>atlascamp/automation/demo/modules/call-number-form</web-form-module> </automation-rule-then-action>
public final class CallNumberVisualiser implements RuleComponentVisualiser{ private final I18nHelper i18nHelper; @Autowired public CallNumberVisualiser(final I18nHelper i18nHelper) { this.i18nHelper = i18nHelper; } @Override public String getName(final RuleComponentVisualiserParam ruleComponentVisualiserParam) { } @Override public Option<String> getLabel(@Nonnull final RuleComponentVisualiserParam ruleComponentVisualiserParam) { }}
final Option<String> configuredLabelOpt = ruleComponentVisualiserParam.ruleConfiguration().getValue(PHONE_NUMBER_KEY); if (configuredLabelOpt.isDefined()) { return some("\"" + configuredLabelOpt.get() + "\""); } else { return none(String.class); }
return i18nHelper.getText("call.number.name");
Implement the visualiser
public interface RuleComponentVisualiser{ String getName(RuleComponentVisualiserParam ruleComponentVisualiserParam); Option<String> getLabel(RuleComponentVisualiserParam ruleComponentVisualiserParam); public static interface RuleComponentVisualiserParam { ApplicationUser getUser(); ConfigurationData ruleConfiguration(); }}
Implement the validator
final Option<String> configuredPhoneNumber = thenActionValidationParam.getConfiguration().getData().getValue(PHONE_NUMBER_KEY); final ApplicationUser userToValidateWith = thenActionValidationParam.getUserToValidateWith(); // For tutorial purposes, we just check the phone number is not blank if (configuredPhoneNumber.isEmpty() || isBlank(configuredPhoneNumber.get())) { return createResultWithFieldError( userToValidateWith, "call.number.missing"); } return ValidationResult.PASSED();
public final class PhoneNumberValidator implements ThenActionValidator{ private final I18nHelper.BeanFactory i18nFactory; @Autowired public PhoneNumberValidator(final I18nHelper.BeanFactory i18nFactory) { this.i18nFactory = i18nFactory; } public ValidationResult validate(final ThenActionValidationParam thenActionValidationParam) { } private ValidationResult createResultWithFieldError(@Nonnull ApplicationUser user, @Nonnull String errorI18nKey) { final I18nHelper i18nHelper = i18nFactory.getInstance(user); Map<String, List<String>> errorList = newHashMap(); errorList.put(PHONE_NUMBER_KEY, newArrayList(i18nHelper.getText(errorI18nKey))); return ValidationResult.FAILED(errorList); }}
public interface ThenActionValidator{ ValidationResult validate(ThenActionValidationParam thenActionValidationParam); interface ThenActionValidationParam { ApplicationUser getUserToValidateWith(); ThenActionConfiguration getConfiguration(); Option<ProjectContext> getProjectContext(); }}
<automation-rule-then-action key="call-number-then-action" class="com.atlassian.plugins.atlascamp.sdautomation.CallMeMaybe" name="Call number" name-i18n-key="call.number.name"> <icon-class>user-status</icon-class> <requires> <require>issue</require> </requires> <visualiser class="com.atlassian.plugins.atlascamp.sdautomation.CallNumberVisualiser"/> <validator class="com.atlassian.plugins.atlascamp.sdautomation.PhoneNumberValidator"/> <web-form-module> </web-form-module> </automation-rule-then-action>
atlascamp/automation/demo/modules/call-number-form
Define the module
define(“ ", [ "servicedesk/jQuery", "servicedesk/underscore"], function ( $, _) {
…
return { render: function(config, errors) { ... }, serialize : function () { ... } } }
atlascamp/automation/demo/modules/call-number-form
Implement the front-end
.automation-servicedesk-call-number-header { padding-bottom: 5px; }
<div class=“automation-servicedesk-call-number-container"> <div class="automation-servicedesk-call-number-header"><b>{getText('call.number.prompt')}</b></div> <input type="text" class="text" name="phoneNumber" value="{$phoneNumber}" placeholder="{getText('call.number.placeholder')}">
</div>
Thank you!
ADAM HYNES & CLEMENT CAPIAUX • DEVELOPERS • ATLASSIAN
(any questions?)bit.ly/jsdcode
Demo source code
bit.ly/jsdguide
Developer guide