Class: JiraIssue

Inherits:
Object
  • Object
show all
Includes:
SLATracker, TriageTracker
Defined in:
lib/jira_issue.rb,
lib/jira_reporting/sla_tracker.rb,
lib/jira_reporting/triage_tracker.rb

Defined Under Namespace

Modules: SLATracker, TriageTracker

Constant Summary collapse

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.

Time.at(142_567_531_6).freeze
STORY_POINTS_FIELD =
'customfield_10007'.freeze
TRIAGER_FIELD =
'customfield_14100'.freeze
SLA_TRIAGED_AT_FIELD =
'customfield_14400'.freeze
OVER_TRIAGE_SLA_FIELD =
'customfield_14200'.freeze
OVER_TRIAGE_SLA_FIELD_YES =
'12500'.freeze
OVER_TRIAGE_SLA_FIELD_NO =
'12501'.freeze
API_OVER_TRIAGE_SLA_FIELD_YES =
{"self"=>"https://optoro.atlassian.net/rest/api/2/customFieldOption/12500", "value"=>"Yes", "id"=>"12500"}.freeze
API_OVER_TRIAGE_SLA_FIELD_NO =
{"self"=>"https://optoro.atlassian.net/rest/api/2/customFieldOption/12501", "value"=>"No", "id"=>"12501"}.freeze
SLA_DUE_TIME_FIELD =
'customfield_12900'.freeze
SLA_DUE_WARNING_FIELD =
'customfield_13401'.freeze
SLA_DUE_WARNING_FIELD_SET =
'12100'.freeze
SLA_CLOSED_AT_FIELD =
'customfield_14000'.freeze
OVER_SLA_FIELD =
'customfield_12901'.freeze
OVER_SLA_FIELD_YES =
'11901'.freeze
OVER_SLA_FIELD_NO =
'11900'.freeze
TOTAL_TIME_OVER_SLA_FIELD =
'customfield_14201'.freeze
API_OVER_SLA_FIELD_YES =
{"self"=>"https://optoro.atlassian.net/rest/api/2/customFieldOption/11901", "value"=>"Yes", "id"=>"11901"}.freeze
API_OVER_SLA_FIELD_NO =
{"self"=>"https://optoro.atlassian.net/rest/api/2/customFieldOption/11900", "value"=>"No", "id"=>"11900"}.freeze
AFFECTED_USER_GROUP_FIELD =
'customfield_13602'.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TriageTracker

#over_triage_sla?, #set_over_triage_sla!, #set_sla_triaged_at!, #time_to_triage, #triage_sla_remaining_time, #triage_sla_target, #triage_sla_total_available_time

Methods included from SLATracker

#approaching_sla_due_time?, #over_sla?, #resolution_base, #set_over_sla!, #set_sla_closed_at!, #set_sla_due_time!, #set_sla_due_warning!, #set_total_time_over_sla!, #sla_base, #sla_diff, #sla_remaining_time, #sla_target, #sla_time_ratio, #sla_total_available_time, #sla_warning_time, #sum_total_time_over_sla, #time_to_finish_work, #time_to_start_work, #total_time_to_resolution

Constructor Details

#initialize(issue) ⇒ JiraIssue

Returns a new instance of JiraIssue.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# 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

Instance Attribute Details

#affected_user_groupObject

Returns the value of attribute affected_user_group.



30
31
32
# File 'lib/jira_issue.rb', line 30

def affected_user_group
  @affected_user_group
end

#assigned_teamObject

Returns the value of attribute assigned_team.



30
31
32
# File 'lib/jira_issue.rb', line 30

def assigned_team
  @assigned_team
end

#cant_reproduceObject

Returns the value of attribute cant_reproduce.



30
31
32
# File 'lib/jira_issue.rb', line 30

def cant_reproduce
  @cant_reproduce
end

#changelogObject

Returns the value of attribute changelog.



30
31
32
# File 'lib/jira_issue.rb', line 30

def changelog
  @changelog
end

#completedObject

Returns the value of attribute completed.



30
31
32
# File 'lib/jira_issue.rb', line 30

def completed
  @completed
end

#created_atObject

Returns the value of attribute created_at.



30
31
32
# File 'lib/jira_issue.rb', line 30

def created_at
  @created_at
end

#deployObject

Returns the value of attribute deploy.



30
31
32
# File 'lib/jira_issue.rb', line 30

def deploy
  @deploy
end

#descriptionObject

Returns the value of attribute description.



30
31
32
# File 'lib/jira_issue.rb', line 30

def description
  @description
end

#in_progressObject

Returns the value of attribute in_progress.



30
31
32
# File 'lib/jira_issue.rb', line 30

def in_progress
  @in_progress
end

#keyObject

Returns the value of attribute key.



30
31
32
# File 'lib/jira_issue.rb', line 30

def key
  @key
end

#labelsObject

Returns the value of attribute labels.



30
31
32
# File 'lib/jira_issue.rb', line 30

def labels
  @labels
end

#mergedObject

Returns the value of attribute merged.



30
31
32
# File 'lib/jira_issue.rb', line 30

def merged
  @merged
end

#merged_stObject

Returns the value of attribute merged_st.



30
31
32
# File 'lib/jira_issue.rb', line 30

def merged_st
  @merged_st
end

#not_acceptedObject

Returns the value of attribute not_accepted.



30
31
32
# File 'lib/jira_issue.rb', line 30

def not_accepted
  @not_accepted
end

#on_productionObject

Returns the value of attribute on_production.



30
31
32
# File 'lib/jira_issue.rb', line 30

def on_production
  @on_production
end

#origObject

Returns the value of attribute orig.



30
31
32
# File 'lib/jira_issue.rb', line 30

def orig
  @orig
end

#over_slaObject

Returns the value of attribute over_sla.



30
31
32
# File 'lib/jira_issue.rb', line 30

def over_sla
  @over_sla
end

#over_triage_slaObject

Returns the value of attribute over_triage_sla.



30
31
32
# File 'lib/jira_issue.rb', line 30

def over_triage_sla
  @over_triage_sla
end

#pr_reviewObject

Returns the value of attribute pr_review.



30
31
32
# File 'lib/jira_issue.rb', line 30

def pr_review
  @pr_review
end

#priority_nameObject

Returns the value of attribute priority_name.



30
31
32
# File 'lib/jira_issue.rb', line 30

def priority_name
  @priority_name
end

#projectObject

Returns the value of attribute project.



30
31
32
# File 'lib/jira_issue.rb', line 30

def project
  @project
end

#raw_sprintsObject

Returns the value of attribute raw_sprints.



30
31
32
# File 'lib/jira_issue.rb', line 30

def raw_sprints
  @raw_sprints
end

#requester_deniedObject

Returns the value of attribute requester_denied.



30
31
32
# File 'lib/jira_issue.rb', line 30

def requester_denied
  @requester_denied
end

#requester_reviewObject

Returns the value of attribute requester_review.



30
31
32
# File 'lib/jira_issue.rb', line 30

def requester_review
  @requester_review
end

#resolvedObject

Returns the value of attribute resolved.



30
31
32
# File 'lib/jira_issue.rb', line 30

def resolved
  @resolved
end

#reverifyObject

Returns the value of attribute reverify.



30
31
32
# File 'lib/jira_issue.rb', line 30

def reverify
  @reverify
end

#sla_closed_atObject

Returns the value of attribute sla_closed_at.



30
31
32
# File 'lib/jira_issue.rb', line 30

def sla_closed_at
  @sla_closed_at
end

#sla_due_timeObject

Returns the value of attribute sla_due_time.



30
31
32
# File 'lib/jira_issue.rb', line 30

def sla_due_time
  @sla_due_time
end

#sla_due_warningObject

Returns the value of attribute sla_due_warning.



30
31
32
# File 'lib/jira_issue.rb', line 30

def sla_due_warning
  @sla_due_warning
end

#sla_triaged_atObject

Returns the value of attribute sla_triaged_at.



30
31
32
# File 'lib/jira_issue.rb', line 30

def sla_triaged_at
  @sla_triaged_at
end

#statusObject

Returns the value of attribute status.



30
31
32
# File 'lib/jira_issue.rb', line 30

def status
  @status
end

#story_pointsObject

Returns the value of attribute story_points.



30
31
32
# File 'lib/jira_issue.rb', line 30

def story_points
  @story_points
end

#summaryObject

Returns the value of attribute summary.



30
31
32
# File 'lib/jira_issue.rb', line 30

def summary
  @summary
end

#total_time_over_slaObject

Returns the value of attribute total_time_over_sla.



30
31
32
# File 'lib/jira_issue.rb', line 30

def total_time_over_sla
  @total_time_over_sla
end

#triagedObject

Returns the value of attribute triaged.



30
31
32
# File 'lib/jira_issue.rb', line 30

def triaged
  @triaged
end

#triagerObject

Returns the value of attribute triager.



30
31
32
# File 'lib/jira_issue.rb', line 30

def triager
  @triager
end

#typeObject

Returns the value of attribute type.



30
31
32
# File 'lib/jira_issue.rb', line 30

def type
  @type
end

#urlObject

Returns the value of attribute url.



30
31
32
# File 'lib/jira_issue.rb', line 30

def url
  @url
end

#verifiedObject

Returns the value of attribute verified.



30
31
32
# File 'lib/jira_issue.rb', line 30

def verified
  @verified
end

Class Method Details

.custom_time_fieldsObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# 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) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
# 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

Instance Method Details

#affected_user_groupsObject



159
160
161
# 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?Boolean

Returns:

  • (Boolean)


173
174
175
176
# 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?Boolean

Returns:

  • (Boolean)


168
169
170
171
# 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?Boolean

Returns:

  • (Boolean)


178
179
180
181
# 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?Boolean

Returns:

  • (Boolean)


163
164
165
166
# 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?Boolean

Returns:

  • (Boolean)


143
144
145
# File 'lib/jira_issue.rb', line 143

def bug?
  type == "Bug / Outage"
end

#changelog_simpleObject



228
229
230
231
232
233
234
235
236
237
238
# 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?Boolean

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
125
126
127
128
# 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_atObject



130
131
132
133
134
135
136
137
138
139
140
141
# 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?Boolean

Returns:

  • (Boolean)


151
152
153
# File 'lib/jira_issue.rb', line 151

def feature?
  type == "Feature Enhancement"
end

#jiralicious_issueObject



76
77
78
# File 'lib/jira_issue.rb', line 76

def jiralicious_issue
  Jiralicious::Issue.find(key)
end

#open?Boolean

Returns:

  • (Boolean)


111
112
113
114
115
# File 'lib/jira_issue.rb', line 111

def open?
  !closed?
  # ["unverified", "triaged", "verified",
  #  "in progress", "pr_review", "pr review"].include? status
end

#operational?Boolean

Returns:

  • (Boolean)


147
148
149
# File 'lib/jira_issue.rb', line 147

def operational?
  type == "Operational Request"
end

#priorityObject



183
184
185
186
187
188
189
190
191
192
193
194
195
196
# 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?Boolean

Returns:

  • (Boolean)


155
156
157
# File 'lib/jira_issue.rb', line 155

def research?
  type == "Research"
end

#sprintsObject



214
215
216
217
218
219
220
# 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_agesObject



270
271
272
273
274
# 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_changesObject



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# 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) ⇒ Object



276
277
278
279
280
281
282
283
284
285
286
287
# 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_qaObject



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# 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