tefoji 1.1.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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