cl-magic 0.3.9 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,112 @@
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
+
17
+ #
18
+ # Features
19
+ #
20
+
21
+ def do_work(options)
22
+ break_at_one_page = false # when developing, set this to true
23
+ jira = Jira.new options[:base_uri], options[:username], options[:token], break_at_one_page
24
+
25
+ @logger.puts ""
26
+ @logger.wait "fetch epics"
27
+ epic_ids, epics = jira.get_epic_ids(options[:project], options[:epic_wildcard])
28
+
29
+ @logger.puts ""
30
+ @logger.wait "fetch issues"
31
+ issues = jira.get_issues_by_epic_ids(options[:project], epic_ids)
32
+
33
+ @logger.puts ""
34
+ @logger.wait "fetch change logs"
35
+ issues = jira.collect_status_changelogs(jira, issues)
36
+
37
+ @logger.puts ""
38
+ @logger.wait "fetch comments"
39
+ issues = jira.collect_comments(jira, issues)
40
+
41
+ @logger.puts ""
42
+ puts issues.to_json
43
+ end
44
+
45
+ #
46
+ # Options
47
+ #
48
+
49
+ options = {}
50
+ global_banner = <<DOC
51
+
52
+ Fetch jira issues, status changelogs and save them to a file
53
+
54
+ Usage: cl jira fetch-by-epics [options]
55
+
56
+ DOC
57
+
58
+ global = OptionParser.new do |g|
59
+ g.banner = global_banner
60
+ add_help_and_verbose(g)
61
+
62
+ g.on("--base-uri URI", "base uri for jira (ex. https://company.atlassian.net)") do |v|
63
+ options[:base_uri] = v
64
+ end
65
+
66
+ g.on("-u", "--username USERNAME", "jira username") do |v|
67
+ options[:username] = v
68
+ end
69
+
70
+ g.on("-t", "--token TOKEN", "jira token (you can create one, google it)") do |v|
71
+ options[:token] = v
72
+ end
73
+
74
+ g.on("-p", "--project KEY", "jira project to fetch from") do |v|
75
+ options[:project] = v
76
+ end
77
+
78
+ g.on("-w", "--epic-wildcard TEXT", "wildcard to filter the epics by") do |v|
79
+ options[:epic_wildcard] = v
80
+ end
81
+
82
+ end
83
+
84
+ #
85
+ # Run
86
+ #
87
+
88
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
89
+ global.parse(ARGV)
90
+
91
+ # error on token right away
92
+ if options[:token].nil?
93
+ @logger.error "missing --token"
94
+ exit
95
+ end
96
+
97
+ # prompt for missing options
98
+ ask_and_store_option(options, :base_uri, "base_uri: ")
99
+ ask_and_store_option(options, :username, "username: ")
100
+ ask_and_store_option(options, :project, "project: ")
101
+ ask_and_store_option(options, :epic_wildcard, "epic_wildcard: ")
102
+
103
+ # display full command
104
+ write_history("""cl jira fetch-by-epics \\
105
+ --base-uri=#{options[:base_uri]} \\
106
+ --username=#{options[:username]} \\
107
+ --project=#{options[:project]} \\
108
+ --epic-wildcard=#{options[:epic_wildcard]} \\
109
+ --token REDACTED
110
+ """)
111
+
112
+ do_work(options)
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env ruby
2
+ # Jira fetch datafile to markdown
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/parse_and_pick.rb'
10
+ require 'cl/magic/common/common_options.rb'
11
+ require 'cl/magic/common/logging.rb'
12
+ require 'cl/magic/common/jira.rb'
13
+
14
+ require 'json'
15
+
16
+ @logger = get_logger()
17
+
18
+
19
+ def post(url, verb, data)
20
+ cmd = """
21
+ curl -X#{verb} \
22
+ #{url} \
23
+ -H 'Content-Type: application/json' \
24
+ -d '#{data.to_json}'
25
+ """
26
+ return `#{cmd}`
27
+ end
28
+
29
+ def create_index()
30
+ url = "#{@ELASTIC_URL}/jira"
31
+
32
+ return post(url, "PUT", {
33
+ "mappings": {
34
+ "properties": {
35
+ "text": {
36
+ "type": "text"
37
+ }
38
+ }
39
+ }
40
+ })
41
+ end
42
+
43
+ # def do_work(options, data)
44
+ # #puts create_index()
45
+
46
+ # url = "#{@ELASTIC_URL}/jira/_doc/1"
47
+ # puts post(url, "POST", {
48
+ # "text": "This is a new issue created in Jira"
49
+ # })
50
+ # end
51
+
52
+
53
+ #
54
+ # Features
55
+ #
56
+
57
+ def do_work(options)
58
+ filepath = File.join(@working_dir, options[:data_filepath])
59
+ issues = JSON.parse(File.read(filepath))
60
+ issues.each do |issue|
61
+
62
+ md = []
63
+ md << "# #{issue['key']}"
64
+ md << "project: #{issue['fields']['project']['key']}"
65
+ md << "created: #{issue['fields']['created']}"
66
+ md << "updated: #{issue['fields']['updated']}"
67
+ md << "status: #{issue['fields']['status']['statusCategory']['name']}" unless issue['fields']["status"].nil?
68
+ md << "priority: #{issue['fields']['priority']['name']}"
69
+ md << "labels: #{issue['fields']['labels'].join(',')}"
70
+ md << "issue_type: #{issue['fields']['issuetype']['name']}" unless issue['fields']["issuetype"].nil?
71
+ md << "assignee: #{issue['fields']['assignee']['displayName']}" unless issue['fields']["assignee"].nil?
72
+ md << ""
73
+ md << "## Summary:"
74
+ md << "#{issue['fields']['summary']}"
75
+ # push to elastic
76
+
77
+ md << "## Comments"
78
+ md << ""
79
+ issue["comments"].each_with_index do |comment, i|
80
+ md << "### Comment by #{comment["author"]["displayName"]} "
81
+ md << ""
82
+ md << "created: #{comment["created"]}"
83
+ md << "#{comment["body"].gsub('{noformat}', "\n```\n")}"
84
+ md << ""
85
+ end
86
+ puts md
87
+ end
88
+ end
89
+
90
+ #
91
+ # Options
92
+ #
93
+
94
+ options = {}
95
+ global_banner = <<DOC
96
+
97
+ Jira fetch datafile to markdown
98
+
99
+ Usage: cl jira to-markdown [options]
100
+
101
+ DOC
102
+
103
+ global = OptionParser.new do |g|
104
+ g.banner = global_banner
105
+ add_help_and_verbose(g)
106
+
107
+ g.on("-f", "--data-filepath FILEPATH", "relative path jira datafile") do |v|
108
+ options[:data_filepath] = v
109
+ end
110
+ end
111
+
112
+ #
113
+ # Run
114
+ #
115
+
116
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
117
+ global.parse(ARGV)
118
+
119
+ ask_and_store_option(options, :data_filepath, "data_filepath: ")
120
+
121
+ # display full command
122
+ write_history("""cl jira to-markdown \\
123
+ --data-filepath=#{options[:data_filepath]}
124
+ """)
125
+
126
+ do_work(options)
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ # Jira fetch datafile to markdown
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/parse_and_pick.rb'
10
+ require 'cl/magic/common/common_options.rb'
11
+ require 'cl/magic/common/logging.rb'
12
+ require 'cl/magic/common/jira.rb'
13
+
14
+ require 'json'
15
+
16
+ @logger = get_logger()
17
+
18
+ #
19
+ # Features
20
+ #
21
+
22
+ def do_work(options)
23
+ filepath = File.join(@working_dir, options[:data_filepath])
24
+ issues = JSON.parse(File.read(filepath))
25
+ issues.each do |i|
26
+ issue_md, comments = Jira.jira_to_markdown(i)
27
+ puts issue_md
28
+ puts comments.map{ |o| o[1] }.join("\n")
29
+ end
30
+ end
31
+
32
+ #
33
+ # Options
34
+ #
35
+
36
+ options = {}
37
+ global_banner = <<DOC
38
+
39
+ Jira fetch datafile to markdown
40
+
41
+ Usage: cl jira to-markdown [options]
42
+
43
+ DOC
44
+
45
+ global = OptionParser.new do |g|
46
+ g.banner = global_banner
47
+ add_help_and_verbose(g)
48
+
49
+ g.on("-f", "--data-filepath FILEPATH", "relative path jira datafile") do |v|
50
+ options[:data_filepath] = v
51
+ end
52
+ end
53
+
54
+ #
55
+ # Run
56
+ #
57
+
58
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
59
+ global.parse(ARGV)
60
+
61
+ ask_and_store_option(options, :data_filepath, "data_filepath: ")
62
+
63
+ # display full command
64
+ write_history("""cl jira to-markdown \\
65
+ --data-filepath=#{options[:data_filepath]}
66
+ """)
67
+
68
+ do_work(options)
@@ -0,0 +1,202 @@
1
+ #!/usr/bin/env ruby
2
+ # Jira fetch datafile to 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
+
18
+ #
19
+ # Features
20
+ #
21
+
22
+ def get_issues_from_datafile(options)
23
+ final_issues = []
24
+ filepath = File.join(@working_dir, options[:data_filepath])
25
+ issues = JSON.parse(File.read(filepath))
26
+
27
+ @logger.info "stats for: #{options[:data_filepath]}"
28
+ issues.each do |issue|
29
+ issuetype = issue["fields"]["issuetype"]["name"]
30
+ labels = issue["fields"]["labels"]
31
+
32
+ has_excluded_labels = (labels & options[:exclude_labels]).any?
33
+ is_excluded_issuetype = options[:exclude_issuetypes].include?(issuetype.downcase)
34
+ final_issues << issue unless has_excluded_labels or is_excluded_issuetype
35
+ end
36
+ return final_issues
37
+ end
38
+
39
+ def in_range_issue_stats(issues, start_date, end_date, options)
40
+
41
+ def in_range_logs(start_date, end_date, issue)
42
+ return issue["status_changelogs"].select do |log|
43
+ (start_date..end_date).cover?(Date.parse(log["created"]))
44
+ end
45
+ end
46
+
47
+ # in-range
48
+ in_range_issues = issues.select do |issue|
49
+
50
+ # issue created date?
51
+ issue_in_range = Date.parse(issue["fields"]["created"]) < end_date
52
+
53
+ # logs created date?
54
+ logs_in_range = in_range_logs(start_date, end_date, issue).any?
55
+
56
+ # select if
57
+ (logs_in_range or issue_in_range)
58
+ end
59
+
60
+ # stat hashes
61
+ return in_range_issues.collect do |issue|
62
+
63
+ # most recent in-range log?
64
+ changelog = in_range_logs(start_date, end_date, issue)
65
+ .sort_by { |l| Date.parse(l["created"]) }.last
66
+
67
+ # yield stat hash
68
+ status = changelog ? changelog["toString"] : issue["fields"]["status"]["name"]
69
+ {
70
+ key: issue["key"],
71
+ issuetype: issue["fields"]["issuetype"]["name"],
72
+ status: status
73
+ }
74
+ end.compact
75
+ end
76
+
77
+ def print_stats(stat_hashes, start_date, end_date)
78
+ counts = {
79
+ start_date: start_date.strftime("%m-%d-%Y"),
80
+ end_date: end_date.strftime("%m-%d-%Y"),
81
+ total: 0,
82
+ total_todo: 0,
83
+ total_done: 0,
84
+ by_type: {}
85
+ }
86
+
87
+ stat_hashes.each do |stat|
88
+ issuetype = stat[:issuetype]
89
+ status = stat[:status]
90
+
91
+ # count by status
92
+ case status
93
+ when "To Do","Ready","Rework","In Progress","In QA","Ready For Code Review","In Code Review"
94
+ increment_type(counts, issuetype, :to_do)
95
+ increment(counts, :total_todo)
96
+ when "Ready to Deploy","Closed"
97
+ increment_type(counts, issuetype, :done)
98
+ increment(counts, :total_done)
99
+ else
100
+ # if status != "Won't Do"
101
+ # debugger
102
+ # end
103
+ end
104
+
105
+ # count totals
106
+ unless status=="Won't Do"
107
+ increment(counts, :total)
108
+ increment_type(counts, issuetype, :total)
109
+ end
110
+
111
+ end
112
+ return counts
113
+ end
114
+
115
+ def oldest_issue_date(issues)
116
+ return issues.collect {|i| Date.parse(i["fields"]["created"])}.sort.first.beginning_of_day
117
+ end
118
+
119
+ def increment_type(hash, type, status_category)
120
+ type = type.downcase.underscore
121
+ hash[:by_type][type] = {} unless hash[:by_type].key?(type)
122
+ hash[:by_type][type][status_category] ||= 0
123
+ hash[:by_type][type][status_category] += 1
124
+ end
125
+
126
+ def increment(hash, key)
127
+ hash[key] ||= 0
128
+ hash[key] += 1
129
+ end
130
+
131
+ def iter_date_range(start_date)
132
+ while start_date < Date.today.end_of_week
133
+ yield start_date.beginning_of_week.beginning_of_day, start_date.end_of_week.end_of_day
134
+ start_date += 1.weeks # increment
135
+ end
136
+ end
137
+
138
+ def do_work(options)
139
+ issues = get_issues_from_datafile(options)
140
+ oldest_date = oldest_issue_date(issues).beginning_of_week
141
+ @logger.info "starting at #{oldest_date}"
142
+
143
+ iter_date_range(oldest_date) do |start_date, end_date|
144
+ stat_hashes = in_range_issue_stats(issues, start_date, end_date, options)
145
+ counts = print_stats(stat_hashes, start_date, end_date)
146
+ puts counts # print each time range
147
+ end
148
+ end
149
+
150
+ #
151
+ # Options
152
+ #
153
+
154
+ options = {
155
+ exclude_issuetypes: []
156
+ }
157
+ global_banner = <<DOC
158
+
159
+ Jira fetch datafile to stats
160
+
161
+ Usage: cl jira to-stats [options]
162
+
163
+ DOC
164
+
165
+ global = OptionParser.new do |g|
166
+ g.banner = global_banner
167
+ add_help_and_verbose(g)
168
+
169
+ g.on("-f", "--data-filepath FILEPATH", "relative path to file produced by 'cl jira fetch' command") do |v|
170
+ options[:data_filepath] = v
171
+ end
172
+
173
+ g.on("-e", "--exclude-issuetypes CSV", "comma separated list of issuetypes you want to exclude") do |v|
174
+ options[:exclude_issuetypes] = v.split(',')
175
+ options[:exclude_issuetypes] << "Won't Do"
176
+ end
177
+
178
+ g.on("-l", "--exclude-labels CSV", "comma separated list of labels that will cause a ticket to be excluded") do |v|
179
+ options[:exclude_labels] = v.split(',')
180
+ end
181
+ end
182
+
183
+ #
184
+ # Run
185
+ #
186
+
187
+ @working_dir = ENV['CL_WORKING_DIR'] # passed through cl-magic to here
188
+ global.parse(ARGV)
189
+
190
+ # prompt for missing options
191
+ ask_and_store_option(options, :data_filepath, "data_filepath: ")
192
+ options[:exclude_issuetypes] = [] if options[:exclude_issuetypes].nil?
193
+ options[:exclude_labels] = [] if options[:exclude_labels].nil?
194
+
195
+ # display full command
196
+ write_history("""cl jira to-stats \\
197
+ --data-filepath=#{options[:data_filepath]} \\
198
+ --exclude-issuetypes=#{options[:exclude_issuetypes].join(',')} \\
199
+ --exclude-labels=#{options[:exclude_labels].join(',')}
200
+ """)
201
+
202
+ do_work(options)
@@ -17,8 +17,10 @@ require 'cl/magic/common/kubectl.rb'
17
17
  # Features
18
18
  #
19
19
 
20
+
20
21
  def do_work(options, pods, containers)
21
- cmd = "kubectl stern '#{pods.collect(&:first).join('|')}' --context #{options[:kube_context]} --namespace #{options[:namespace]} --container '#{containers.collect(&:first).join('|')}' --since #{options[:since]} --container-state 'running,waiting,terminated'"
22
+ container_name_regex = "^(#{containers.collect(&:first).join('|')})$"
23
+ cmd = "kubectl stern '#{pods.collect(&:first).join('|')}' --context #{options[:kube_context]} --namespace #{options[:namespace]} --container '#{container_name_regex}' --since #{options[:since]} --container-state 'running,waiting,terminated'"
22
24
  cmd += " | grep #{options[:grep]}" if options[:grep]
23
25
 
24
26
  @logger.puts
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)