tefoji 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a9bb72bd908fda02253da35856c56dd5aa0c889af3abcdb5bfea31b329563bee
4
- data.tar.gz: 8e50beef2605dec8298ede53aca85413e5dd547ea911bafcbb38a7968a7b93b4
3
+ metadata.gz: c181a5680af5eebeab13d8a78d1e5430df0f3e7535cd9083771af8fbff6d3d06
4
+ data.tar.gz: 9e0e8b9dcbc36cdbc093a038bd67d4b38f56197bf25695329d78f15c273e5ace
5
5
  SHA512:
6
- metadata.gz: cfa17bc65afb628477b49ebbcb383bd6f1864acea598a6333751ffb1327895bd427f5c03e38f0d8f4fd622afaf5854ebd4b097f3cf96fba976086e2831f4527f
7
- data.tar.gz: 857c3522c6397462163b74257edd7a1c7f145399da45000ad95d941a4bf315bc6695f3a2935ad879dc78c729088e1f8f5335b7e7a72eca551c808ba03896fe31
6
+ metadata.gz: 40b84da52997e8df28012872982e3e50c2f3b18da3a2f80650d08c0f45525a3fa6803e1c9fdca9a82e6461c67882357ddc77d85125b39000176930bb6ccf3464
7
+ data.tar.gz: 6396ee3ff93fd5214500ebe2913d5eddce18968741532e8c7a95b4547a4ffef7251fd9cf0667a30236503b76d1468f44e650f2816ca8815e0f8d6dbaf7891593
@@ -27,6 +27,7 @@ module UserFunctions
27
27
  prompt_only
28
28
  random
29
29
  release_type
30
+ scrum_team
30
31
  split
31
32
  ]
32
33
  end
@@ -51,10 +52,10 @@ module UserFunctions
51
52
  PE_INTERNAL: 'PE',
52
53
  POOLER: 'POOLER',
53
54
  PROJECT_CENTRAL: 'PC',
55
+ PUPPET: 'PUP',
54
56
  PUPPETDB: 'PDB',
55
57
  PUPPETSERVER: 'SERVER',
56
58
  PUPPET_AGENT: 'PA',
57
- PUPPET: 'PUP',
58
59
  QUALITY_ENGINEERING: 'QENG',
59
60
  RELEASE_ENGINEERING: 'RE',
60
61
  SLV: 'SLV',
@@ -67,17 +68,23 @@ module UserFunctions
67
68
 
68
69
  def jira_sprints
69
70
  {
70
- RE_KANBAN: 3150,
71
- DUMPLING_READY_TO_WORK: 5515
71
+ DUMPLING_READY_TO_WORK: 5515,
72
+ RE_KANBAN: 3150
72
73
  }
73
74
  end
74
75
 
75
76
  def jira_statuses
77
+ # READY_FOR_ENGINEERING deprecated and translated to ACCEPTED
76
78
  {
77
- READY_FOR_ENGINEERING: 111,
78
- ACCEPTED: 271,
79
- DEVELOPING_FEATURE: 61,
80
- DEVELOPING_EPIC: 41
79
+ ACCEPTED: 11,
80
+ READY_FOR_ENGINEERING: 11,
81
+ ON_HOLD: 21,
82
+ UNDER_INVESTIGATION: 31,
83
+ TESTING: 41,
84
+ IN_PROGRESS: 51,
85
+ IN_REVIEW: 61,
86
+ CLOSED: 71,
87
+ CANCELED: 81
81
88
  }
82
89
  end
83
90
 
@@ -184,6 +191,7 @@ module UserFunctions
184
191
  def jira_team(args)
185
192
  _jira_key('team', jira_teams, args[0])
186
193
  end
194
+ alias scrum_team jira_team
187
195
 
188
196
  # Return the first n fields of '.' - delimited version string
189
197
  def n_digit_version(args)
@@ -192,6 +192,12 @@ module Tefoji
192
192
 
193
193
  private
194
194
 
195
+ ## BUG BUG 'type' should ALWAYS be defined but we have a number of assumptions in
196
+ # this file that just check for the field existence and not the value.
197
+ def epic?(issue_data)
198
+ issue_data.key?('type') && issue_data['type'].casecmp?(ISSUE_EPIC)
199
+ end
200
+
195
201
  def jira_get(jira_request_path, fail_if_not_found = true)
196
202
  # Jira likes to send complete URLs for responses. Handle
197
203
  # the case where we've received a 'self' query from Jira with
@@ -321,6 +327,28 @@ module Tefoji
321
327
  end
322
328
  end
323
329
 
330
+ # Backward compatiblity: translate 'team' to 'scrum_team'
331
+ scrum_team = issue_data['scrum_team'] || issue_data['team']
332
+ if scrum_team
333
+ if epic?(issue_data)
334
+ jira_fields['components'] = [{ FIELD_NAME => scrum_team }]
335
+ else
336
+ jira_fields['customfield_11500'] = { FIELD_VALUE => scrum_team }
337
+ end
338
+ end
339
+
340
+ # We're only allowed multiple teams (as 'components') in epics. For ordinary
341
+ # issues take only the first of the list
342
+ scrum_teams = issue_data['scrum_teams'] || issue_data['teams']
343
+ if scrum_teams
344
+ processed_teams = scrum_teams.to_a.flatten.reject { |t| t =~ /^\s*$/ }
345
+ if epic?(issue_data)
346
+ jira_fields['components'] = processed_teams.map { |team| { FIELD_NAME => team } }
347
+ else
348
+ jira_fields['customfield_11500'] = { FIELD_VALUE => processed_teams.first }
349
+ end
350
+ end
351
+
324
352
  # Default issue type to ISSUE_TASK if it isn't already set
325
353
  jira_fields['issuetype'] = { FIELD_NAME => ISSUE_TASK }
326
354
 
@@ -350,7 +378,7 @@ module Tefoji
350
378
  if issue_data['type']
351
379
  jira_fields['issuetype'] = { FIELD_NAME => issue_data['type'] }
352
380
  # If this is an epic, we need to add an epic name
353
- if issue_data['type'].casecmp?(ISSUE_EPIC)
381
+ if epic?(issue_data)
354
382
  jira_fields['customfield_10011'] = issue_data['epic_name'] || issue_data['summary']
355
383
  end
356
384
  end
@@ -408,7 +436,7 @@ module Tefoji
408
436
  if issue_data['type']
409
437
  jira_fields['issuetype'] = { FIELD_NAME => issue_data['type'] }
410
438
  # If this is an epic, we need to add an epic name
411
- if issue_data['type'].casecmp?(ISSUE_EPIC)
439
+ if epic?(issue_data)
412
440
  jira_fields['customfield_10007'] = issue_data['epic_name'] || issue_data['summary']
413
441
  end
414
442
  end
@@ -416,15 +444,7 @@ module Tefoji
416
444
  if issue_data['story_points']
417
445
  jira_fields['customfield_10002'] = issue_data['story_points'].to_i
418
446
  end
419
- if issue_data['team']
420
- jira_fields['customfield_14200'] = { FIELD_VALUE => issue_data['team'] }
421
- end
422
- if issue_data['teams']
423
- teams = issue_data['teams'].to_a.flatten.reject { |t| t =~ /^\s*$/ }
424
- unless teams.empty?
425
- jira_fields['customfield_14201'] = teams.map { |team| { FIELD_VALUE => team } }
426
- end
427
- end
447
+
428
448
  if issue_data['subteam']
429
449
  jira_fields['customfield_11700'] = [issue_data['subteam']]
430
450
  end
data/lib/tefoji.rb CHANGED
@@ -18,6 +18,8 @@ module Tefoji
18
18
  include Logging
19
19
  include UserFunctions
20
20
 
21
+ attr_accessor :template_data, :jira_api
22
+
21
23
  # @param [Hash] user_options options provided by the user through the CLI.
22
24
  # @see CLI
23
25
  def initialize(user_options = {}, log_location = $stderr)
@@ -38,9 +40,9 @@ module Tefoji
38
40
  @issue_counter = 1
39
41
 
40
42
  # Changable internal states
41
- @feature_issue = nil
42
43
  @default_target_epic = nil
43
44
  @epic_security = nil
45
+ @deferral_data = {}
44
46
 
45
47
  # Logging
46
48
  @log_level = Logger::INFO
@@ -113,9 +115,7 @@ module Tefoji
113
115
  @logger.info "- Processing \"#{template_name}\" template"
114
116
  @template_data = template_data
115
117
 
116
- if @template_data.key?('feature')
117
- generate_feature_with_issues
118
- elsif @template_data.key?('epics')
118
+ if @template_data.key?('epics')
119
119
  generate_epics_with_issues('epics')
120
120
  elsif @template_data.key?('epic')
121
121
  generate_epics_with_issues('epic')
@@ -236,9 +236,6 @@ module Tefoji
236
236
 
237
237
  yaml_data = YAML.safe_load(raw_yaml, filename: included_template_path)
238
238
 
239
- # In included templates, we don't include features.
240
- yaml_data.delete('feature')
241
-
242
239
  # In included templates, we don't include epics unless they are specifically kept.
243
240
  unless include_epics
244
241
  yaml_data.delete('epics')
@@ -282,29 +279,6 @@ module Tefoji
282
279
  @jira_api.save_authentication(@jira_auth_file, @jira_cloud)
283
280
  end
284
281
 
285
- private
286
-
287
- # Generate a feature, a list of epics, and associated issues
288
- def generate_feature_with_issues
289
- @feature_issue = generate_feature
290
- generate_epics('epics').each do |epic|
291
- @default_target_epic = epic
292
- generate_ordinary_issues
293
- end
294
- end
295
-
296
- # Generate a 'feature' issue
297
- def generate_feature
298
- feature_to_do = @template_data['feature']
299
-
300
- feature = variable_substitute(feature_to_do)
301
- feature['type'] = JiraApi::ISSUE_NEW_FEATURE
302
-
303
- @feature_issue = @jira_api.create_issue(feature, @jira_cloud)
304
- @logger.info "Feature issue: #{@feature_issue['key']}"
305
- @feature_issue
306
- end
307
-
308
282
  def generate_epics_with_issues(epic_key)
309
283
  epics = generate_epics(epic_key)
310
284
  epics.each do |epic|
@@ -365,7 +339,10 @@ module Tefoji
365
339
  epic_issue = @jira_api.create_issue(epic, @jira_cloud)
366
340
  epic_issue['short_name'] = short_name
367
341
  @logger.info 'Epic: %16s [%s]' % [epic_issue['key'], short_name]
368
- @jira_api.link_issues(@feature_issue['key'], epic_issue['key']) if @feature_issue
342
+ @deferral_data[short_name.to_s] = {
343
+ 'jira' => epic_issue,
344
+ 'raw' => epic
345
+ }
369
346
  epic_issue
370
347
  end
371
348
  end
@@ -385,10 +362,6 @@ module Tefoji
385
362
  # Iterate through all issues in the template, creating each one in Jira.
386
363
  # Link the issues back to their epic, if required
387
364
  def generate_ordinary_issues
388
- # Need to keep a map of tefoji data and associated responses from Jira to perform
389
- # deferred updates, like blocked_by or watchers
390
- deferral_data = {}
391
-
392
365
  issue_defaults = {}
393
366
  if @template_data.key?('issue_defaults')
394
367
  issue_defaults = variable_substitute(@template_data['issue_defaults'])
@@ -417,12 +390,12 @@ module Tefoji
417
390
 
418
391
  # deferral_data is kept to match the jira raw request data to the jira response data.
419
392
  # This is needed so that deferred actions (like 'blocked_by') can be correctly mapped.
420
- deferral_data[raw_issue_data['short_name'].to_s] = {
393
+ @deferral_data[raw_issue_data['short_name'].to_s] = {
421
394
  'jira' => jira_issue,
422
395
  'raw' => raw_issue_data
423
396
  }
424
397
  end
425
- process_deferred_updates(deferral_data)
398
+ process_deferred_updates
426
399
  end
427
400
 
428
401
  # Transform the tefoji issue data and tefoji issue_defaults into a hash that is ready to
@@ -538,16 +511,16 @@ module Tefoji
538
511
  #
539
512
  # @param deferral_data [Hash] raw template and jira response data
540
513
  # @see generate_ordinary_issues
541
- def process_deferred_updates(deferral_data)
514
+ def process_deferred_updates
542
515
  ## Find deferred tags in the jira_epic_issues and do the required work
543
516
  deferred_tags.each do |deferred_tag|
544
517
  case deferred_tag
545
518
  when 'blocked_by'
546
- process_blocked_by(deferral_data)
519
+ process_blocked_by
547
520
  when 'status'
548
- process_status(deferral_data)
521
+ process_status
549
522
  when 'watchers'
550
- process_watchers(deferral_data)
523
+ process_watchers
551
524
  else
552
525
  raise "Unimplemented deferred_tag: #{deferred_tag}"
553
526
  end
@@ -557,10 +530,10 @@ module Tefoji
557
530
  ## The process_* methods have repeated code. This could be cleaned up
558
531
  ## with some well-thought-through functional techniques. Since the list
559
532
  ## of methods is short, I'll pass on that for now.
560
- def process_blocked_by(deferral_data)
533
+ def process_blocked_by
561
534
  deferred_tag = 'blocked_by'
562
535
 
563
- deferral_data.each_value do |deferral_hash|
536
+ @deferral_data.each_value do |deferral_hash|
564
537
  raw_issue_data = deferral_hash['raw']
565
538
  jira_issue_data = deferral_hash['jira']
566
539
  next unless raw_issue_data.key?(deferred_tag)
@@ -568,10 +541,9 @@ module Tefoji
568
541
  target_issues = [raw_issue_data[deferred_tag].value].flatten
569
542
  target_issues.each do |target_issue_name|
570
543
  this_issue_key = jira_issue_data['key']
571
-
572
544
  # If we skipped creating the target issue because of a 'conditional',
573
545
  # don't try to link to it.
574
- target_issue_key = deferral_data.dig(target_issue_name, 'jira', 'key')
546
+ target_issue_key = @deferral_data.dig(target_issue_name, 'jira', 'key')
575
547
  next if target_issue_key.nil?
576
548
 
577
549
  @jira_api.link_issues(target_issue_key, this_issue_key)
@@ -580,10 +552,10 @@ module Tefoji
580
552
  end
581
553
  end
582
554
 
583
- def process_status(deferral_data)
555
+ def process_status
584
556
  deferred_tag = 'status'
585
557
 
586
- deferral_data.each_value do |deferral_hash|
558
+ @deferral_data.each_value do |deferral_hash|
587
559
  raw_issue_data = deferral_hash['raw']
588
560
  jira_issue_data = deferral_hash['jira']
589
561
  next unless raw_issue_data.key?(deferred_tag)
@@ -600,10 +572,10 @@ module Tefoji
600
572
  end
601
573
  end
602
574
 
603
- def process_watchers(deferral_data)
575
+ def process_watchers
604
576
  deferred_tag = 'watchers'
605
577
 
606
- deferral_data.each_value do |deferral_hash|
578
+ @deferral_data.each_value do |deferral_hash|
607
579
  raw_issue_data = deferral_hash['raw']
608
580
  jira_issue_data = deferral_hash['jira']
609
581
 
@@ -816,40 +788,27 @@ module Tefoji
816
788
  end
817
789
 
818
790
  def missing_expected_keys?(template_uri, template)
819
- expected_keys = %w[epic epics feature issues]
791
+ expected_keys = %w[epic epics issues]
820
792
  return false if (template.keys & expected_keys).any?
821
793
 
822
794
  @logger.error "Cannot find any known template keywords in \"#{template_uri}\""
823
795
  return true
824
796
  end
825
797
 
826
- # Iterates through the assignees in a template and checks if they exist on jira. If any assignee in
827
- # the template does not exist, it will fail, outputting a message saying which users failed and
798
+ # Iterates through the assignees in a template and checks if they
799
+ # exist on jira. If any assignee in the template does not exist,
800
+ # it will fail, outputting a message saying which users failed and
828
801
  # what epics/issues they are associated with.
829
- # NOTE: This check does not guarantee a user is assignable, so there may be cases when a user passes
830
- # the check but still fails when being assigned a ticket/epic. If we find a reasonable way to
831
- # check assignability prior to the epic/issue being created, we should implement it in place of
832
- # using the get_username method.
833
- def check_assignees(main_template_data)
834
- valid_users = []
835
-
836
- invalid_users_to_features = {}
837
- main_template_data['features']&.each do |feature|
838
- next unless feature['assignee']
839
-
840
- assignee_username = get_username_from_assignee(feature['assignee'])
841
- next if valid_users.include?(assignee_username)
842
802
 
843
- update_user_validity(assignee_username, feature['summary'], valid_users, invalid_users_to_features)
844
- end
803
+ # NOTE: This check does not guarantee a user is assignable, so
804
+ # there may be cases when a user passes the check but still
805
+ # fails when being assigned a ticket/epic. If we find a
806
+ # reasonable way to check assignability prior to the
807
+ # epic/issue being created, we should implement it in place
808
+ # of using the get_username method.
845
809
 
846
- if main_template_data.dig('feature', 'assignee')
847
- assignee_username = get_username_from_assignee(main_template_data['feature']['assignee'])
848
- unless valid_users.include?(assignee_username)
849
- update_user_validity(assignee_username, main_template_data['feature']['summary'], valid_users,
850
- invalid_users_to_features)
851
- end
852
- end
810
+ def check_assignees(main_template_data)
811
+ valid_users = []
853
812
 
854
813
  invalid_users_to_epics = {}
855
814
  main_template_data['epics']&.each do |epic|
@@ -858,22 +817,35 @@ module Tefoji
858
817
  assignee_username = get_username_from_assignee(epic['assignee'])
859
818
  next if valid_users.include?(assignee_username)
860
819
 
861
- update_user_validity(assignee_username, epic['summary'], valid_users, invalid_users_to_epics)
820
+ update_user_validity(
821
+ assignee_username,
822
+ epic['summary'],
823
+ valid_users,
824
+ invalid_users_to_epics
825
+ )
862
826
  end
863
827
 
864
828
  if main_template_data.dig('epic', 'assignee')
865
829
  assignee_username = get_username_from_assignee(main_template_data['epic']['assignee'])
866
830
  unless valid_users.include?(assignee_username)
867
- update_user_validity(assignee_username, main_template_data['epic']['summary'], valid_users,
868
- invalid_users_to_epics)
831
+ update_user_validity(
832
+ assignee_username,
833
+ main_template_data['epic']['summary'],
834
+ valid_users,
835
+ invalid_users_to_epics
836
+ )
869
837
  end
870
838
  end
871
839
 
872
840
  invalid_users_to_issue_defaults = {}
873
841
  if main_template_data.dig('issue_defaults', 'assignee')
874
- assignee_username = get_username_from_assignee(main_template_data['issue_defaults']['assignee'])
842
+ assignee_username = get_username_from_assignee(
843
+ main_template_data['issue_defaults']['assignee']
844
+ )
875
845
  unless valid_users.include?(assignee_username)
876
- update_user_validity(assignee_username, 'issue_defaults', valid_users, invalid_users_to_issue_defaults)
846
+ update_user_validity(
847
+ assignee_username, 'issue_defaults', valid_users, invalid_users_to_issue_defaults
848
+ )
877
849
  end
878
850
  end
879
851
 
@@ -884,19 +856,18 @@ module Tefoji
884
856
  assignee_username = get_username_from_assignee(issue['assignee'])
885
857
  next if valid_users.include?(assignee_username)
886
858
 
887
- update_user_validity(assignee_username, issue['summary'], valid_users, invalid_users_to_issues)
859
+ update_user_validity(
860
+ assignee_username,
861
+ issue['summary'],
862
+ valid_users,
863
+ invalid_users_to_issues
864
+ )
888
865
  end
889
- unless invalid_users_to_features.empty? &&
890
- invalid_users_to_epics.empty? &&
866
+ unless invalid_users_to_epics.empty? &&
891
867
  invalid_users_to_issue_defaults.empty? &&
892
868
  invalid_users_to_issues.empty?
893
869
  invalid_user_message = "Invalid assignees:\n"
894
870
  end
895
- unless invalid_users_to_features.empty?
896
- invalid_users_to_features.each do |assignee, feature|
897
- invalid_user_message += "Assignee #{assignee} associated with features #{feature}\n"
898
- end
899
- end
900
871
  unless invalid_users_to_epics.empty?
901
872
  invalid_users_to_epics.each do |assignee, epic|
902
873
  invalid_user_message += "Assignee #{assignee} associated with epics #{epic}\n"
@@ -924,7 +895,8 @@ module Tefoji
924
895
  end
925
896
 
926
897
  unless username
927
- fatal "invalid assignee format for issue: #{issue['short_name']} failed: must be a function or a string"
898
+ fatal "invalid assignee format for issue: #{issue['short_name']} failed: ",
899
+ 'must be a function or a string'
928
900
  end
929
901
 
930
902
  username = username.value unless username.is_a?(String)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tefoji
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet Labs
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-05-18 00:00:00.000000000 Z
11
+ date: 2023-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug