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 +4 -4
- data/lib/mixins/user_functions.rb +15 -7
- data/lib/tefoji/jira_api.rb +31 -11
- data/lib/tefoji.rb +60 -88
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c181a5680af5eebeab13d8a78d1e5430df0f3e7535cd9083771af8fbff6d3d06
|
4
|
+
data.tar.gz: 9e0e8b9dcbc36cdbc093a038bd67d4b38f56197bf25695329d78f15c273e5ace
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
71
|
-
|
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
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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)
|
data/lib/tefoji/jira_api.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
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?('
|
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
|
-
@
|
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
|
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
|
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
|
519
|
+
process_blocked_by
|
547
520
|
when 'status'
|
548
|
-
process_status
|
521
|
+
process_status
|
549
522
|
when 'watchers'
|
550
|
-
process_watchers
|
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
|
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
|
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
|
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
|
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
|
827
|
-
#
|
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
|
-
|
844
|
-
|
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
|
-
|
847
|
-
|
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(
|
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(
|
868
|
-
|
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(
|
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(
|
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(
|
859
|
+
update_user_validity(
|
860
|
+
assignee_username,
|
861
|
+
issue['summary'],
|
862
|
+
valid_users,
|
863
|
+
invalid_users_to_issues
|
864
|
+
)
|
888
865
|
end
|
889
|
-
unless
|
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:
|
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:
|
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-
|
11
|
+
date: 2023-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry-byebug
|