cl-magic 0.3.8 → 0.3.9
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/Gemfile.lock +12 -1
- data/README.md +15 -1
- data/cl-magic.gemspec +2 -0
- data/lib/cl/magic/cl-auth +7 -1
- data/lib/cl/magic/cl-dk +45 -35
- data/lib/cl/magic/cl-jira-fetch +128 -0
- data/lib/cl/magic/cl-jira-stats +180 -0
- data/lib/cl/magic/common/common_options.rb +8 -1
- data/lib/cl/magic/common/jira.rb +162 -0
- data/lib/cl/magic/version.rb +1 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42235afcff8b801982ca721b3217756dad15a401063e59310900dd595c05a4e2
|
4
|
+
data.tar.gz: a49e3798271b7cef9f8e82d9b0a6e9846e9a56a1de1d4d75d91ed81e2291d237
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5319a3c52cc59b51c2a6a0e8469cc7085e6485765c335ba44bf6709937c8eb7d12b4b410c78ebb71b283faabd466d647b9109399b20ea9e72ffc4d5fdfa6b252
|
7
|
+
data.tar.gz: 5f016f4f25736f492b0985fe4ebe5cfb0f33597cf8a365d240743f7635c8142db1726c6c17ffac9a4f6a34946e6864e8a0eabda6b8ef89dd5068f6f14161dc06
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cl-magic (0.3.
|
4
|
+
cl-magic (0.3.9)
|
5
|
+
activesupport
|
5
6
|
optparse-subcommand
|
6
7
|
pastel
|
7
8
|
tty-command
|
@@ -11,7 +12,15 @@ PATH
|
|
11
12
|
GEM
|
12
13
|
remote: https://rubygems.org/
|
13
14
|
specs:
|
15
|
+
activesupport (7.0.6)
|
16
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
17
|
+
i18n (>= 1.6, < 2)
|
18
|
+
minitest (>= 5.1)
|
19
|
+
tzinfo (~> 2.0)
|
14
20
|
byebug (11.1.3)
|
21
|
+
concurrent-ruby (1.2.2)
|
22
|
+
i18n (1.14.1)
|
23
|
+
concurrent-ruby (~> 1.0)
|
15
24
|
minitest (5.18.0)
|
16
25
|
optparse-subcommand (1.0.0)
|
17
26
|
pastel (0.8.0)
|
@@ -31,6 +40,8 @@ GEM
|
|
31
40
|
tty-screen (~> 0.8)
|
32
41
|
wisper (~> 2.0)
|
33
42
|
tty-screen (0.8.1)
|
43
|
+
tzinfo (2.0.6)
|
44
|
+
concurrent-ruby (~> 1.0)
|
34
45
|
wisper (2.0.1)
|
35
46
|
|
36
47
|
PLATFORMS
|
data/README.md
CHANGED
@@ -20,4 +20,18 @@ cd $MAGIC_DIR bundle install --path=vendor
|
|
20
20
|
|
21
21
|
# symlink
|
22
22
|
ln -s $MAGIC_DIR/bin/cl /usr/local/bin
|
23
|
-
```
|
23
|
+
```
|
24
|
+
|
25
|
+
## Development
|
26
|
+
|
27
|
+
If you installed the gem, you need to remove the simlink
|
28
|
+
|
29
|
+
```
|
30
|
+
rm /usr/local/bin/cl
|
31
|
+
```
|
32
|
+
|
33
|
+
Then sim-link to your cl-magic source code's bin script
|
34
|
+
|
35
|
+
```
|
36
|
+
ln -s $(pwd)/bin/cl /usr/local/bin
|
37
|
+
```
|
data/cl-magic.gemspec
CHANGED
data/lib/cl/magic/cl-auth
CHANGED
@@ -41,6 +41,12 @@ end
|
|
41
41
|
def aws_okta(options)
|
42
42
|
is_tty = $stdout.isatty
|
43
43
|
|
44
|
+
if options[:aws_profile].nil?
|
45
|
+
puts
|
46
|
+
@logger.info "Note: have you run 'aws-okta add' already?"
|
47
|
+
puts
|
48
|
+
end
|
49
|
+
|
44
50
|
# pick profile
|
45
51
|
profiles = get_aws_profiles()
|
46
52
|
profile = pick_single_result(profiles, "Pick aws profile", options[:aws_profile])
|
@@ -56,7 +62,7 @@ def aws_okta(options)
|
|
56
62
|
# do work
|
57
63
|
if is_tty
|
58
64
|
puts
|
59
|
-
@logger.info "export into your environment with"
|
65
|
+
@logger.info "now export into your environment with"
|
60
66
|
puts
|
61
67
|
puts "export $(#{history_command})"
|
62
68
|
else
|
data/lib/cl/magic/cl-dk
CHANGED
@@ -80,21 +80,23 @@ end
|
|
80
80
|
#
|
81
81
|
|
82
82
|
def print_dk_help_line(key, help)
|
83
|
-
if
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
83
|
+
if $stdout.isatty
|
84
|
+
if help.nil?
|
85
|
+
@logger.puts("#{key.ljust(15, ' ')} ???no help???")
|
86
|
+
else
|
87
|
+
key = key.ljust(15, ' ')
|
88
|
+
help_parts = help.split(";")
|
88
89
|
|
89
|
-
|
90
|
-
|
90
|
+
# first line
|
91
|
+
@logger.puts(key, help_parts.shift)
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
93
|
+
# following lines
|
94
|
+
padding = "".ljust(15, ' ')
|
95
|
+
help_parts.each do |p|
|
96
|
+
@logger.puts(padding, p)
|
97
|
+
end
|
98
|
+
@logger.puts("") if help.end_with?(";")
|
96
99
|
end
|
97
|
-
puts("") if help.end_with?(";")
|
98
100
|
end
|
99
101
|
end
|
100
102
|
|
@@ -131,7 +133,7 @@ def print_dk_help(dk_parts_hash, dk_make_hash, args)
|
|
131
133
|
puts "ADDITIONAL TURNKEY COMMANDS"
|
132
134
|
puts " - dk set-world sets the location of the world directory"
|
133
135
|
puts " - dk make turnkey commands for a project"
|
134
|
-
puts " - dk
|
136
|
+
puts " - dk set-parts save parts so they are automatically applied to commands"
|
135
137
|
puts ""
|
136
138
|
puts "-------------------------"
|
137
139
|
end
|
@@ -139,21 +141,27 @@ def print_dk_help(dk_parts_hash, dk_make_hash, args)
|
|
139
141
|
end
|
140
142
|
|
141
143
|
def print_make_help(dk_parts_hash, dk_make_hash)
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
144
|
+
if $stdout.isatty
|
145
|
+
puts ""
|
146
|
+
puts "Usage: dk [DK_PARTS] make COMMAND"
|
147
|
+
puts ""
|
148
|
+
puts "Make commands designed to make your developer experience more turnkey"
|
149
|
+
puts ""
|
150
|
+
puts "Parts:"
|
151
|
+
dk_parts_hash.keys.each do |key|
|
152
|
+
print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
|
153
|
+
end
|
154
|
+
puts ""
|
155
|
+
puts "Commands:"
|
156
|
+
dk_make_hash.keys.each do |key|
|
157
|
+
print_dk_help_line(key, dk_make_hash[key].fetch('help'))
|
158
|
+
end
|
159
|
+
puts ""
|
160
|
+
else
|
161
|
+
dk_make_hash.keys.each do |key|
|
162
|
+
puts key
|
163
|
+
end
|
155
164
|
end
|
156
|
-
puts ""
|
157
165
|
end
|
158
166
|
|
159
167
|
def print_set_world_help()
|
@@ -227,10 +235,10 @@ end
|
|
227
235
|
def merge_world_files(compose_hash, show_help=false)
|
228
236
|
dk_proj_path = get_world_project_path()
|
229
237
|
if dk_proj_path
|
230
|
-
print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help
|
238
|
+
print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help and $stdout.isatty
|
231
239
|
|
232
240
|
Dir.glob("#{dk_proj_path}/*.yml").sort.each do |filepath|
|
233
|
-
print_dk_help_line("dk-world", "#{filepath}") if show_help
|
241
|
+
print_dk_help_line("dk-world", "#{filepath}") if show_help and $stdout.isatty
|
234
242
|
|
235
243
|
# read file and replace
|
236
244
|
contents = File.read(filepath)
|
@@ -274,10 +282,12 @@ def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, sa
|
|
274
282
|
print_dk_help(dk_parts_hash, dk_make_hash, args)
|
275
283
|
|
276
284
|
def print_and_merge_part(part_key, dk_part, compose_hash)
|
277
|
-
# print
|
278
|
-
help_str = dk_part.fetch('help')
|
279
|
-
print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
|
280
285
|
|
286
|
+
# print
|
287
|
+
if $stdout.isatty
|
288
|
+
help_str = dk_part.fetch('help')
|
289
|
+
print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
|
290
|
+
end
|
281
291
|
# merge
|
282
292
|
return dk_merge_and_remove(compose_hash, dk_part)
|
283
293
|
end
|
@@ -305,7 +315,7 @@ def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, sa
|
|
305
315
|
break
|
306
316
|
end
|
307
317
|
end
|
308
|
-
puts ""
|
318
|
+
@logger.puts "" if $stdout.isatty # tailing line break
|
309
319
|
tempfile.write(compose_hash.to_yaml) # write it to the tempfile
|
310
320
|
|
311
321
|
# clone args
|
@@ -423,7 +433,7 @@ def run_dk_make(compose_args, dk_make_hash, dk_parts_hash, selected_part_keys)
|
|
423
433
|
make_commands.each_with_index do |key, i|
|
424
434
|
|
425
435
|
if not dk_make_hash.has_key?(key)
|
426
|
-
|
436
|
+
@logger.error "#{key} - command not found"
|
427
437
|
exit 1
|
428
438
|
else
|
429
439
|
|
@@ -463,7 +473,7 @@ def prep_make_command(c, selected_part_keys)
|
|
463
473
|
c = interpolate_parts_into_command(c, selected_part_keys)
|
464
474
|
|
465
475
|
# logging
|
466
|
-
@logger.puts ""
|
476
|
+
@logger.puts "" if $stdout.isatty
|
467
477
|
@logger.wait(c)
|
468
478
|
cmd = "cd #{@working_dir} && #{c}"
|
469
479
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Fetch jira issues, status changelogs and save them to a file
|
3
|
+
require 'optparse'
|
4
|
+
require 'optparse/subcommand'
|
5
|
+
require 'tty-command'
|
6
|
+
require 'tty-prompt'
|
7
|
+
|
8
|
+
require 'cl/magic/common/common_options.rb'
|
9
|
+
require 'cl/magic/common/logging.rb'
|
10
|
+
require 'cl/magic/common/jira.rb'
|
11
|
+
|
12
|
+
require 'net/http'
|
13
|
+
require 'json'
|
14
|
+
|
15
|
+
require 'byebug'
|
16
|
+
|
17
|
+
@logger = get_logger()
|
18
|
+
@cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
|
19
|
+
|
20
|
+
#
|
21
|
+
# Features
|
22
|
+
#
|
23
|
+
|
24
|
+
def do_work(options)
|
25
|
+
break_at_one_page = false # when developing, set this to true
|
26
|
+
jira = Jira.new options[:base_uri], options[:username], options[:token], break_at_one_page
|
27
|
+
|
28
|
+
puts ""
|
29
|
+
@logger.wait "fetch epics"
|
30
|
+
epic_ids = jira.get_epic_ids(options[:project], options[:epic_wildcard])
|
31
|
+
@logger.success "#{epic_ids.count} epics"
|
32
|
+
|
33
|
+
puts ""
|
34
|
+
@logger.wait "fetch issues"
|
35
|
+
issues = jira.get_issues(options[:project], epic_ids)
|
36
|
+
@logger.success "#{issues.count} issues"
|
37
|
+
|
38
|
+
puts ""
|
39
|
+
@logger.wait "fetch & merge change logs"
|
40
|
+
issues = collect_status_changelogs(jira, issues, options)
|
41
|
+
@logger.success ""
|
42
|
+
|
43
|
+
puts ""
|
44
|
+
@logger.wait "saving file"
|
45
|
+
output_filepath = File.join(@working_dir, options[:output_filename])
|
46
|
+
File.open(output_filepath, 'w') do |file|
|
47
|
+
issues.each do |issue|
|
48
|
+
file.puts(issue.to_json)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@logger.success " #{output_filepath}"
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Options
|
57
|
+
#
|
58
|
+
|
59
|
+
options = {
|
60
|
+
output_filename: "jira-fetch-#{Time.now.strftime("%Y%m%d%H%M%S")}.datafile"
|
61
|
+
}
|
62
|
+
global_banner = <<DOC
|
63
|
+
|
64
|
+
Fetch jira issues, status changelogs and save them to a file
|
65
|
+
|
66
|
+
Usage: #{@cl_cmd_name} [options]
|
67
|
+
|
68
|
+
DOC
|
69
|
+
|
70
|
+
global = OptionParser.new do |g|
|
71
|
+
g.banner = global_banner
|
72
|
+
add_help_and_verbose(g)
|
73
|
+
|
74
|
+
g.on("--base-uri URI", "base uri for jira (ex. https://company.atlassian.net)") do |v|
|
75
|
+
options[:base_uri] = v
|
76
|
+
end
|
77
|
+
|
78
|
+
g.on("-u", "--username USERNAME", "jira username") do |v|
|
79
|
+
options[:username] = v
|
80
|
+
end
|
81
|
+
|
82
|
+
g.on("-t", "--token TOKEN", "jira token (you can create one, google it)") do |v|
|
83
|
+
options[:token] = v
|
84
|
+
end
|
85
|
+
|
86
|
+
g.on("-p", "--project KEY", "jira project to fetch from") do |v|
|
87
|
+
options[:project] = v
|
88
|
+
end
|
89
|
+
|
90
|
+
g.on("-w", "--epic-wildcard TEXT", "wildcard to filter the epics by") do |v|
|
91
|
+
options[:epic_wildcard] = v
|
92
|
+
end
|
93
|
+
|
94
|
+
g.on("-f", "--output-filename NAME", "the name of the output file (defaults to jira-fetch-timestamp.datafile)") do |v|
|
95
|
+
options[:output_filename] = v
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Run
|
102
|
+
#
|
103
|
+
|
104
|
+
@working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
|
105
|
+
global.parse(ARGV)
|
106
|
+
|
107
|
+
# error on token right away
|
108
|
+
if options[:token].nil?
|
109
|
+
@logger.error "missing --token"
|
110
|
+
exit
|
111
|
+
end
|
112
|
+
|
113
|
+
# prompt for missing options
|
114
|
+
ask_and_store_option(options, :base_uri, "base_uri: ")
|
115
|
+
ask_and_store_option(options, :username, "username: ")
|
116
|
+
ask_and_store_option(options, :project, "project: ")
|
117
|
+
ask_and_store_option(options, :epic_wildcard, "epic_wildcard: ")
|
118
|
+
ask_and_store_option(options, :output_filename, "output_filename: ")
|
119
|
+
|
120
|
+
# display full command
|
121
|
+
write_history("""#{@cl_cmd_name} \\
|
122
|
+
--base-uri=#{options[:base_uri]} \\
|
123
|
+
--username=#{options[:username]} \\
|
124
|
+
--project=#{options[:project]} \\
|
125
|
+
--epic-wildcard=#{options[:epic_wildcard]}
|
126
|
+
""")
|
127
|
+
|
128
|
+
do_work(options)
|
@@ -0,0 +1,180 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Fetch jira issues print stats
|
3
|
+
require 'optparse'
|
4
|
+
require 'optparse/subcommand'
|
5
|
+
require 'tty-command'
|
6
|
+
require 'tty-prompt'
|
7
|
+
require 'active_support/all'
|
8
|
+
|
9
|
+
require 'cl/magic/common/common_options.rb'
|
10
|
+
require 'cl/magic/common/logging.rb'
|
11
|
+
require 'cl/magic/common/jira.rb'
|
12
|
+
|
13
|
+
require 'net/http'
|
14
|
+
require 'json'
|
15
|
+
|
16
|
+
require 'byebug'
|
17
|
+
|
18
|
+
@logger = get_logger()
|
19
|
+
@cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
|
20
|
+
|
21
|
+
#
|
22
|
+
# Features
|
23
|
+
#
|
24
|
+
|
25
|
+
def get_issues_from_datafile(options)
|
26
|
+
issues = []
|
27
|
+
filepath = File.join(@working_dir, options[:data_filepath])
|
28
|
+
File.foreach(filepath) do |line|
|
29
|
+
issue = JSON.parse(line)
|
30
|
+
issuetype = issue["fields"]["issuetype"]["name"]
|
31
|
+
|
32
|
+
# filter if needed
|
33
|
+
issues << issue unless options[:exclude_issuetypes].include?(issuetype)
|
34
|
+
end
|
35
|
+
return issues
|
36
|
+
end
|
37
|
+
|
38
|
+
def oldest_issue_date(issues)
|
39
|
+
return issues.collect {|i| Date.parse(i["fields"]["created"])}.sort.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def do_work(options)
|
43
|
+
issues = get_issues_from_datafile(options)
|
44
|
+
oldest_date = oldest_issue_date(issues)
|
45
|
+
iter_date_range(oldest_date) do |start_date, end_date|
|
46
|
+
stat_hashes = issues_to_stat_hashes(issues, start_date, end_date, options)
|
47
|
+
counts = print_stats(stat_hashes, start_date, end_date)
|
48
|
+
puts counts # print each time range
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def iter_date_range(start_date)
|
53
|
+
current_date = start_date.beginning_of_week
|
54
|
+
while current_date < 1.week.ago.end_of_week
|
55
|
+
yield current_date, current_date.end_of_week
|
56
|
+
current_date += 2.weeks # increment
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def issues_to_stat_hashes(issues, start_date, end_date, options)
|
61
|
+
|
62
|
+
# parse dates for changelogs
|
63
|
+
issues.each do |issue|
|
64
|
+
issue["status_changelogs"].each do |l|
|
65
|
+
date_string = l["created"]
|
66
|
+
l["created"] = Date.parse(date_string) unless date_string.class == Date
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# collect stat hashes
|
71
|
+
return issues.collect do |issue|
|
72
|
+
|
73
|
+
# skip if issue is out of range
|
74
|
+
issue_created = Date.parse(issue["fields"]["created"])
|
75
|
+
if issue_created > end_date
|
76
|
+
nil
|
77
|
+
else
|
78
|
+
# get in-range status change
|
79
|
+
status_changelog = nil
|
80
|
+
status_changelog = issue["status_changelogs"]
|
81
|
+
.select { |l| l["created"] > end_date }
|
82
|
+
.sort_by { |l| l["created"] }.last if start_date && end_date
|
83
|
+
|
84
|
+
# exclude issue types
|
85
|
+
if status_changelog and options[:exclude_issuetypes].include?(status_changelog["toString"])
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
# yield stat hash
|
89
|
+
{
|
90
|
+
issuetype: issue["fields"]["issuetype"]["name"],
|
91
|
+
status: status_changelog ? status_changelog["toString"] : issue["fields"]["status"]["name"]
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end.compact
|
96
|
+
end
|
97
|
+
|
98
|
+
def print_stats(stat_hashes, start_date, end_date)
|
99
|
+
counts = {
|
100
|
+
start_date: start_date.strftime("%m-%d-%Y"),
|
101
|
+
end_date: end_date.strftime("%m-%d-%Y"),
|
102
|
+
total_todo: 0,
|
103
|
+
total_done: 0,
|
104
|
+
total: stat_hashes ? stat_hashes.count : 0
|
105
|
+
}
|
106
|
+
return counts unless stat_hashes.any?
|
107
|
+
stat_hashes.each do |stat|
|
108
|
+
|
109
|
+
issuetype = stat[:issuetype]
|
110
|
+
status = stat[:status]
|
111
|
+
|
112
|
+
# count all
|
113
|
+
increment(counts, "status_#{issuetype.downcase.underscore}")
|
114
|
+
|
115
|
+
# count by status
|
116
|
+
case status
|
117
|
+
when "Ready","Rework","In Progress","In QA","Ready For Code Review","In Code Review"
|
118
|
+
increment(counts, :total_todo)
|
119
|
+
when "Ready to Deploy","Closed"
|
120
|
+
increment(counts, :total_done)
|
121
|
+
# else
|
122
|
+
# raise "invalid ticket status: #{status}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
return counts
|
126
|
+
end
|
127
|
+
|
128
|
+
def increment(hash, key)
|
129
|
+
if hash.key? key
|
130
|
+
hash[key] += 1
|
131
|
+
else
|
132
|
+
hash[key] = 1
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# Options
|
138
|
+
#
|
139
|
+
|
140
|
+
options = {
|
141
|
+
exclude_issuetypes: []
|
142
|
+
}
|
143
|
+
global_banner = <<DOC
|
144
|
+
|
145
|
+
Process jira fetch file an return stats
|
146
|
+
|
147
|
+
Usage: #{@cl_cmd_name} [options]
|
148
|
+
|
149
|
+
DOC
|
150
|
+
|
151
|
+
global = OptionParser.new do |g|
|
152
|
+
g.banner = global_banner
|
153
|
+
add_help_and_verbose(g)
|
154
|
+
|
155
|
+
g.on("-f", "--data-filepath FILEPATH", "relative path to file produced by 'cl jira fetch' command") do |v|
|
156
|
+
options[:data_filepath] = v
|
157
|
+
end
|
158
|
+
|
159
|
+
g.on("-e", "--exclude-issuetypes CSV", "comma separated list of issuetypes you want to exclude") do |v|
|
160
|
+
options[:exclude_issuetypes] = v.split(',')
|
161
|
+
options[:exclude_issuetypes] << "Won't Do"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# Run
|
167
|
+
#
|
168
|
+
|
169
|
+
@working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
|
170
|
+
global.parse(ARGV)
|
171
|
+
|
172
|
+
# prompt for missing options
|
173
|
+
ask_and_store_option(options, :data_filepath, "data_filepath: ")
|
174
|
+
|
175
|
+
# display full command
|
176
|
+
write_history("""#{@cl_cmd_name} \\
|
177
|
+
--data-filepath=#{options[:data_filepath]}
|
178
|
+
""")
|
179
|
+
|
180
|
+
do_work(options)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'tty-prompt'
|
1
2
|
|
2
3
|
def add_help_and_verbose(opts)
|
3
4
|
add_verbose(opts)
|
@@ -13,4 +14,10 @@ def add_help(opts)
|
|
13
14
|
puts opts
|
14
15
|
exit
|
15
16
|
end
|
16
|
-
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def ask_and_store_option(options, key, question)
|
20
|
+
if options[key].nil?
|
21
|
+
options[key] = TTY::Prompt.new.ask(question)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
|
3
|
+
class Jira
|
4
|
+
|
5
|
+
def initialize(base_uri, username, token, break_at_one_page=false)
|
6
|
+
@base_uri = base_uri.chomp("/")
|
7
|
+
@username = username
|
8
|
+
@token = token
|
9
|
+
@break_at_one_page = break_at_one_page
|
10
|
+
end
|
11
|
+
|
12
|
+
#
|
13
|
+
# Fetch: Issues & Change Logs
|
14
|
+
#
|
15
|
+
|
16
|
+
def get_epic_ids(project, epic_wildcard)
|
17
|
+
jql_query = "project = \"#{project}\" AND issuetype = Epic AND text ~ \"#{epic_wildcard}\""
|
18
|
+
results = run_jql_query(jql_query)
|
19
|
+
return results.select{|h| h['fields']['summary'].start_with? epic_wildcard}.map {|h| h['id']}
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_issues(project, epic_ids)
|
23
|
+
jql_query = "project = \"#{project}\" AND parentEpic IN (#{epic_ids.join(',')})"
|
24
|
+
return run_jql_query(jql_query)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get_issue_status_changelog(issue_key)
|
28
|
+
uri = URI.parse("#{@base_uri}/rest/api/2/issue/#{issue_key}/changelog")
|
29
|
+
jira_get(uri) do |response|
|
30
|
+
result = JSON.parse(response.body)
|
31
|
+
return result["values"]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Helpers: GET & POST
|
37
|
+
#
|
38
|
+
|
39
|
+
def jira_get(uri)
|
40
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
41
|
+
http.use_ssl = true
|
42
|
+
|
43
|
+
# get request
|
44
|
+
request = Net::HTTP::Get.new(uri.path)
|
45
|
+
request.basic_auth(@username, @token)
|
46
|
+
|
47
|
+
# fetch
|
48
|
+
response = http.request(request)
|
49
|
+
if response.code == '200'
|
50
|
+
yield response
|
51
|
+
else
|
52
|
+
raise "Jira query failed with HTTP status code #{response.code}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def jira_post(uri, body)
|
57
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
58
|
+
http.use_ssl = true
|
59
|
+
|
60
|
+
# post request
|
61
|
+
request = Net::HTTP::Post.new(uri.path)
|
62
|
+
request.basic_auth(@username, @token)
|
63
|
+
request.content_type = 'application/json'
|
64
|
+
request.body = body.to_json
|
65
|
+
|
66
|
+
# fetch
|
67
|
+
response = http.request(request)
|
68
|
+
if response.code == '200'
|
69
|
+
yield response
|
70
|
+
else
|
71
|
+
raise "Jira query failed with HTTP status code #{response.code}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Fetch: JQL Query
|
77
|
+
#
|
78
|
+
|
79
|
+
def run_jql_query(jql)
|
80
|
+
start_at = 0
|
81
|
+
max_results = 50
|
82
|
+
total_results = nil
|
83
|
+
all_results = []
|
84
|
+
|
85
|
+
page_loop = true
|
86
|
+
while page_loop
|
87
|
+
|
88
|
+
uri = URI("#{@base_uri}/rest/api/2/search")
|
89
|
+
body = { jql: jql, startAt: start_at, maxResults: max_results }
|
90
|
+
|
91
|
+
# post
|
92
|
+
jira_post(uri, body) do |response|
|
93
|
+
result = JSON.parse(response.body)
|
94
|
+
|
95
|
+
# get issues
|
96
|
+
issues = result['issues']
|
97
|
+
all_results += issues
|
98
|
+
|
99
|
+
# debug: one page only
|
100
|
+
if @break_at_one_page
|
101
|
+
page_loop = false
|
102
|
+
break
|
103
|
+
end
|
104
|
+
|
105
|
+
# paginate
|
106
|
+
total_results ||= result['total']
|
107
|
+
if all_results.count == total_results
|
108
|
+
page_loop = false # we got them all, stop paging
|
109
|
+
else
|
110
|
+
start_at += max_results # else next page
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
print '.' # loop
|
115
|
+
end
|
116
|
+
all_results.map {|h| h}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Collect status changelogs
|
122
|
+
#
|
123
|
+
# Given a array of jira issue hashes
|
124
|
+
# * fetch the change log
|
125
|
+
# * filter down to status changes
|
126
|
+
# * add it to the issue hash as ["status_changelogs"]
|
127
|
+
#
|
128
|
+
|
129
|
+
def collect_status_changelogs(jira, issues, options)
|
130
|
+
final_issue_hashes = []
|
131
|
+
|
132
|
+
issues.each do |issue|
|
133
|
+
issue_key = issue["key"]
|
134
|
+
issue["status_changelogs"] = []
|
135
|
+
|
136
|
+
# fetch change log
|
137
|
+
print '.'
|
138
|
+
changelogs = jira.get_issue_status_changelog(issue_key)
|
139
|
+
|
140
|
+
changelogs.each do |change_log|
|
141
|
+
|
142
|
+
# all items that are status changes
|
143
|
+
status_logs = change_log["items"].select {|i| i["field"]=="status"}
|
144
|
+
status_logs = status_logs.collect do |status_log|
|
145
|
+
{
|
146
|
+
"key": issue_key,
|
147
|
+
"created": change_log["created"],
|
148
|
+
"toString": status_log["toString"],
|
149
|
+
"fromString": status_log["fromString"]
|
150
|
+
}
|
151
|
+
end
|
152
|
+
|
153
|
+
# append them to issue
|
154
|
+
status_logs.each do |status_log|
|
155
|
+
issue["status_changelogs"] << status_log
|
156
|
+
end if status_logs.count > 0
|
157
|
+
end
|
158
|
+
|
159
|
+
final_issue_hashes << issue # save
|
160
|
+
end
|
161
|
+
return final_issue_hashes
|
162
|
+
end
|
data/lib/cl/magic/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cl-magic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Don Najd
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03
|
11
|
+
date: 2023-07-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: byebug
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -132,6 +146,8 @@ files:
|
|
132
146
|
- lib/cl/magic/cl-gc-tags
|
133
147
|
- lib/cl/magic/cl-glab-commit
|
134
148
|
- lib/cl/magic/cl-history
|
149
|
+
- lib/cl/magic/cl-jira-fetch
|
150
|
+
- lib/cl/magic/cl-jira-stats
|
135
151
|
- lib/cl/magic/cl-kube-cp
|
136
152
|
- lib/cl/magic/cl-kube-deployment
|
137
153
|
- lib/cl/magic/cl-kube-ktx
|
@@ -145,6 +161,7 @@ files:
|
|
145
161
|
- lib/cl/magic/cl-vault
|
146
162
|
- lib/cl/magic/common/common_options.rb
|
147
163
|
- lib/cl/magic/common/gcloud.rb
|
164
|
+
- lib/cl/magic/common/jira.rb
|
148
165
|
- lib/cl/magic/common/kubectl.rb
|
149
166
|
- lib/cl/magic/common/logging.rb
|
150
167
|
- lib/cl/magic/common/parse_and_pick.rb
|