Class: ProjectConfig
- Includes:
- DiscardChangesBefore
- Defined in:
- lib/jirametrics/project_config.rb
Instance Attribute Summary collapse
-
#all_boards ⇒ Object
readonly
Returns the value of attribute all_boards.
-
#board_configs ⇒ Object
readonly
Returns the value of attribute board_configs.
-
#data_version ⇒ Object
readonly
Returns the value of attribute data_version.
-
#download_config ⇒ Object
readonly
Returns the value of attribute download_config.
-
#exporter ⇒ Object
readonly
Returns the value of attribute exporter.
-
#file_configs ⇒ Object
readonly
Returns the value of attribute file_configs.
-
#jira_config ⇒ Object
readonly
Returns the value of attribute jira_config.
-
#jira_url ⇒ Object
Returns the value of attribute jira_url.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#possible_statuses ⇒ Object
readonly
Returns the value of attribute possible_statuses.
-
#settings ⇒ Object
readonly
Returns the value of attribute settings.
-
#target_path ⇒ Object
readonly
Returns the value of attribute target_path.
-
#time_range ⇒ Object
Returns the value of attribute time_range.
Instance Method Summary collapse
-
#add_issues(issues_list) ⇒ Object
To be used by the aggregate_config only.
- #add_possible_status(status) ⇒ Object
- #aggregate(&block) ⇒ Object
- #aggregated_project? ⇒ Boolean
- #anonymize ⇒ Object
- #anonymize_data ⇒ Object
- #attach_linked_issues(issue:, all_issues:) ⇒ Object
- #attach_parent(issue:, all_issues:) ⇒ Object
- #attach_subtasks(issue:, all_issues:) ⇒ Object
- #board(id:, &block) ⇒ Object
- #discard_changes_before_hook(issues_cutoff_times) ⇒ Object
- #download(&block) ⇒ Object
- #evaluate_next_level ⇒ Object
- #file(&block) ⇒ Object
- #file_prefix(prefix = nil) ⇒ Object
- #find_board_by_id(board_id = nil) ⇒ Object
- #find_default_board ⇒ Object
- #find_status(name:) ⇒ Object
-
#group_filenames_and_board_ids(path:) ⇒ Object
Scan through the issues directory (path), select the filenames to be loaded and map them to board ids.
- #guess_board_id ⇒ Object
-
#initialize(exporter:, jira_config:, block:, target_path: '.', name: '') ⇒ ProjectConfig
constructor
A new instance of ProjectConfig.
- #issues ⇒ Object
- #load_all_boards ⇒ Object
- #load_board(board_id:, filename:) ⇒ Object
- #load_issues_from_issues_directory(path:, timezone_offset:) ⇒ Object
- #load_issues_from_target_directory(path:, timezone_offset:) ⇒ Object
- #load_project_metadata ⇒ Object
- #load_sprints ⇒ Object
- #load_status_category_mappings ⇒ Object
- #raise_with_message_about_missing_category_information ⇒ Object
- #run ⇒ Object
- #status_category_mapping(status:, category:, type: nil) ⇒ Object
- #to_time(string) ⇒ Object
Methods included from DiscardChangesBefore
Constructor Details
#initialize(exporter:, jira_config:, block:, target_path: '.', name: '') ⇒ ProjectConfig
Returns a new instance of ProjectConfig.
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/jirametrics/project_config.rb', line 13 def initialize exporter:, jira_config:, block:, target_path: '.', name: '' @exporter = exporter @block = block @file_configs = [] @download_config = nil @target_path = target_path @jira_config = jira_config @possible_statuses = StatusCollection.new @name = name @board_configs = [] @settings = { 'stalled_threshold' => 5, 'blocked_statuses' => [], 'stalled_statuses' => [], 'blocked_link_text' => [], 'colors' => { 'stalled' => 'orange', 'blocked' => '#FF7400' } } end |
Instance Attribute Details
#all_boards ⇒ Object (readonly)
Returns the value of attribute all_boards.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def all_boards @all_boards end |
#board_configs ⇒ Object (readonly)
Returns the value of attribute board_configs.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def board_configs @board_configs end |
#data_version ⇒ Object (readonly)
Returns the value of attribute data_version.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def data_version @data_version end |
#download_config ⇒ Object (readonly)
Returns the value of attribute download_config.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def download_config @download_config end |
#exporter ⇒ Object (readonly)
Returns the value of attribute exporter.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def exporter @exporter end |
#file_configs ⇒ Object (readonly)
Returns the value of attribute file_configs.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def file_configs @file_configs end |
#jira_config ⇒ Object (readonly)
Returns the value of attribute jira_config.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def jira_config @jira_config end |
#jira_url ⇒ Object
Returns the value of attribute jira_url.
11 12 13 |
# File 'lib/jirametrics/project_config.rb', line 11 def jira_url @jira_url end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def name @name end |
#possible_statuses ⇒ Object (readonly)
Returns the value of attribute possible_statuses.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def possible_statuses @possible_statuses end |
#settings ⇒ Object (readonly)
Returns the value of attribute settings.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def settings @settings end |
#target_path ⇒ Object (readonly)
Returns the value of attribute target_path.
9 10 11 |
# File 'lib/jirametrics/project_config.rb', line 9 def target_path @target_path end |
#time_range ⇒ Object
Returns the value of attribute time_range.
11 12 13 |
# File 'lib/jirametrics/project_config.rb', line 11 def time_range @time_range end |
Instance Method Details
#add_issues(issues_list) ⇒ Object
To be used by the aggregate_config only. Not intended to be part of the public API
265 266 267 268 269 270 271 272 273 274 |
# File 'lib/jirametrics/project_config.rb', line 265 def add_issues issues_list @issues = [] if @issues.nil? @all_boards = {} if @all_boards.nil? issues_list.each do |issue| @issues << issue board = issue.board @all_boards[board.id] = board unless @all_boards[board.id] end end |
#add_possible_status(status) ⇒ Object
200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/jirametrics/project_config.rb', line 200 def add_possible_status status existing_status = find_status(name: status.name) if existing_status if existing_status.category_name != status.category_name raise "Redefining status category #{status} with #{existing_status}. Was one set in the config?" end return end @possible_statuses << status end |
#aggregate(&block) ⇒ Object
73 74 75 76 77 78 79 80 81 |
# File 'lib/jirametrics/project_config.rb', line 73 def aggregate &block raise 'Not allowed to have multiple aggregate blocks in one project' if @aggregate_config raise 'Not allowed to have both an aggregate and a download section. Pick only one.' if @download_config @aggregate_config = AggregateConfig.new project_config: self, block: block return if @exporter.downloading? @aggregate_config.evaluate_next_level end |
#aggregated_project? ⇒ Boolean
58 59 60 |
# File 'lib/jirametrics/project_config.rb', line 58 def aggregated_project? !!@aggregate_config end |
#anonymize ⇒ Object
421 422 423 |
# File 'lib/jirametrics/project_config.rb', line 421 def anonymize @anonymizer_needed = true end |
#anonymize_data ⇒ Object
425 426 427 |
# File 'lib/jirametrics/project_config.rb', line 425 def anonymize_data Anonymizer.new(project_config: self).run end |
#attach_linked_issues(issue:, all_issues:) ⇒ Object
325 326 327 328 329 330 331 332 |
# File 'lib/jirametrics/project_config.rb', line 325 def attach_linked_issues issue:, all_issues: issue.issue_links.each do |link| if link.other_issue.artificial? other = all_issues.find { |i| i.key == link.other_issue.key } link.other_issue = other if other end end end |
#attach_parent(issue:, all_issues:) ⇒ Object
319 320 321 322 323 |
# File 'lib/jirametrics/project_config.rb', line 319 def attach_parent issue:, all_issues: parent_key = issue.parent_key parent = all_issues.find { |i| i.key == parent_key } issue.parent = parent if parent end |
#attach_subtasks(issue:, all_issues:) ⇒ Object
311 312 313 314 315 316 317 |
# File 'lib/jirametrics/project_config.rb', line 311 def attach_subtasks issue:, all_issues: issue.raw['fields']['subtasks']&.each do |subtask_element| subtask_key = subtask_element['key'] subtask = all_issues.find { |i| i.key == subtask_key } issue.subtasks << subtask if subtask end end |
#board(id:, &block) ⇒ Object
83 84 85 86 |
# File 'lib/jirametrics/project_config.rb', line 83 def board id:, &block config = BoardConfig.new(id: id, block: block, project_config: self) @board_configs << config end |
#discard_changes_before_hook(issues_cutoff_times) ⇒ Object
429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/jirametrics/project_config.rb', line 429 def discard_changes_before_hook issues_cutoff_times issues_cutoff_times.each do |issue, cutoff_time| days = (cutoff_time.to_date - issue.changes.first.time.to_date).to_i + 1 = "#{issue.key}(#{issue.type}) discarding #{days} " if days == 1 << "day of data on #{cutoff_time.to_date}" else << "days of data from #{issue.changes.first.time.to_date} to #{cutoff_time.to_date}" end puts end puts "Discarded data from #{issues_cutoff_times.count} issues out of a total #{issues.size}" end |
#download(&block) ⇒ Object
62 63 64 65 66 67 |
# File 'lib/jirametrics/project_config.rb', line 62 def download &block raise 'Not allowed to have multiple download blocks in one project' if @download_config raise 'Not allowed to have both an aggregate and a download section. Pick only one.' if @aggregate_config @download_config = DownloadConfig.new project_config: self, block: block end |
#evaluate_next_level ⇒ Object
36 37 38 |
# File 'lib/jirametrics/project_config.rb', line 36 def evaluate_next_level instance_eval(&@block) end |
#file(&block) ⇒ Object
69 70 71 |
# File 'lib/jirametrics/project_config.rb', line 69 def file &block @file_configs << FileConfig.new(project_config: self, block: block) end |
#file_prefix(prefix = nil) ⇒ Object
88 89 90 91 |
# File 'lib/jirametrics/project_config.rb', line 88 def file_prefix prefix = nil @file_prefix = prefix unless prefix.nil? @file_prefix end |
#find_board_by_id(board_id = nil) ⇒ Object
256 257 258 259 260 261 262 |
# File 'lib/jirametrics/project_config.rb', line 256 def find_board_by_id board_id = nil board = all_boards[board_id || guess_board_id] raise "Unable to find configuration for board_id: #{board_id}" if board.nil? board end |
#find_default_board ⇒ Object
334 335 336 337 338 339 340 341 342 |
# File 'lib/jirametrics/project_config.rb', line 334 def find_default_board default_board = all_boards.values.first raise "No boards found for project #{name.inspect}" if all_boards.empty? if all_boards.size != 1 puts "Multiple boards are in use for project #{name.inspect}. Picked #{(default_board.name).inspect} to attach issues to." end default_board end |
#find_status(name:) ⇒ Object
214 215 216 |
# File 'lib/jirametrics/project_config.rb', line 214 def find_status name: @possible_statuses.find_by_name name end |
#group_filenames_and_board_ids(path:) ⇒ Object
Scan through the issues directory (path), select the filenames to be loaded and map them to board ids. It’s ok if there are multiple files for the same issue. We load the newest one and map all the other board ids appropriately.
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/jirametrics/project_config.rb', line 385 def group_filenames_and_board_ids path: hash = {} Dir.foreach(path) do |filename| # Matches either FAKE-123.json or FAKE-123-456.json if /^(?<key>[^-]+-\d+)(?<_>-(?<board_id>\d+))?\.json$/ =~ filename (hash[key] ||= []) << [filename, board_id&.to_i || :unknown] end end result = {} hash.values.collect do |list| if list.size == 1 filename, board_id = *list.first result[filename] = board_id == :unknown ? board_id : [board_id] else max_time = nil max_board_id = nil max_filename = nil all_board_ids = [] list.each do |filename, board_id| mtime = File.mtime(File.join(path, filename)) if max_time.nil? || mtime > max_time max_time = mtime max_board_id = board_id max_filename = filename end all_board_ids << board_id unless board_id == :unknown end result[max_filename] = all_board_ids end end result end |
#guess_board_id ⇒ Object
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/jirametrics/project_config.rb', line 239 def guess_board_id return nil if aggregated_project? unless all_boards&.size == 1 = "If the board_id isn't set then we look for all board configurations in the target" \ ' directory. ' if all_boards.nil? || all_boards.empty? += ' In this case, we couldn\'t find any configuration files in the target directory.' else += 'If there is only one, we use that. In this case we found configurations for' \ " the following board ids and this is ambiguous: #{all_boards.keys}" end raise end all_boards.keys[0] end |
#issues ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 |
# File 'lib/jirametrics/project_config.rb', line 276 def issues raise "issues are being loaded before boards in project #{name.inspect}" if all_boards.nil? && !aggregated_project? unless @issues if @aggregate_config raise 'This is an aggregated project and issues should have been included with the include_issues_from ' \ 'declaration but none are here. Check your config.' end timezone_offset = exporter.timezone_offset issues_path = "#{@target_path}#{file_prefix}_issues/" if File.exist?(issues_path) && File.directory?(issues_path) issues = load_issues_from_issues_directory path: issues_path, timezone_offset: timezone_offset elsif File.exist?(@target_path) && File.directory?(@target_path) issues = load_issues_from_target_directory path: @target_path, timezone_offset: timezone_offset else puts "Can't find issues in either #{path} or #{@target_path}" end # Attach related issues issues.each do |i| attach_subtasks issue: i, all_issues: issues attach_parent issue: i, all_issues: issues attach_linked_issues issue: i, all_issues: issues end # We'll have some issues that are in the list that weren't part of the initial query. Once we've # attached them in the appropriate places, remove any that aren't part of that initial set. @issues = issues.select { |i| i.in_initial_query? } end @issues end |
#load_all_boards ⇒ Object
105 106 107 108 109 110 111 112 113 |
# File 'lib/jirametrics/project_config.rb', line 105 def load_all_boards Dir.foreach(@target_path) do |file| next unless file =~ /^#{@file_prefix}_board_(\d+)_configuration\.json$/ board_id = $1.to_i load_board board_id: board_id, filename: "#{@target_path}#{file}" end raise "No boards found in #{@target_path.inspect}" if @all_boards.nil? end |
#load_board(board_id:, filename:) ⇒ Object
115 116 117 118 119 120 121 |
# File 'lib/jirametrics/project_config.rb', line 115 def load_board board_id:, filename: board = Board.new( raw: JSON.parse(File.read(filename)), possible_statuses: @possible_statuses ) board.project_config = self (@all_boards ||= {})[board_id] = board end |
#load_issues_from_issues_directory(path:, timezone_offset:) ⇒ Object
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 |
# File 'lib/jirametrics/project_config.rb', line 362 def load_issues_from_issues_directory path:, timezone_offset: issues = [] default_board = nil group_filenames_and_board_ids(path: path).each do |filename, board_ids| content = File.read(File.join(path, filename)) if board_ids == :unknown boards = [(default_board ||= find_default_board)] else boards = board_ids.collect { |b| all_boards[b] } end boards.each do |board| issues << Issue.new(raw: JSON.parse(content), timezone_offset: timezone_offset, board: board) end end issues end |
#load_issues_from_target_directory(path:, timezone_offset:) ⇒ Object
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 |
# File 'lib/jirametrics/project_config.rb', line 344 def load_issues_from_target_directory path:, timezone_offset: puts "Deprecated: issues in the target directory for project #{@name}. " \ 'Download again and this should fix itself.' default_board = find_default_board issues = [] Dir.foreach(path) do |filename| if filename =~ /#{file_prefix}_\d+\.json/ content = JSON.parse File.read("#{path}#{filename}") content['issues'].each do |issue| issues << Issue.new(raw: issue, timezone_offset: timezone_offset, board: default_board) end end end issues end |
#load_project_metadata ⇒ Object
218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/jirametrics/project_config.rb', line 218 def filename = "#{@target_path}/#{file_prefix}_meta.json" json = JSON.parse(File.read(filename)) @data_version = json['version'] || 1 start = json['date_start'] || json['time_start'] # date_start is the current format. Time is the old. stop = json['date_end'] || json['time_end'] @time_range = to_time(start)..to_time(stop) @jira_url = json['jira_url'] rescue Errno::ENOENT puts "== Can't load files from the target directory. Did you forget to download first? ==" raise end |
#load_sprints ⇒ Object
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/jirametrics/project_config.rb', line 184 def load_sprints Dir.foreach(@target_path) do |file| next unless file =~ /#{file_prefix}_board_(\d+)_sprints_\d+/ board_id = $1.to_i timezone_offset = exporter.timezone_offset JSON.parse(File.read("#{target_path}#{file}"))['values'].each do |json| @all_boards[board_id].sprints << Sprint.new(raw: json, timezone_offset: timezone_offset) end end @all_boards.each_value do |board| board.sprints.sort_by!(&:id) end end |
#load_status_category_mappings ⇒ Object
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/jirametrics/project_config.rb', line 154 def load_status_category_mappings filename = "#{@target_path}/#{file_prefix}_statuses.json" # We may not always have this file. Load it if we can. return unless File.exist? filename status_json_snippets = [] json = JSON.parse(File.read(filename)) if json[0]['statuses'] # Response from /api/2/{project_code}/status json.each do |type_config| status_json_snippets += type_config['statuses'] end else # Response from /api/2/status status_json_snippets = json end status_json_snippets.each do |snippet| category_config = snippet['statusCategory'] status_name = snippet['name'] add_possible_status Status.new( name: status_name, id: snippet['id'].to_i, category_name: category_config['name'], category_id: category_config['id'].to_i ) end end |
#raise_with_message_about_missing_category_information ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/jirametrics/project_config.rb', line 123 def = String.new << 'Could not determine categories for some of the statuses used in this data set.\n\n' \ 'If you specify a project: then we\'ll ask Jira for those mappings. If you\'ve done that' \ ' and we still don\'t have the right mapping, which is possible, then use the' \ " 'status_category_mapping' declaration in your config to manually add one.\n\n" \ ' The mappings we do know about are below:' @possible_statuses.each do |status| << "\n type: #{status.type.inspect}, status: #{status.name.inspect}, " \ "category: #{status.category_name.inspect}'" end << "\n\nThe ones we're missing are the following:" missing_statuses = [] issues.each do |issue| issue.changes.each do |change| next unless change.status? missing_statuses << change.value unless find_status(name: change.value) end end missing_statuses.uniq.each do |status_name| << "\n status: #{status_name.inspect}, category: <unknown>" end raise end |
#run ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/jirametrics/project_config.rb', line 40 def run unless aggregated_project? load_status_category_mappings load_all_boards load_sprints end anonymize_data if @anonymizer_needed @board_configs.each do |board_config| board_config.run end @file_configs.each do |file_config| file_config.run end end |
#status_category_mapping(status:, category:, type: nil) ⇒ Object
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/jirametrics/project_config.rb', line 93 def status_category_mapping status:, category:, type: nil puts "Deprecated: ProjectConfig.status_category_mapping no longer needs a type: #{type.inspect}" if type status_object = find_status(name: status) if status_object puts "Status/Category mapping was already present. Ignoring redefinition: #{status_object}" return end add_possible_status Status.new(name: status, id: nil, category_name: category, category_id: nil) end |