class JiraIssue

Constants

AFFECTED_USER_GROUP_FIELD
API_OVER_SLA_FIELD_NO
API_OVER_SLA_FIELD_YES
API_OVER_TRIAGE_SLA_FIELD_NO
API_OVER_TRIAGE_SLA_FIELD_YES
FIVE_P_DATE

The FIVE_P_DATE variable exists for tickets created before 2015-03-06 15:55:16 -0500. Not used for any tickets after that. Tickets created after above date use differnt SLA measurements.

OVER_SLA_FIELD
OVER_SLA_FIELD_NO
OVER_SLA_FIELD_YES
OVER_TRIAGE_SLA_FIELD
OVER_TRIAGE_SLA_FIELD_NO
OVER_TRIAGE_SLA_FIELD_YES
SLA_CLOSED_AT_FIELD
SLA_DUE_TIME_FIELD
SLA_DUE_WARNING_FIELD
SLA_DUE_WARNING_FIELD_SET
SLA_TRIAGED_AT_FIELD
STORY_POINTS_FIELD
TOTAL_TIME_OVER_SLA_FIELD
TRIAGER_FIELD

Attributes

affected_user_group[RW]
assigned_team[RW]
cant_reproduce[RW]
changelog[RW]
completed[RW]
created_at[RW]
deploy[RW]
description[RW]
in_progress[RW]
key[RW]
labels[RW]
merged[RW]
merged_st[RW]
not_accepted[RW]
on_production[RW]
orig[RW]
over_sla[RW]
over_triage_sla[RW]
pr_review[RW]
priority_name[RW]
project[RW]
raw_sprints[RW]
requester_denied[RW]
requester_review[RW]
resolved[RW]
reverify[RW]
sla_closed_at[RW]
sla_due_time[RW]
sla_due_warning[RW]
sla_triaged_at[RW]
status[RW]
story_points[RW]
summary[RW]
total_time_over_sla[RW]
triaged[RW]
triager[RW]
type[RW]
url[RW]
verified[RW]

Public Class Methods

custom_time_fields() click to toggle source
# File lib/jira_issue.rb, line 94
def self.custom_time_fields
  Hash[[
    [10812, "Support xt Can't Reproduce"],
    [10808, 'Support xt In Progress'],
    [10809, 'Support xt Not Accepted'],
    [10817, 'Support xt Requester Denied'],
    [10814, 'Support xt Requester Review'],
    [13101, 'Support xt Merged'],
    [13102, 'Support xt On Production'],
    [10816, 'Support xt Resolved'],
    [10815, 'Support xt Reverify'],
    [10813, 'Support xt Review'],
    [10810, 'Support xt Triaged'],
    [10811, 'Support xt Verified']
  ].map{|k,v| ["customfield_#{k}", v]}]
end
find(query, offset = 0) click to toggle source
# File lib/jira_issue.rb, line 80
def self.find(query, offset = 0)
  ask = 100
  result = Jiralicious.search(query, start_at: offset, max_results: ask)
  issues = result.issues_raw
  issues.map! { |i| new(i) }
  if issues.count == ask && result.num_results > offset + ask
    issues += find(query, offset + ask)
  end
  issues
rescue Jiralicious::JqlError => e
  $stderr.puts "Error: #{e}"
  []
end
new(issue) click to toggle source
# File lib/jira_issue.rb, line 40
def initialize(issue)
  issue.extend(DotNotation)
  self.key                 = issue.dot 'key'
  self.created_at          = Time.parse(issue.dot('fields.created'))
  self.type                = issue.dot 'fields.issuetype.name'
  self.priority_name       = issue.dot 'fields.priority.name'
  self.status              = issue.dot('fields.status.name').downcase
  self.project             = issue.dot 'fields.project.key'
  self.description         = issue.dot 'fields.description'
  self.summary             = issue.dot 'fields.summary'
  self.labels              = issue.dot 'fields.labels'
  self.assigned_team       = issue.dot 'fields.customfield_12601.value'
  self.story_points        = issue.dot "fields.#{STORY_POINTS_FIELD}"
  self.orig                = issue
  self.url                 = "https://optoro.atlassian.net/browse/#{key}"
  self.raw_sprints         = issue.dot 'fields.customfield_10007'
  self.sla_triaged_at      = Time.parse(issue.dot("fields.#{SLA_TRIAGED_AT_FIELD}")) if issue.dot("fields.#{SLA_TRIAGED_AT_FIELD}").present?
  self.over_triage_sla     = issue.dot "fields.#{OVER_TRIAGE_SLA_FIELD}"
  self.triager             = issue.dot "fields.#{TRIAGER_FIELD}.name"
  self.sla_due_time        = Time.parse(issue.dot("fields.#{SLA_DUE_TIME_FIELD}")) if issue.dot("fields.#{SLA_DUE_TIME_FIELD}").present?
  self.sla_due_warning     = issue.dot "fields.#{SLA_DUE_WARNING_FIELD}"
  self.sla_closed_at       = Time.parse(issue.dot("fields.#{SLA_CLOSED_AT_FIELD}")) if issue.dot("fields.#{SLA_CLOSED_AT_FIELD}").present?
  self.over_sla            = issue.dot "fields.#{OVER_SLA_FIELD}"
  self.total_time_over_sla = issue.dot "fields.#{TOTAL_TIME_OVER_SLA_FIELD}"
  self.affected_user_group = issue.dot "fields.#{AFFECTED_USER_GROUP_FIELD}"

  inv = self.class.custom_time_fields.invert
  inv.each do |name, key|
    x = name.gsub(/'/,'').gsub(/ +/, '_').downcase.gsub(/support_xt_/,'')
    v = issue.dot "fields.#{key}"
    v = Time.parse(v) if v
    x = "pr_review" if x == "review" # rename
    send("#{x}=", v)
  end
end

Public Instance Methods

affected_user_groups() click to toggle source
# File lib/jira_issue.rb, line 159
def affected_user_groups
  affected_user_group.map {|user_group| user_group["value"]} unless affected_user_group.nil?
end
affects_client_corporate_users?() click to toggle source
# File lib/jira_issue.rb, line 173
def affects_client_corporate_users?
  return false if affected_user_groups.nil?
  affected_user_groups.include?("Client Corporate Users")
end
affects_customers?() click to toggle source
# File lib/jira_issue.rb, line 168
def affects_customers?
  return false if affected_user_groups.nil?
  affected_user_groups.include?("Customers")
end
affects_internal_optoro_users?() click to toggle source
# File lib/jira_issue.rb, line 178
def affects_internal_optoro_users?
  return false if affected_user_groups.nil?
  affected_user_groups.include?("Internal Optoro Users")
end
affects_warehouse_users?() click to toggle source
# File lib/jira_issue.rb, line 163
def affects_warehouse_users?
  return false if affected_user_groups.nil?
  affected_user_groups.include?("WH Users")
end
bug?() click to toggle source
# File lib/jira_issue.rb, line 143
def bug?
  type == "Bug / Outage"
end
changelog_simple() click to toggle source
# File lib/jira_issue.rb, line 228
def changelog_simple
  changelog.map { |c|
    c["items"].map { |i|
      i.slice("field", "fromString","toString")
       .merge({
         "created" => c["created"],
         "author_out" => c["author"]["name"] || ""
       })
    }
  }.flatten
end
closed?() click to toggle source
# File lib/jira_issue.rb, line 117
def closed?
  [ "requester review",
    "merged",
    "merged_st",
    "on production",
    "resolved",
    "not accepted",
    "can't reproduce",
    "deploy",
    "completed"
  ].include? status
end
closed_at() click to toggle source
# File lib/jira_issue.rb, line 130
def closed_at
  [ requester_review,
    merged,
    merged_st,
    on_production,
    resolved,
    not_accepted,
    cant_reproduce,
    deploy,
    completed
  ].compact.min
end
feature?() click to toggle source
# File lib/jira_issue.rb, line 151
def feature?
  type == "Feature Enhancement"
end
jiralicious_issue() click to toggle source
# File lib/jira_issue.rb, line 76
def jiralicious_issue
  Jiralicious::Issue.find(key)
end
open?() click to toggle source
# File lib/jira_issue.rb, line 111
def open?
  !closed?
  # ["unverified", "triaged", "verified",
  #  "in progress", "pr_review", "pr review"].include? status
end
operational?() click to toggle source
# File lib/jira_issue.rb, line 147
def operational?
  type == "Operational Request"
end
priority() click to toggle source
# File lib/jira_issue.rb, line 183
def priority
  case priority_name
  when /P1/
    :p1
  when /P2/
    :p2
  when /P3/
    :p3
  when /P4/
    :p4
  else
    :p5
  end
end
research?() click to toggle source
# File lib/jira_issue.rb, line 155
def research?
  type == "Research"
end
sprints() click to toggle source
# File lib/jira_issue.rb, line 214
def sprints
  if raw_sprints
    raw_sprints.map {|s| Hash[s.match(/\[(.*)\]/)[1].split(',').map{|f|f.split('=')}]}
  else
    []
  end
end
status_ages() click to toggle source
# File lib/jira_issue.rb, line 270
def status_ages
  ages = {}
  status_changes.each{|c| ages[c[:status]] = ages.fetch(c[:status], 0) + c[:age_hours].to_r.round(2).to_f}
  ages
end
status_changes() click to toggle source
# File lib/jira_issue.rb, line 240
def status_changes
  status_changes = changelog_simple.select{|c| c["field"] == "status"}
  start = created_at
  author = orig["fields"]["reporter"].andand["name"] || ""
  status_changes.each { |c|
    c["age_hours"] = (Time.parse(c["created"]).to_i - start.to_i)/60.0/60
    c["started"] = start
    start = Time.parse(c["created"])
    cur_author = author
    author = c["author_out"]
    c["author_in"] = cur_author
  }
  statuses = status_changes.map { |s|
    {
      status: s["fromString"],
      start: s["started"],
      end: Time.parse(s["created"]),
      author_in: s["author_in"],
      author_out: s["author_out"],
      age_hours: s["age_hours"]
    }
  }
  statuses << {
    status: status,
    start: start,
    author_in: author
  }

end
status_duration_during(status, start_date, end_date) click to toggle source
# File lib/jira_issue.rb, line 276
def status_duration_during(status, start_date, end_date)
  ages = {}
  status_changes.each{|c| ages[c["fromString"]] = ages.fetch(c["fromString"],0) + c["age_hours"]}
  status_changes.each{|c| ages[c[:status]] = ages.fetch(c[:status], 0) + c[:age_hours].to_r.round(2).to_f}
  last_status = status_changes.last
  if last_status
    ages[last_status["toString"]] =
      ages.fetch(last_status["toString"], 0) +
      ((Time.now.to_f - Time.parse(last_status["created"]).to_f)/60.0/60)
  end
  ages
end
time_to_qa() click to toggle source
# File lib/jira_issue.rb, line 198
def time_to_qa
  if triaged && (verified || cant_reproduce || in_progress)
    if verified
      verified - triaged
    elsif cant_reproduce
      cant_reproduce - triaged
    else
      in_progress - triaged
    end
  elsif triaged
    Time.now - triaged
  else
    nil
  end
end