bugsink-cli 0.1.4 → 0.2.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/bugsink/cli.rb +186 -12
- data/lib/bugsink/client.rb +6 -2
- data/lib/bugsink/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6bc968f07247dd56c8bfa574b1ddd7bf03764d279001cc287fecd7a56b2334b8
|
|
4
|
+
data.tar.gz: 72c40d7b7326f2ed4455f7e5b9a1298abd547b9d92ae2b07bedfce0794e60d28
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1a4e76f4a5e2a2cab703612e0bc76214245bb405a8063c647160257925c4117913ac61b2ca2884063623bcea52c0f5299e1fc5f8f64d93ab9960204bf03c3496
|
|
7
|
+
data.tar.gz: f968128ba88dd6126e358cfe23fc9a36bd20462844870a6de3bd64d2576e29c0bf2f08eaf8631a91aed7bdc7c73b4fd973e07023a3277774889d0ed2666126dc
|
data/lib/bugsink/cli.rb
CHANGED
|
@@ -179,9 +179,24 @@ module Bugsink
|
|
|
179
179
|
response = @client.issues_list(
|
|
180
180
|
project_id: project_id,
|
|
181
181
|
sort: @options[:sort] || 'last_seen',
|
|
182
|
-
order: @options[:order] || 'desc'
|
|
182
|
+
order: @options[:order] || 'desc',
|
|
183
|
+
limit: @options[:limit] || 250,
|
|
184
|
+
cursor: @options[:cursor]
|
|
183
185
|
)
|
|
184
|
-
|
|
186
|
+
if @options[:all]
|
|
187
|
+
response = fetch_all_pages(response) do |cursor|
|
|
188
|
+
@client.issues_list(
|
|
189
|
+
project_id: project_id,
|
|
190
|
+
sort: @options[:sort] || 'last_seen',
|
|
191
|
+
order: @options[:order] || 'desc',
|
|
192
|
+
limit: @options[:limit] || 250,
|
|
193
|
+
cursor: cursor
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
data = apply_status_filter(response.data)
|
|
198
|
+
data = apply_grep_filter_on_data(data, response)
|
|
199
|
+
output_paginated_data(data, response, format: @options[:format])
|
|
185
200
|
when 'get'
|
|
186
201
|
uuid = args[0]
|
|
187
202
|
unless uuid
|
|
@@ -192,15 +207,36 @@ module Bugsink
|
|
|
192
207
|
issue = @client.issue_get(uuid)
|
|
193
208
|
output_single(issue, format: @options[:format])
|
|
194
209
|
when 'resolve'
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
parse_resolve_options!(args)
|
|
211
|
+
uuids = args.reject { |a| a.start_with?('--') }
|
|
212
|
+
if @options[:stdin]
|
|
213
|
+
stdin_uuids = read_stdin_lines
|
|
214
|
+
uuids.concat(stdin_uuids)
|
|
215
|
+
end
|
|
216
|
+
if uuids.empty?
|
|
197
217
|
error('Issue UUID required')
|
|
198
218
|
exit 1
|
|
199
219
|
end
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
resolved = 0
|
|
221
|
+
failed = 0
|
|
222
|
+
uuids.each do |uuid|
|
|
223
|
+
begin
|
|
224
|
+
issue = @client.issue_resolve(uuid)
|
|
225
|
+
resolved += 1
|
|
226
|
+
output_single(issue, format: @options[:format]) unless @options[:format] == 'quiet'
|
|
227
|
+
puts uuid if @options[:format] == 'quiet'
|
|
228
|
+
rescue Bugsink::Client::ClientError => e
|
|
229
|
+
failed += 1
|
|
230
|
+
$stderr.puts "✗ Failed to resolve #{uuid}: #{e.message}"
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
total = resolved + failed
|
|
234
|
+
if failed > 0
|
|
235
|
+
$stderr.puts "Resolved #{resolved}/#{total} issues. #{failed} failed."
|
|
236
|
+
exit 1
|
|
237
|
+
else
|
|
238
|
+
$stderr.puts "Resolved #{resolved}/#{total} issues."
|
|
239
|
+
end
|
|
204
240
|
else
|
|
205
241
|
error("Unknown issues action: #{action}")
|
|
206
242
|
exit 1
|
|
@@ -214,9 +250,21 @@ module Bugsink
|
|
|
214
250
|
error('Issue UUID required (use --issue)') && exit(1) unless @options[:issue]
|
|
215
251
|
response = @client.events_list(
|
|
216
252
|
issue_uuid: @options[:issue],
|
|
217
|
-
order: @options[:order] || 'desc'
|
|
253
|
+
order: @options[:order] || 'desc',
|
|
254
|
+
limit: @options[:limit] || 250,
|
|
255
|
+
cursor: @options[:cursor]
|
|
218
256
|
)
|
|
219
|
-
|
|
257
|
+
if @options[:all]
|
|
258
|
+
response = fetch_all_pages(response) do |cursor|
|
|
259
|
+
@client.events_list(
|
|
260
|
+
issue_uuid: @options[:issue],
|
|
261
|
+
order: @options[:order] || 'desc',
|
|
262
|
+
limit: @options[:limit] || 250,
|
|
263
|
+
cursor: cursor
|
|
264
|
+
)
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
output_paginated(response, format: @options[:format])
|
|
220
268
|
when 'get'
|
|
221
269
|
uuid = args[0]
|
|
222
270
|
unless uuid
|
|
@@ -243,8 +291,21 @@ module Bugsink
|
|
|
243
291
|
parse_release_options!(args)
|
|
244
292
|
project_id = @options[:project_id] || @config.project_id
|
|
245
293
|
error('Project ID required (use --project or set via config)') && exit(1) unless project_id
|
|
246
|
-
response = @client.releases_list(
|
|
247
|
-
|
|
294
|
+
response = @client.releases_list(
|
|
295
|
+
project_id: project_id,
|
|
296
|
+
limit: @options[:limit] || 250,
|
|
297
|
+
cursor: @options[:cursor]
|
|
298
|
+
)
|
|
299
|
+
if @options[:all]
|
|
300
|
+
response = fetch_all_pages(response) do |cursor|
|
|
301
|
+
@client.releases_list(
|
|
302
|
+
project_id: project_id,
|
|
303
|
+
limit: @options[:limit] || 250,
|
|
304
|
+
cursor: cursor
|
|
305
|
+
)
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
output_paginated(response, format: @options[:format])
|
|
248
309
|
when 'get'
|
|
249
310
|
uuid = args[0]
|
|
250
311
|
unless uuid
|
|
@@ -269,6 +330,16 @@ module Bugsink
|
|
|
269
330
|
end
|
|
270
331
|
end
|
|
271
332
|
|
|
333
|
+
def parse_resolve_options!(args)
|
|
334
|
+
return if args.nil? || args.empty?
|
|
335
|
+
|
|
336
|
+
OptionParser.new do |opts|
|
|
337
|
+
opts.on('--stdin', 'Read UUIDs from stdin, one per line') { @options[:stdin] = true }
|
|
338
|
+
opts.on('--json', 'Output as JSON') { @options[:format] = 'json' }
|
|
339
|
+
opts.on('--quiet', 'Minimal output (resolved UUIDs only)') { @options[:format] = 'quiet' }
|
|
340
|
+
end.parse!(args)
|
|
341
|
+
end
|
|
342
|
+
|
|
272
343
|
def parse_format_options!(args)
|
|
273
344
|
return if args.nil? || args.empty?
|
|
274
345
|
|
|
@@ -288,6 +359,8 @@ module Bugsink
|
|
|
288
359
|
end.parse!(args)
|
|
289
360
|
end
|
|
290
361
|
|
|
362
|
+
VALID_STATUSES = %w[open resolved muted all].freeze
|
|
363
|
+
|
|
291
364
|
def parse_issue_options!(args)
|
|
292
365
|
return if args.nil? || args.empty?
|
|
293
366
|
|
|
@@ -295,6 +368,17 @@ module Bugsink
|
|
|
295
368
|
opts.on('--project=ID', 'Project ID') { |v| @options[:project_id] = v.to_i }
|
|
296
369
|
opts.on('--sort=FIELD', 'Sort field') { |v| @options[:sort] = v }
|
|
297
370
|
opts.on('--order=ORDER', 'Sort order (asc|desc)') { |v| @options[:order] = v }
|
|
371
|
+
opts.on('--cursor=VALUE', 'Pagination cursor') { |v| @options[:cursor] = v }
|
|
372
|
+
opts.on('--limit=N', Integer, 'Number of results per page') { |v| @options[:limit] = v }
|
|
373
|
+
opts.on('--all', 'Fetch all pages') { @options[:all] = true }
|
|
374
|
+
opts.on('--grep=PATTERN', 'Filter results by type or value (case-insensitive)') { |v| @options[:grep] = v }
|
|
375
|
+
opts.on('--status=VALUE', "Filter by status: #{VALID_STATUSES.join(', ')} (default: all)") do |v|
|
|
376
|
+
unless VALID_STATUSES.include?(v)
|
|
377
|
+
error("Invalid --status value '#{v}'. Valid values: #{VALID_STATUSES.join(', ')}")
|
|
378
|
+
exit 1
|
|
379
|
+
end
|
|
380
|
+
@options[:status] = v
|
|
381
|
+
end
|
|
298
382
|
opts.on('--json', 'Output as JSON') { @options[:format] = 'json' }
|
|
299
383
|
opts.on('--quiet', 'Minimal output') { @options[:format] = 'quiet' }
|
|
300
384
|
end.parse!(args)
|
|
@@ -306,6 +390,9 @@ module Bugsink
|
|
|
306
390
|
OptionParser.new do |opts|
|
|
307
391
|
opts.on('--issue=UUID', 'Issue UUID (required)') { |v| @options[:issue] = v }
|
|
308
392
|
opts.on('--order=ORDER', 'Sort order (asc|desc)') { |v| @options[:order] = v }
|
|
393
|
+
opts.on('--cursor=VALUE', 'Pagination cursor') { |v| @options[:cursor] = v }
|
|
394
|
+
opts.on('--limit=N', Integer, 'Number of results per page') { |v| @options[:limit] = v }
|
|
395
|
+
opts.on('--all', 'Fetch all pages') { @options[:all] = true }
|
|
309
396
|
opts.on('--json', 'Output as JSON') { @options[:format] = 'json' }
|
|
310
397
|
opts.on('--quiet', 'Minimal output') { @options[:format] = 'quiet' }
|
|
311
398
|
end.parse!(args)
|
|
@@ -316,11 +403,18 @@ module Bugsink
|
|
|
316
403
|
|
|
317
404
|
OptionParser.new do |opts|
|
|
318
405
|
opts.on('--project=ID', 'Project ID') { |v| @options[:project_id] = v.to_i }
|
|
406
|
+
opts.on('--cursor=VALUE', 'Pagination cursor') { |v| @options[:cursor] = v }
|
|
407
|
+
opts.on('--limit=N', Integer, 'Number of results per page') { |v| @options[:limit] = v }
|
|
408
|
+
opts.on('--all', 'Fetch all pages') { @options[:all] = true }
|
|
319
409
|
opts.on('--json', 'Output as JSON') { @options[:format] = 'json' }
|
|
320
410
|
opts.on('--quiet', 'Minimal output') { @options[:format] = 'quiet' }
|
|
321
411
|
end.parse!(args)
|
|
322
412
|
end
|
|
323
413
|
|
|
414
|
+
def read_stdin_lines
|
|
415
|
+
$stdin.each_line.map(&:strip).reject(&:empty?)
|
|
416
|
+
end
|
|
417
|
+
|
|
324
418
|
def parse_json_arg(json_str)
|
|
325
419
|
return {} unless json_str
|
|
326
420
|
|
|
@@ -330,6 +424,86 @@ module Bugsink
|
|
|
330
424
|
exit 1
|
|
331
425
|
end
|
|
332
426
|
|
|
427
|
+
def fetch_all_pages(first_response)
|
|
428
|
+
response = first_response
|
|
429
|
+
while response.has_more?
|
|
430
|
+
next_response = yield(response.next_cursor)
|
|
431
|
+
response.add_data(next_response.data)
|
|
432
|
+
# Transfer cursor state so we can keep looping
|
|
433
|
+
response.instance_variable_set(:@next_cursor, next_response.next_cursor)
|
|
434
|
+
end
|
|
435
|
+
response
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
def apply_status_filter(data)
|
|
439
|
+
case @options[:status]
|
|
440
|
+
when 'open'
|
|
441
|
+
data.select { |i| !i['is_resolved'] && !i['is_muted'] }
|
|
442
|
+
when 'resolved'
|
|
443
|
+
data.select { |i| i['is_resolved'] }
|
|
444
|
+
when 'muted'
|
|
445
|
+
data.select { |i| i['is_muted'] }
|
|
446
|
+
else
|
|
447
|
+
data
|
|
448
|
+
end
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def apply_grep_filter_on_data(data, response)
|
|
452
|
+
return data unless @options[:grep]
|
|
453
|
+
|
|
454
|
+
pattern = @options[:grep].downcase
|
|
455
|
+
original_count = data.size
|
|
456
|
+
filtered = data.select do |issue|
|
|
457
|
+
(issue['calculated_type']&.downcase&.include?(pattern)) ||
|
|
458
|
+
(issue['calculated_value']&.downcase&.include?(pattern))
|
|
459
|
+
end
|
|
460
|
+
$stderr.puts "#{filtered.size} of #{original_count} issues match '#{@options[:grep]}'"
|
|
461
|
+
if response.has_more? && !@options[:all]
|
|
462
|
+
$stderr.puts "Note: --grep only filters the current page. Use --all --grep to search all issues."
|
|
463
|
+
end
|
|
464
|
+
filtered
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def apply_grep_filter(response)
|
|
468
|
+
apply_grep_filter_on_data(response.data, response)
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def output_paginated_data(data, response, format:)
|
|
472
|
+
case format
|
|
473
|
+
when 'json'
|
|
474
|
+
if response.has_more?
|
|
475
|
+
puts JSON.pretty_generate({ 'data' => data, 'next_cursor' => response.next_cursor })
|
|
476
|
+
else
|
|
477
|
+
puts JSON.pretty_generate(data)
|
|
478
|
+
end
|
|
479
|
+
when 'quiet'
|
|
480
|
+
data.each { |item| puts item['id'] || item['uuid'] }
|
|
481
|
+
else
|
|
482
|
+
output_table(data)
|
|
483
|
+
if response.has_more?
|
|
484
|
+
$stderr.puts "Showing #{response.data.size} results. More available — use --cursor=#{response.next_cursor} or --all"
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
def output_paginated(response, format:)
|
|
490
|
+
case format
|
|
491
|
+
when 'json'
|
|
492
|
+
if response.has_more?
|
|
493
|
+
puts JSON.pretty_generate({ 'data' => response.data, 'next_cursor' => response.next_cursor })
|
|
494
|
+
else
|
|
495
|
+
puts JSON.pretty_generate(response.data)
|
|
496
|
+
end
|
|
497
|
+
when 'quiet'
|
|
498
|
+
response.data.each { |item| puts item['id'] || item['uuid'] }
|
|
499
|
+
else
|
|
500
|
+
output_table(response.data)
|
|
501
|
+
if response.has_more?
|
|
502
|
+
$stderr.puts "Showing #{response.data.size} results. More available — use --cursor=#{response.next_cursor} or --all"
|
|
503
|
+
end
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
333
507
|
def output_list(data, format:)
|
|
334
508
|
case format
|
|
335
509
|
when 'json'
|
data/lib/bugsink/client.rb
CHANGED
|
@@ -199,8 +199,12 @@ module Bugsink
|
|
|
199
199
|
end
|
|
200
200
|
|
|
201
201
|
# Releases
|
|
202
|
-
def releases_list(project_id:)
|
|
203
|
-
query = {
|
|
202
|
+
def releases_list(project_id:, limit: 250, cursor: nil)
|
|
203
|
+
query = {
|
|
204
|
+
project: project_id,
|
|
205
|
+
limit: limit
|
|
206
|
+
}
|
|
207
|
+
query[:cursor] = cursor if cursor
|
|
204
208
|
|
|
205
209
|
response = self.class.get('/api/canonical/0/releases/', query: query)
|
|
206
210
|
check_response(response)
|
data/lib/bugsink/version.rb
CHANGED