Enhance Helpdesk Support
YouTrack provides three main features that let you use it as a helpdesk:
The Mailbox Integration, which pulls email messages from one or more email servers on a regular basis and processes them to create issues and comments.
Email Notifications, which let you send messages to any email address.
And, of course, Workflows, which help you glue it all together!
The basic flow that is supported in YouTrack works as follows:
A customer sends an email message to your support inbox. This creates a new issue in YouTrack.
A support engineer responds to the inquiry by adding a comment to the issue. This generates an email notification that is sent back to the customer as a reply to the original email.
The custom replies to the email notification. The response is processed by YouTrack and added as a comment to the original issue.
You can find a detailed description of this feature in our Helpdesk Tutorial.
Support Service-level Agreements
An important element for an established product support team is the service-level agreement (SLA). This helps set the customers’ expectations for receiving an answer to their requests and comments.
Consider an SLA with the following rules:
Business hours are from 10:00 – 18:00 CET.
Customers can expect a response to their initial support request within three hours.
The response time for a follow-up request is four hours.
You can support an SLA in YouTrack with a workflow. In YouTrack, we’ll customize the setup for our helpdesk project as follows:
The project that we use for the helpdesk uses the following values for the State field:
Value | Description |
---|
New | Default value. Assigned to tickets upon creation. |
Open | Assigned to tickets that are waiting for a reaction from the support team. |
Pending | Assigned to tickets that are waiting for a reaction from the customer. |
On hold | Used for tickets that require input from a developer. |
Fixed | Assigned to resolved tickets. |
For New tickets that have not been answered by the support team, the Overdue tag is added three business hours after the issue was created.
For Open tickets, the Overdue tag is added four business hours after the last comment from a customer was added.
To sort tickets in the order that they should be answered, we add a private custom field: To reply before. This field stores values as a date and time.
When we set up these fields and attach the corresponding workflow rules, the support team can pull tickets from a queue that is ordered by response time using the following query:
State: Open, New sort by: {To reply before} asc
First, we need a utility function that defines our business hours. This is saved as a custom script with the name time-operations
.
// Starting hour of the business day defined in the server time zone.
const START = 10;
// Finishing hour of the business day defined in the server time zone.
const FINISH = 18;
// Length of the business day in hours.
const BUSINESS_DAY_LENGTH = FINISH - START;
const HOUR_IN_MS = 60 * 60 * 1000;
const DAY_IN_MS = 24 * HOUR_IN_MS;
/*
* @param {Date} [date] date to check to be inside business hours interval
* @return {boolean} `true` is the date is within [START, FINISH) interval on
* business days and `false` otherwise
*/
const isWithinBusinessHours = function (date) {
const date1 = new Date(date);
const dayOfWeek = date1.getDay();
if (dayOfWeek === 0 || dayOfWeek === 1) {
return false;
}
const hour = date1.getHours();
return (hour >= START && hour < FINISH);
};
/*
* @param {Number} [date] date in ms from the start of the epoch
* @param {Number} [hours] number of hours to add to this date
* @returns {Number} resulting date with respect to business hours, defined above
*/
const addBusinessHours = function (date, hours) {
let result = date + hours * HOUR_IN_MS;
if (!isWithinBusinessHours(new Date(result))) {
// It's evening, let's skip the night.
result += (24 - BUSINESS_DAY_LENGTH) * HOUR_IN_MS;
}
if (!isWithinBusinessHours(new Date(result))) {
// It's the weekend, let's skip it too.
result += 2 * DAY_IN_MS;
}
return result;
};
exports.addBusinessHours = addBusinessHours;
Now, we can set the value of the To reply before field according to our SLA. The first rule is for the initial response:
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const timeOp = require('./time-operations');
// Number of business hours to reply to the new issue.
const NEW_ISSUE_SLA = 3;
exports.rule = entities.Issue.onChange({
title: 'New Issue SLA',
guard: (ctx) => {
return ctx.issue.becomesReported;
},
action: (ctx) => {
ctx.issue.fields.ToReply = timeOp.addBusinessHours(Date.now(), NEW_ISSUE_SLA);
},
requirements: {
ToReply: {
type: entities.Field.dateTimeType,
name: 'To reply before'
}
}
});
The second rule is for the follow-up:
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const timeOp = require('./time-operations');
// Number of business hours to reply to the replied issue.
const REPLIED_ISSUE_SLA = 4;
exports.rule = entities.Issue.onChange({
title: 'Replied Issue SLA',
guard: (ctx) => {
const comments = ctx.issue.comments;
return comments.added.isNotEmpty() &&
comments.added.last().author.login === ctx.helpdeskBot.login;
},
action: (ctx) => {
ctx.issue.fields.ToReply = timeOp.addBusinessHours(Date.now(), REPLIED_ISSUE_SLA);
},
requirements: {
ToReply: {
type: entities.Field.dateTimeType,
name: 'To reply before'
},
// This is the user which is set as reporter in mail rule settings:
helpdeskBot: {
type: entities.User,
login: 'helpdesk-bot'
}
}
});
The following rule adds the Overdue tag to tickets when the SLA is violated:
const entities = require('@jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onSchedule({
title: 'Set “Overdue" tag',
cron: '0 * * * * ?',
search: 'State: Open, New has: {To reply before} tag: -overdue',
action: (ctx) => {
const toReply = ctx.issue.fields.ToReply;
if (toReply < Date.now()) {
ctx.issue.addTag(ctx.overdue.name);
}
},
requirements: {
ToReply: {
type: entities.Field.dateTimeType,
name: 'To reply before'
},
overdue: {
type: entities.IssueTag
}
}
});
And finally, we clear the value from the To reply before field and remove the Overdue tag when the ticket no longer requires a response from the support team:
const entities = require('@jetbrains/youtrack-scripting-api/entities');
exports.rule = entities.Issue.onChange({
title: 'Clear "To reply before" field and remove “Overdue" tag when issue is not “New" or “Open"',
guard: (ctx) => {
const fs = ctx.issue.fields;
return fs.isChanged(ctx.State) && fs.State &&
fs.State.name !== ctx.State.New.name &&
fs.State.name !== ctx.State.Open.name;
},
action: (ctx) => {
const issue = ctx.issue;
issue.fields.ToReply = null;
if (issue.hasTag(ctx.overdue.name)) {
issue.removeTag(ctx.overdue.name);
}
},
requirements: {
State: {
type: entities.State.fieldType,
Open: {},
New: {}
},
ToReply: {
type: entities.Field.dateTimeType,
name: 'To reply before'
},
overdue: {
type: entities.IssueTag
}
}
});
You can apply additional logic to these rules depending on the terms of your support SLA, for example:
You can assign priorities to each ticket and change the SLA response times based on the priority.
You can set up a state machine to restrict the legal transitions between values for the State field.
If you have an international team that works in different time zones and business hours, you can tune the time-operations
script to take these into account for each member of the support team.
Assign Tickets with a Round-robin Rotation
Another popular helpdesk practice is to assign new tickets based on a round-robin scheme. This balances the workload between members of the support team. The idea is simple — whenever a new ticket appears, it is assigned to the team member who is assigned the fewest New and Open tickets.
The implementation is straight-forward. When a new ticket is created, the workflow rule checks the workload of each support engineer. The member of the team with the fewest tickets is selected as the assignee.
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const search = require('@jetbrains/youtrack-scripting-api/search');
exports.rule = entities.Issue.onChange({
title: 'Set Assignee automatically via Round Robin scheme',
guard: (ctx) => {
const issue = ctx.issue;
return issue.becomesReported && !issue.fields.Assignee;
},
action: (ctx) => {
const assignees = ctx.Assignee.values;
const numbers = {};
assignees.forEach(function (assignee) {
numbers[assignee.login] = 0;
});
const issues = search.search(ctx.issue.project, '#unresolved has: Assignee');
issues.forEach(function (issue) {
numbers[issue.fields.Assignee.login] += 1;
});
let min = Number.MAX_VALUE;
let user = null;
assignees.forEach(function (assignee) {
if (numbers[assignee.login] < min) {
min = numbers[assignee.login];
user = assignee;
}
});
ctx.issue.fields.Assignee = user;
},
requirements: {
Assignee: {
type: entities.User.fieldType
}
}
});
You can use this scheme not just for the helpdesk, but also for any process where you want to balance the workload. Also, the rules for balancing new assignments can be much more complex. For example, you can assign higher weights to overdue tickets.
Last modified: 21 April 2023