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 +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
|