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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d6e87c966484bef28de8d57d3b733bb9211f7925e5943e8e45b50b02fb3e7b4
4
- data.tar.gz: 6f52d0e3729438b669558c0a57b35f4a28f4ad5269c308ede1258e3935509c74
3
+ metadata.gz: 6bc968f07247dd56c8bfa574b1ddd7bf03764d279001cc287fecd7a56b2334b8
4
+ data.tar.gz: 72c40d7b7326f2ed4455f7e5b9a1298abd547b9d92ae2b07bedfce0794e60d28
5
5
  SHA512:
6
- metadata.gz: 8b6fe6696bffdde6885de9241d44b29aab3f2a1a7ecb057f24a4c02db8aa4a1dd11deb38b9dda16539cea1b87661d9d3d930cd19fbf6186da71742419636cc92
7
- data.tar.gz: 15a37b55ce9b5771b8d7440a40b0345ee14937b76a05bfc4e40a3ed35a9a629aa82a7fbd32bb8e7e6aa9eca2366d94f98c637bed1fad6fb8010a33a9bec2d34c
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
- output_list(response.data, format: @options[:format])
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
- uuid = args[0]
196
- unless uuid
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
- parse_format_options!(args[1..] || [])
201
- issue = @client.issue_resolve(uuid)
202
- output_single(issue, format: @options[:format])
203
- success("Issue #{uuid} resolved")
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
- output_list(response.data, format: @options[:format])
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(project_id: project_id)
247
- output_list(response.data, format: @options[:format])
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'
@@ -199,8 +199,12 @@ module Bugsink
199
199
  end
200
200
 
201
201
  # Releases
202
- def releases_list(project_id:)
203
- query = { project: project_id }
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bugsink
4
- VERSION = '0.1.4'
4
+ VERSION = '0.2.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bugsink-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomáš Landovský