logoBack to App page
SLA Time Management
Documentation
Migration to Cloud
FAQ
Release notes
SLA/JSM comparison
SLA Time Management
Documentation
Migration to Cloud
FAQ
Release notes
SLA/JSM comparison
Last updated Jul 27, 2025

Other apps compatibility

Learn about apps and functionalities that can be used with SLA Time Management.

Deviniti - My Requests Extension for Jira Service Management

The SLA Time Management app is compatible with My Requests Extension for Jira Service Management - it enables users to display the SLA field as Columns on the Requests page.

Configure an SLA
Configure an SLA

Deviniti - Queues for Jira & JSM

The SLA Time Management app is compatible with Queues for Jira & JSM - it enables users to display the SLA field in a column.

With JQL you can find a specific SLA value. You can create a new queue that will filter, for example, issues that are close to breaching an SLA. You may also configure notifications within Queues based on JQL and SLA values.

Configure an SLA
Configure an SLA

eazyBI

This chapter explains how to integrate SLA Time Management with eazyBI to gain insights into your SLA performance. By combining the tracking power of the SLA app with eazyBI’s advanced reporting and visualization capabilities, you can build custom dashboards, charts, and reports to monitor SLA compliance, identify trends, and support data-driven decision-making.

Steps

  1. Navigate to Settings in eazyBI and go to Advanced Settings.
  2. Copy the configuration below. Replace XXXXXX with the custom field ID that was created by the SLA app and YYYYYY with the name of this custom field (navigate to Project Settings>Fields to find the name of your field).
[customfield_XXXXXX_times_started]
name = "YYYYYY - Times started"
data_type = "integer"
dimension = false
property = true
measure = true

javascript_code = '''
// Check if the custom field exists and has a value
if (!issue.fields.customfield_XXXXXX) {
  return 0;
}

try {
  // Parse the JSON object from the custom field
  let timeTrackingData;
  if (typeof issue.fields.customfield_XXXXXX === 'string') {
    timeTrackingData = JSON.parse(issue.fields.customfield_XXXXXX);
  } else {
    timeTrackingData = issue.fields.customfield_XXXXXX;
  }
  
  // Check if changeLogs array exists
  if (!timeTrackingData.changeLogs || !Array.isArray(timeTrackingData.changeLogs)) {
    return 0;
  }
  
  // Count events with type "start"
  let startEventCount = 0;
  for (let i = 0; i < timeTrackingData.changeLogs.length; i++) {
    if (timeTrackingData.changeLogs[i].type === "start") {
      startEventCount++;
    }
  }
  
  return startEventCount;
  
} catch (error) {
  // Return 0 if JSON parsing fails or any other error occurs
  return 0;
}
'''

json_fields = ["customfield_XXXXXX"]

[customfield_XXXXXX_times_stopped]
name = "YYYYYY - Times stopped"
data_type = "integer"
dimension = false
property = true
measure = true

javascript_code = '''
// Check if the custom field exists and has a value
if (!issue.fields.customfield_XXXXXX) {
  return 0;
}

try {
  // Parse the JSON object from the custom field
  let timeTrackingData;
  if (typeof issue.fields.customfield_XXXXXX === 'string') {
    timeTrackingData = JSON.parse(issue.fields.customfield_XXXXXX);
  } else {
    timeTrackingData = issue.fields.customfield_XXXXXX;
  }
  
  // Check if changeLogs array exists
  if (!timeTrackingData.changeLogs || !Array.isArray(timeTrackingData.changeLogs)) {
    return 0;
  }
  
  // Count events with type "end"
  let endEventCount = 0;
  for (let i = 0; i < timeTrackingData.changeLogs.length; i++) {
    if (timeTrackingData.changeLogs[i].type === "end") {
      endEventCount++;
    }
  }
  
  return endEventCount;
  
} catch (error) {
  // Return 0 if JSON parsing fails or any other error occurs
  return 0;
}
'''

json_fields = ["customfield_XXXXXX"]

[customfield_XXXXXX_time_elapsed]
name = "YYYYYY - Time Elapsed"
data_type = "integer"
dimension = false
property = true
measure = true

javascript_code = '''
// Get the object from custom field XXXXXX
var timeTrackingObject = issue.fields.customfield_XXXXXX;

// Check if the object exists and has spentTimeMillis property
if (timeTrackingObject && timeTrackingObject.spentTimeMillis !== undefined) {
  // Convert milliseconds to seconds by dividing by 1000
  return timeTrackingObject.spentTimeMillis / 1000;
}

// Return null if the field or property doesn't exist
return null;
'''

json_fields = ["customfield_XXXXXX"]

[customfield_xxxxxx_first_response]
name = "YYYYYY - First response time"
data_type = "integer"
dimension = false
property = true
measure = true

javascript_code = '''
// Get the custom field value
let fieldValue = issue.fields.customfield_XXXXXX;

// Check if field exists and has changeLogs
if (!fieldValue || !fieldValue.changeLogs || !Array.isArray(fieldValue.changeLogs)) {
  return null;
}

let earliestStart = null;
let earliestEnd = null;

// Iterate through changeLogs to find earliest start and end timestamps
for (let i = 0; i < fieldValue.changeLogs.length; i++) {
  let changeLog = fieldValue.changeLogs[i];
  
  if (changeLog.type === "start") {
    if (earliestStart === null || changeLog.timestamp < earliestStart) {
      earliestStart = changeLog.timestamp;
    }
  } else if (changeLog.type === "end") {
    if (earliestEnd === null || changeLog.timestamp < earliestEnd) {
      earliestEnd = changeLog.timestamp;
    }
  }
}

// Check if both start and end events were found
if (earliestStart === null || earliestEnd === null) {
  return null;
}

// Calculate difference in milliseconds and convert to seconds
let differenceMs = earliestEnd - earliestStart;
let differenceSeconds = Math.floor(differenceMs / 1000);

return differenceSeconds;
'''

json_fields = ["customfield_XXXXXX"]

[customfield_XXXXXX_goal]
name = "YYYYYY - Goal"
data_type = "integer"
dimension = false
property = true
measure = true

javascript_code = '''
// Get the object value from customfield_XXXXXX
var fieldValue = issue.fields.customfield_XXXXXX;

// Check if the field has a value and the goalDurationMillis property exists
if (fieldValue && fieldValue.hasOwnProperty('goalDurationMillis')) {
  // Convert milliseconds to seconds
  return fieldValue.goalDurationMillis / 1000;
} else {
  return null;
}
'''

json_fields = ["customfield_XXXXXX"]

[customfield_XXXXXX_was_goal_breached]
name = "YYYYYY - was goal breached?"
data_type = "string"
dimension = true
property = true
measure = false

javascript_code = '''
// Get the value from the custom field
var timeAnalysisData = issue.fields.customfield_XXXXXX;

// Check if the field has a value
if (!timeAnalysisData) {
  return null;
}

try {
  // If the value is already a JavaScript object, use it directly
  // Otherwise, try to parse it as JSON
  var parsedData = typeof timeAnalysisData === 'object' ? 
    timeAnalysisData : JSON.parse(timeAnalysisData);
  
  // Extract and return the isBreached value as a string if it exists
  if (parsedData && parsedData.isBreached !== undefined) {
    return parsedData.isBreached === true ? "true" : "false";
  } else {
    return null;
  }
} catch (e) {
  // Handle JSON parsing errors
  return "Error parsing JSON: " + e.message;
}
'''

json_fields = ["customfield_XXXXXX"]

[customfield_XXXXXX_status]
name = "YYYYYY - status"
data_type = "string"
dimension = true
property = true
measure = false

javascript_code = '''
// Get the value from the custom field
var timeAnalysisData = issue.fields.customfield_XXXXXX;

// Check if the field has a value
if (!timeAnalysisData) {
  return null;
}

try {
  // If the value is already a JavaScript object, use it directly
  // Otherwise, try to parse it as JSON
  var parsedData = typeof timeAnalysisData === 'object' ? 
    timeAnalysisData : JSON.parse(timeAnalysisData);
  
  // Extract the textValue if it exists
  if (parsedData && parsedData.textValue) {
    var textValue = parsedData.textValue;
    
    // Find the position of the first comma
    var commaIndex = textValue.indexOf(',');
    
    // If a comma exists, return only the text up to that comma
    // Otherwise return the entire textValue
    if (commaIndex !== -1) {
      return textValue.substring(0, commaIndex);
    } else {
      return textValue;
    }
  } else {
    return null;
  }
} catch (e) {
  // Handle JSON parsing errors
  return "Error parsing JSON: " + e.message;
}
'''

json_fields = ["customfield_XXXXXX"]
  1. Fields related to that SLA will be available in the import configuration in the Custom Fields tab.
Available field Explanation
First response time Time measured from the first SLA launch to the first stop.
Goal Goal value measured in seconds.
Timer status Time status of an SLA for a specific issue.
Time elapsed Total time of an SLA for a specific issue.
Times started How many times SLA time counting had started for a specific issue.
Times stopped How many times SLA time counting had stopped for a specific issue.
Was goal breached? True/false value that shows whether the goal was breached.
Info

You can import these values as a Property, Measure and in some cases - Dimension.

This allows you to create different reports that help you track SLAs for certain items.