cl-magic 0.3.8 → 0.4.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: aee6512ae8cbee738242f87ec89baa10a56c04c2600fdf766c93f7844d6d6a38
4
- data.tar.gz: d43a49eca90391948305353fa7d0242bf5bf14504492531bf9cda43bd7a4d0cf
3
+ metadata.gz: '08b4655abcd92a0d5e3a779fdc7e0e09231c7cb15d073e06878c085227ef3f9d'
4
+ data.tar.gz: 63999d535d6ad18d22b8cff1e259b9b054bbf7faf75939e54c9819885ca24feb
5
5
  SHA512:
6
- metadata.gz: 3b040f7fe5dfccd9c62395d433a1c445f9c246e27b98636c0af318beca7e62ac7a4a082bdfdf1f68e8e602b2652ff3a9c961f611a317de5d2555262652405544
7
- data.tar.gz: 4a0ced863718cfe30127ff57f18698ef641ae520245094166d511310b7995f55c08e3644e8028044e27389681250e8c63c7850bf44c43e933546aa8bc2bbb557
6
+ metadata.gz: 4157d207e824e2d0a309790187873d3b6b02c41bd892fc3436e359cc1f4639044576129d1a8d36ac9a0faee8afa6dc2366db506f93401bf4165e93766435f3d7
7
+ data.tar.gz: 0bce3b21105e81dd1bc1d5656e941ceec30814bb01e19c16a7a5ee57eba98403093d7359eb9e113bd60b0850a87764b1241a08febe1881dcde74c303fb9d35be
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cl-magic (0.3.8)
4
+ cl-magic (0.4.0)
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
@@ -34,5 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "tty-command"
35
35
  spec.add_dependency "tty-prompt"
36
36
  spec.add_dependency "pastel"
37
+ spec.add_dependency "activesupport"
37
38
  spec.add_development_dependency "byebug"
39
+
38
40
  end
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])
@@ -55,8 +61,20 @@ def aws_okta(options)
55
61
  # cmd = "aws-okta add" # old
56
62
  # do work
57
63
  if is_tty
64
+
65
+ # ensure we are logged in
66
+ begin
67
+ TTY::Command.new(:printer => :null).run("aws-okta exec #{options[:aws_profile]} -- env")
68
+ rescue TTY::Command::ExitError => e
69
+ if e.message.include? "Failed to authenticate"
70
+ @logger.info "authenticate first with: aws-okta add"
71
+ else
72
+ raise
73
+ end
74
+ end
75
+
58
76
  puts
59
- @logger.info "export into your environment with"
77
+ @logger.info "now export into your environment with"
60
78
  puts
61
79
  puts "export $(#{history_command})"
62
80
  else
data/lib/cl/magic/cl-dk CHANGED
@@ -41,7 +41,11 @@ end
41
41
 
42
42
  def get_repo_basename()
43
43
  command = "cd #{@working_dir} && basename $(git remote get-url origin 2> /dev/null) .git"
44
- return TTY::Command.new(:printer => :null).run(command).out.gsub('.git', '').strip.chomp
44
+ repo_basename = TTY::Command.new(:printer => :null).run(command).out.gsub('.git', '').strip.chomp
45
+ if repo_basename==".git" or repo_basename==""
46
+ return File.basename(@working_dir)
47
+ end
48
+ return repo_basename
45
49
  end
46
50
 
47
51
  def get_world_settings_filepath()
@@ -80,21 +84,23 @@ end
80
84
  #
81
85
 
82
86
  def print_dk_help_line(key, help)
83
- if help.nil?
84
- @logger.puts("#{key.ljust(15, ' ')} ???no help???")
85
- else
86
- key = key.ljust(15, ' ')
87
- help_parts = help.split(";")
87
+ if $stdout.isatty
88
+ if help.nil?
89
+ @logger.puts("#{key.ljust(15, ' ')} ???no help???")
90
+ else
91
+ key = key.ljust(15, ' ')
92
+ help_parts = help.split(";")
88
93
 
89
- # first line
90
- @logger.puts(key, help_parts.shift)
94
+ # first line
95
+ @logger.puts(key, help_parts.shift)
91
96
 
92
- # following lines
93
- padding = "".ljust(15, ' ')
94
- help_parts.each do |p|
95
- @logger.puts(padding, p)
97
+ # following lines
98
+ padding = "".ljust(15, ' ')
99
+ help_parts.each do |p|
100
+ @logger.puts(padding, p)
101
+ end
102
+ @logger.puts("") if help.end_with?(";")
96
103
  end
97
- puts("") if help.end_with?(";")
98
104
  end
99
105
  end
100
106
 
@@ -104,56 +110,60 @@ def print_dk_help(dk_parts_hash, dk_make_hash, args)
104
110
  has_dk_commands = dk_parts_hash.keys.any?
105
111
 
106
112
  if no_args or asked_for_help
107
- if has_dk_commands
108
- puts ""
109
- puts "Usage: dk [DK_PARTS] [COMPOSE_OPTIONS] COMPOSE_COMMAND"
110
- puts ""
111
- puts "Run docker compose while munging yamls in sophisticated ways."
112
- puts ""
113
- if get_repo_basename
114
- puts "PROJ INFO"
115
- puts " - Repo basename: #{get_repo_basename}"
116
- puts " - World filepath: #{get_world_project_path()}"
117
- puts ""
118
- end
119
- puts "PROJ PARTS"
120
- dk_parts_hash.keys.each do |key|
121
- print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
122
- end
123
- puts ""
124
- puts "YML TOKENS"
125
- puts " - <dk-remove> removes section of yaml"
126
- puts " - <dk-replace> replaces values in a yaml array"
127
- puts " - <dk-world-path> absolute filepath to world directory"
128
- puts " - <dk-project-path> absolute filepath to world/project directory"
129
- puts " - <dk-working-path> absolute filepath to location dk command was run from"
130
- puts ""
131
- puts "ADDITIONAL TURNKEY COMMANDS"
132
- puts " - dk set-world sets the location of the world directory"
133
- puts " - dk make turnkey commands for a project"
134
- puts " - dk save-parts save parts so they are automatically applied to commands"
113
+ puts ""
114
+ puts "Usage: dk [DK_PARTS] [COMPOSE_OPTIONS] COMPOSE_COMMAND"
115
+ puts ""
116
+ puts "Run docker compose while munging yamls in sophisticated ways."
117
+ puts ""
118
+ if get_repo_basename
119
+ puts "PROJ INFO"
120
+ puts " - Repo basename: #{get_repo_basename}"
121
+ puts " - World filepath: #{get_world_project_path()}"
135
122
  puts ""
136
- puts "-------------------------"
137
123
  end
124
+ puts "PROJ PARTS"
125
+ dk_parts_hash.keys.each do |key|
126
+ print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
127
+ end
128
+ puts ""
129
+ puts "YML TOKENS"
130
+ puts " - <dk-remove> removes section of yaml"
131
+ puts " - <dk-replace> replaces values in a yaml array"
132
+ puts " - <dk-world-path> absolute filepath to world directory"
133
+ puts " - <dk-project-path> absolute filepath to world/project directory"
134
+ puts " - <dk-working-path> absolute filepath to location dk command was run from"
135
+ puts ""
136
+ puts "ADDITIONAL TURNKEY COMMANDS"
137
+ puts " - dk set-world sets the location of the world directory"
138
+ puts " - dk make turnkey commands for a project"
139
+ puts " - dk set-parts save parts so they are automatically applied to commands"
140
+ puts ""
141
+ puts "-------------------------"
138
142
  end
139
143
  end
140
144
 
141
145
  def print_make_help(dk_parts_hash, dk_make_hash)
142
- puts ""
143
- puts "Usage: dk [DK_PARTS] make COMMAND"
144
- puts ""
145
- puts "Make commands designed to make your developer experience more turnkey"
146
- puts ""
147
- puts "Parts:"
148
- dk_parts_hash.keys.each do |key|
149
- print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
150
- end
151
- puts ""
152
- puts "Commands:"
153
- dk_make_hash.keys.each do |key|
154
- print_dk_help_line(key, dk_make_hash[key].fetch('help'))
146
+ if $stdout.isatty
147
+ puts ""
148
+ puts "Usage: dk [DK_PARTS] make COMMAND"
149
+ puts ""
150
+ puts "Make commands designed to make your developer experience more turnkey"
151
+ puts ""
152
+ puts "Parts:"
153
+ dk_parts_hash.keys.each do |key|
154
+ print_dk_help_line(key, dk_parts_hash[key].fetch('help'))
155
+ end
156
+ puts ""
157
+ puts "Commands:"
158
+ dk_make_hash.keys.each do |key|
159
+ print_dk_help_line(key, dk_make_hash[key].fetch('help'))
160
+ end
161
+ puts ""
162
+ else
163
+ dk_make_hash.keys.each do |key|
164
+ puts key
165
+ end
155
166
  end
156
- puts ""
157
167
  end
158
168
 
159
169
  def print_set_world_help()
@@ -227,10 +237,10 @@ end
227
237
  def merge_world_files(compose_hash, show_help=false)
228
238
  dk_proj_path = get_world_project_path()
229
239
  if dk_proj_path
230
- print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help
240
+ print_dk_help_line("dk-project-path", "#{dk_proj_path}") if show_help and $stdout.isatty
231
241
 
232
242
  Dir.glob("#{dk_proj_path}/*.yml").sort.each do |filepath|
233
- print_dk_help_line("dk-world", "#{filepath}") if show_help
243
+ print_dk_help_line("dk-world", "#{filepath}") if show_help and $stdout.isatty
234
244
 
235
245
  # read file and replace
236
246
  contents = File.read(filepath)
@@ -274,10 +284,12 @@ def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, sa
274
284
  print_dk_help(dk_parts_hash, dk_make_hash, args)
275
285
 
276
286
  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
287
 
288
+ # print
289
+ if $stdout.isatty
290
+ help_str = dk_part.fetch('help')
291
+ print_dk_help_line("#{part_key}", "#{help_str ? '- ' + help_str : ''}") if dk_part.keys.any?
292
+ end
281
293
  # merge
282
294
  return dk_merge_and_remove(compose_hash, dk_part)
283
295
  end
@@ -305,7 +317,7 @@ def merge_parts_save_and_prep_args(compose_hash, dk_parts_hash, dk_make_hash, sa
305
317
  break
306
318
  end
307
319
  end
308
- puts "" # tailing line break
320
+ @logger.puts "" if $stdout.isatty # tailing line break
309
321
  tempfile.write(compose_hash.to_yaml) # write it to the tempfile
310
322
 
311
323
  # clone args
@@ -423,7 +435,7 @@ def run_dk_make(compose_args, dk_make_hash, dk_parts_hash, selected_part_keys)
423
435
  make_commands.each_with_index do |key, i|
424
436
 
425
437
  if not dk_make_hash.has_key?(key)
426
- puts "#{key} - command not found"
438
+ @logger.error "#{key} - command not found"
427
439
  exit 1
428
440
  else
429
441
 
@@ -463,7 +475,7 @@ def prep_make_command(c, selected_part_keys)
463
475
  c = interpolate_parts_into_command(c, selected_part_keys)
464
476
 
465
477
  # logging
466
- @logger.puts ""
478
+ @logger.puts "" if $stdout.isatty
467
479
  @logger.wait(c)
468
480
  cmd = "cd #{@working_dir} && #{c}"
469
481
  end
@@ -0,0 +1,127 @@
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
+ @logger = get_logger()
16
+ @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
17
+
18
+ #
19
+ # Features
20
+ #
21
+
22
+ def do_work(options)
23
+ break_at_one_page = false # when developing, set this to true
24
+ jira = Jira.new options[:base_uri], options[:username], options[:token], break_at_one_page
25
+
26
+ puts ""
27
+ @logger.wait "fetch epics"
28
+ epic_ids, epics = jira.get_epic_ids(options[:project], options[:epic_wildcard])
29
+ @logger.success "#{epic_ids.count} epics"
30
+
31
+ puts ""
32
+ @logger.wait "fetch issues"
33
+ issues = jira.get_issues(options[:project], epic_ids)
34
+ @logger.success "#{issues.count} issues"
35
+
36
+ puts ""
37
+ @logger.wait "fetch & merge change logs"
38
+ issues = collect_status_changelogs(jira, issues, options)
39
+ @logger.success ""
40
+
41
+ puts ""
42
+ @logger.wait "saving file"
43
+ output_filepath = File.join(@working_dir, options[:output_filename])
44
+ File.open(output_filepath, 'w') do |file|
45
+ issues.each do |o|
46
+ file.puts(o.to_json)
47
+ end
48
+ end
49
+
50
+ @logger.success " #{output_filepath}"
51
+ end
52
+
53
+ #
54
+ # Options
55
+ #
56
+
57
+ options = {
58
+ output_filename: "jira-fetch-#{Time.now.strftime("%Y%m%d%H%M%S")}.datafile"
59
+ }
60
+ global_banner = <<DOC
61
+
62
+ Fetch jira issues, status changelogs and save them to a file
63
+
64
+ Usage: #{@cl_cmd_name} [options]
65
+
66
+ DOC
67
+
68
+ global = OptionParser.new do |g|
69
+ g.banner = global_banner
70
+ add_help_and_verbose(g)
71
+
72
+ g.on("--base-uri URI", "base uri for jira (ex. https://company.atlassian.net)") do |v|
73
+ options[:base_uri] = v
74
+ end
75
+
76
+ g.on("-u", "--username USERNAME", "jira username") do |v|
77
+ options[:username] = v
78
+ end
79
+
80
+ g.on("-t", "--token TOKEN", "jira token (you can create one, google it)") do |v|
81
+ options[:token] = v
82
+ end
83
+
84
+ g.on("-p", "--project KEY", "jira project to fetch from") do |v|
85
+ options[:project] = v
86
+ end
87
+
88
+ g.on("-w", "--epic-wildcard TEXT", "wildcard to filter the epics by") do |v|
89
+ options[:epic_wildcard] = v
90
+ end
91
+
92
+ g.on("-f", "--output-filename NAME", "the name of the output file (defaults to jira-fetch-timestamp.datafile)") do |v|
93
+ options[:output_filename] = v
94
+ end
95
+
96
+ end
97
+
98
+ #
99
+ # Run
100
+ #
101
+
102
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
103
+ global.parse(ARGV)
104
+
105
+ # error on token right away
106
+ if options[:token].nil?
107
+ @logger.error "missing --token"
108
+ exit
109
+ end
110
+
111
+ # prompt for missing options
112
+ ask_and_store_option(options, :base_uri, "base_uri: ")
113
+ ask_and_store_option(options, :username, "username: ")
114
+ ask_and_store_option(options, :project, "project: ")
115
+ ask_and_store_option(options, :epic_wildcard, "epic_wildcard: ")
116
+ ask_and_store_option(options, :output_filename, "output_filename: ")
117
+
118
+ # display full command
119
+ write_history("""#{@cl_cmd_name} \\
120
+ --base-uri=#{options[:base_uri]} \\
121
+ --username=#{options[:username]} \\
122
+ --project=#{options[:project]} \\
123
+ --epic-wildcard=#{options[:epic_wildcard]} \\
124
+ --token
125
+ """)
126
+
127
+ do_work(options)
@@ -0,0 +1,199 @@
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
+ @logger = get_logger()
17
+ @cl_cmd_name = File.basename(__FILE__).split('-').join(' ')
18
+
19
+ #
20
+ # Features
21
+ #
22
+
23
+ def get_issues_from_datafile(options)
24
+ issues = []
25
+ filepath = File.join(@working_dir, options[:data_filepath])
26
+ File.foreach(filepath) do |line|
27
+ issue = JSON.parse(line)
28
+ issuetype = issue["fields"]["issuetype"]["name"]
29
+ labels = issue["fields"]["labels"]
30
+
31
+ has_excluded_labels = (labels & options[:exclude_labels]).any?
32
+ is_excluded_issuetype = options[:exclude_issuetypes].include?(issuetype.downcase)
33
+ issues << issue unless has_excluded_labels or is_excluded_issuetype
34
+ end
35
+ return issues
36
+ end
37
+
38
+ def in_range_issue_stats(issues, start_date, end_date, options)
39
+
40
+ def in_range_logs(start_date, end_date, issue)
41
+ return issue["status_changelogs"].select do |log|
42
+ (start_date..end_date).cover?(Date.parse(log["created"]))
43
+ end
44
+ end
45
+
46
+ # in-range
47
+ in_range_issues = issues.select do |issue|
48
+
49
+ # issue created date?
50
+ issue_in_range = Date.parse(issue["fields"]["created"]) < end_date
51
+
52
+ # logs created date?
53
+ logs_in_range = in_range_logs(start_date, end_date, issue).any?
54
+
55
+ # select if
56
+ (logs_in_range or issue_in_range)
57
+ end
58
+
59
+ # stat hashes
60
+ return in_range_issues.collect do |issue|
61
+
62
+ # most recent in-range log?
63
+ changelog = in_range_logs(start_date, end_date, issue)
64
+ .sort_by { |l| Date.parse(l["created"]) }.last
65
+
66
+ # yield stat hash
67
+ status = changelog ? changelog["toString"] : issue["fields"]["status"]["name"]
68
+ {
69
+ key: issue["key"],
70
+ issuetype: issue["fields"]["issuetype"]["name"],
71
+ status: status
72
+ }
73
+ end.compact
74
+ end
75
+
76
+ def print_stats(stat_hashes, start_date, end_date)
77
+ counts = {
78
+ start_date: start_date.strftime("%m-%d-%Y"),
79
+ end_date: end_date.strftime("%m-%d-%Y"),
80
+ total: 0,
81
+ total_todo: 0,
82
+ total_done: 0,
83
+ by_type: {}
84
+ }
85
+
86
+ stat_hashes.each do |stat|
87
+ issuetype = stat[:issuetype]
88
+ status = stat[:status]
89
+
90
+ # count by status
91
+ case status
92
+ when "To Do","Ready","Rework","In Progress","In QA","Ready For Code Review","In Code Review"
93
+ increment_type(counts, issuetype, :to_do)
94
+ increment(counts, :total_todo)
95
+ when "Ready to Deploy","Closed"
96
+ increment_type(counts, issuetype, :done)
97
+ increment(counts, :total_done)
98
+ else
99
+ # if status != "Won't Do"
100
+ # debugger
101
+ # end
102
+ end
103
+
104
+ # count totals
105
+ unless status=="Won't Do"
106
+ increment(counts, :total)
107
+ increment_type(counts, issuetype, :total)
108
+ end
109
+
110
+ end
111
+ return counts
112
+ end
113
+
114
+ def oldest_issue_date(issues)
115
+ return issues.collect {|i| Date.parse(i["fields"]["created"])}.sort.first.beginning_of_day
116
+ end
117
+
118
+ def increment_type(hash, type, status_category)
119
+ type = type.downcase.underscore
120
+ hash[:by_type][type] = {} unless hash[:by_type].key?(type)
121
+ hash[:by_type][type][status_category] ||= 0
122
+ hash[:by_type][type][status_category] += 1
123
+ end
124
+
125
+ def increment(hash, key)
126
+ hash[key] ||= 0
127
+ hash[key] += 1
128
+ end
129
+
130
+ def iter_date_range(start_date)
131
+ while start_date < Date.today.end_of_week
132
+ yield start_date.beginning_of_week.beginning_of_day, start_date.end_of_week.end_of_day
133
+ start_date += 1.weeks # increment
134
+ end
135
+ end
136
+
137
+ def do_work(options)
138
+ issues = get_issues_from_datafile(options)
139
+ oldest_date = oldest_issue_date(issues).beginning_of_week
140
+ iter_date_range(oldest_date) do |start_date, end_date|
141
+ stat_hashes = in_range_issue_stats(issues, start_date, end_date, options)
142
+ counts = print_stats(stat_hashes, start_date, end_date)
143
+ puts counts # print each time range
144
+ end
145
+ end
146
+
147
+ #
148
+ # Options
149
+ #
150
+
151
+ options = {
152
+ exclude_issuetypes: []
153
+ }
154
+ global_banner = <<DOC
155
+
156
+ Process jira fetch file an return stats
157
+
158
+ Usage: #{@cl_cmd_name} [options]
159
+
160
+ DOC
161
+
162
+ global = OptionParser.new do |g|
163
+ g.banner = global_banner
164
+ add_help_and_verbose(g)
165
+
166
+ g.on("-f", "--data-filepath FILEPATH", "relative path to file produced by 'cl jira fetch' command") do |v|
167
+ options[:data_filepath] = v
168
+ end
169
+
170
+ g.on("-e", "--exclude-issuetypes CSV", "comma separated list of issuetypes you want to exclude") do |v|
171
+ options[:exclude_issuetypes] = v.split(',')
172
+ options[:exclude_issuetypes] << "Won't Do"
173
+ end
174
+
175
+ g.on("-l", "--exclude-labels CSV", "comma separated list of labels that will cause a ticket to be excluded") do |v|
176
+ options[:exclude_labels] = v.split(',')
177
+ end
178
+ end
179
+
180
+ #
181
+ # Run
182
+ #
183
+
184
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
185
+ global.parse(ARGV)
186
+
187
+ # prompt for missing options
188
+ ask_and_store_option(options, :data_filepath, "data_filepath: ")
189
+ options[:exclude_issuetypes] = [] if options[:exclude_issuetypes].nil?
190
+ options[:exclude_labels] = [] if options[:exclude_labels].nil?
191
+
192
+ # display full command
193
+ write_history("""#{@cl_cmd_name} \\
194
+ --data-filepath=#{options[:data_filepath]} \\
195
+ --exclude-issuetypes=#{options[:exclude_issuetypes].join(',')} \\
196
+ --exclude-labels=#{options[:exclude_labels].join(',')}
197
+ """)
198
+
199
+ do_work(options)
data/lib/cl/magic/cl-poll CHANGED
@@ -17,10 +17,10 @@ require 'cl/magic/common/kubectl.rb'
17
17
  # Features
18
18
  #
19
19
 
20
- def do_work(options)
20
+ def do_work(options, remaining_options)
21
21
  while true
22
- tty_command = TTY::Command.new(printer: :null)
23
- out, err = tty_command.run(ARGV.join(' '))
22
+ tty_command = TTY::Command.new(printer: :null, pty: options[:pty])
23
+ out, err = tty_command.run("cd #{@working_dir} && #{remaining_options.join(' ')}")
24
24
  puts out
25
25
  puts
26
26
  sleep(1)
@@ -31,7 +31,9 @@ end
31
31
  # Options
32
32
  #
33
33
 
34
- options = {}
34
+ options = {
35
+ pty: true
36
+ }
35
37
  global_banner = <<DOC
36
38
 
37
39
  A sandbox to try things
@@ -43,10 +45,23 @@ DOC
43
45
  global = OptionParser.new do |g|
44
46
  g.banner = global_banner
45
47
  add_help_and_verbose(g)
48
+
49
+ g.on("--no-pty", "disable pseudo terminal") do |v|
50
+ options[:pty] = v
51
+ end
46
52
  end
47
53
 
48
54
  #
49
55
  # Run
50
56
  #
51
57
 
52
- do_work(options)
58
+ remaining_options = []
59
+ global.order! do |option|
60
+ remaining_options << option
61
+ end
62
+
63
+ global.parse(ARGV)
64
+
65
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
66
+
67
+ do_work(options, remaining_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,163 @@
1
+
2
+ class Jira
3
+
4
+ def initialize(base_uri, username, token, break_at_one_page=false)
5
+ @base_uri = base_uri.chomp("/")
6
+ @username = username
7
+ @token = token
8
+ @break_at_one_page = break_at_one_page
9
+ end
10
+
11
+ #
12
+ # Fetch: Issues & Change Logs
13
+ #
14
+
15
+ def get_epic_ids(project, epic_wildcard)
16
+ jql_query = "project = \"#{project}\" AND issuetype = Epic AND text ~ \"#{epic_wildcard}\""
17
+ results = run_jql_query(jql_query)
18
+ epics = results.select{|h| h['fields']['summary'].start_with? epic_wildcard}
19
+ epic_ids = epics.map {|h| h['id']}
20
+ return epic_ids, epics
21
+ end
22
+
23
+ def get_issues(project, epic_ids)
24
+ jql_query = "project = \"#{project}\" AND parentEpic IN (#{epic_ids.join(',')})"
25
+ return run_jql_query(jql_query)
26
+ end
27
+
28
+ def get_issue_status_changelog(issue_key)
29
+ uri = URI.parse("#{@base_uri}/rest/api/2/issue/#{issue_key}/changelog")
30
+ jira_get(uri) do |response|
31
+ result = JSON.parse(response.body)
32
+ return result["values"]
33
+ end
34
+ end
35
+
36
+ #
37
+ # Helpers: GET & POST
38
+ #
39
+
40
+ def jira_get(uri)
41
+ http = Net::HTTP.new(uri.host, uri.port)
42
+ http.use_ssl = true
43
+
44
+ # get request
45
+ request = Net::HTTP::Get.new(uri.path)
46
+ request.basic_auth(@username, @token)
47
+
48
+ # fetch
49
+ response = http.request(request)
50
+ if response.code == '200'
51
+ yield response
52
+ else
53
+ raise "Jira query failed with HTTP status code #{response.code}"
54
+ end
55
+ end
56
+
57
+ def jira_post(uri, body)
58
+ http = Net::HTTP.new(uri.host, uri.port)
59
+ http.use_ssl = true
60
+
61
+ # post request
62
+ request = Net::HTTP::Post.new(uri.path)
63
+ request.basic_auth(@username, @token)
64
+ request.content_type = 'application/json'
65
+ request.body = body.to_json
66
+
67
+ # fetch
68
+ response = http.request(request)
69
+ if response.code == '200'
70
+ yield response
71
+ else
72
+ raise "Jira query failed with HTTP status code #{response.code}"
73
+ end
74
+ end
75
+
76
+ #
77
+ # Fetch: JQL Query
78
+ #
79
+
80
+ def run_jql_query(jql)
81
+ start_at = 0
82
+ max_results = 50
83
+ total_results = nil
84
+ all_results = []
85
+
86
+ page_loop = true
87
+ while page_loop
88
+
89
+ uri = URI("#{@base_uri}/rest/api/2/search")
90
+ body = { jql: jql, startAt: start_at, maxResults: max_results }
91
+
92
+ # post
93
+ jira_post(uri, body) do |response|
94
+ result = JSON.parse(response.body)
95
+
96
+ # get issues
97
+ issues = result['issues']
98
+ all_results += issues
99
+
100
+ # debug: one page only
101
+ if @break_at_one_page
102
+ page_loop = false
103
+ break
104
+ end
105
+
106
+ # paginate
107
+ total_results ||= result['total']
108
+ if all_results.count == total_results
109
+ page_loop = false # we got them all, stop paging
110
+ else
111
+ start_at += max_results # else next page
112
+ end
113
+ end
114
+
115
+ print '.' # loop
116
+ end
117
+ all_results.map {|h| h}
118
+ end
119
+ end
120
+
121
+ #
122
+ # Collect status changelogs
123
+ #
124
+ # Given a array of jira issue hashes
125
+ # * fetch the change log
126
+ # * filter down to status changes
127
+ # * add it to the issue hash as ["status_changelogs"]
128
+ #
129
+
130
+ def collect_status_changelogs(jira, issues, options)
131
+ final_issue_hashes = []
132
+
133
+ issues.each do |issue|
134
+ issue_key = issue["key"]
135
+ issue["status_changelogs"] = []
136
+
137
+ # fetch change log
138
+ print '.'
139
+ changelogs = jira.get_issue_status_changelog(issue_key)
140
+
141
+ changelogs.each do |change_log|
142
+
143
+ # all items that are status changes
144
+ status_logs = change_log["items"].select {|i| i["field"]=="status"}
145
+ status_logs = status_logs.collect do |status_log|
146
+ {
147
+ "key": issue_key,
148
+ "created": change_log["created"],
149
+ "toString": status_log["toString"],
150
+ "fromString": status_log["fromString"]
151
+ }
152
+ end
153
+
154
+ # append them to issue
155
+ status_logs.each do |status_log|
156
+ issue["status_changelogs"] << status_log
157
+ end
158
+ end
159
+
160
+ final_issue_hashes << issue # save
161
+ end
162
+ return final_issue_hashes
163
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cl
4
4
  module Magic
5
- VERSION = "0.3.8"
5
+ VERSION = "0.4.0"
6
6
  end
7
7
  end
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.8
4
+ version: 0.4.0
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-21 00:00:00.000000000 Z
11
+ date: 2023-07-18 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