plan_my_stuff 0.23.1 → 0.24.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: '0853fca62201cdc20425d04f36c026ac6c774f25c66fc50c8af03153351ec3c9'
4
- data.tar.gz: bb2fcbc5a0c73e63a855171c59bea381276e2d6dc3644c46a86cf0c45b5ca0a9
3
+ metadata.gz: 1cf33c92cfa763f5ace87a1419cf8c7369f27e8adc10d1c092c044b99d151a4a
4
+ data.tar.gz: 3b2a482534b9f9094d550385c064f50f4dc37db3c2a6361e4aa899d4b4dc5713
5
5
  SHA512:
6
- metadata.gz: 2289f6bfac960098c6c5a9c8fdc90f66a1078f75fb4aea86620f2b65cfdcef201e12c4b48eabd52d5d7bebf8b488051550597544f98433bc2529c44eae4d8a11
7
- data.tar.gz: f9c66f66762f229bf9023ac85e6b636740cc74020dbe011f046b6dd391304b2e5d1416abe72bc8a7c9c8c2018c28f8fd393d370a44649fe1767f1c7d96a05fb3
6
+ metadata.gz: ef36dbcfb77d4c3763d2165993c50fb9f4f7e8b0729a1efc1d63fb9155abb039bf9db92fb59a49d3302ebfec024bafc19dd15b54be84a2d7919040405a10803f
7
+ data.tar.gz: beae8f4926a95e4a47786c4ffefc886300f255466d7e08a7e607f444e91883e920b3bf60d666d33b539eed2dac7a1241f64cd23ae42ebe292a8a113ce150de8d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.24.0
4
+
5
+ ### Added
6
+
7
+ - `PlanMyStuff::Issue.list` and `PlanMyStuff::Issue.count` now accept `issue_type:` (String / Symbol) and
8
+ `issue_fields:` (Hash keyed by display name) filter kwargs. `issue_type:` resolves symbol nicknames through
9
+ `ISSUE_TYPE_NICKNAMES` and `config.issue_types`; Arrays raise `ArgumentError` since GitHub's REST `type` param
10
+ and Search `type:` qualifier each accept only one value. `issue_fields:` accepts scalar (equality) or `Range`
11
+ (date / numeric bounds -- inclusive
12
+ `..` -> `>=`/`<=`, exclusive `...` -> `>=`/`<`, beginless / endless ranges drop the unbounded side) values and ANDs
13
+ multiple constraints together. Composes with the existing `priority_list:` filter into the same
14
+ `issue_field_values` (REST) / `field.<slug>:` (Search) qualifier list. Search-API calls flip
15
+ `advanced_search=true` when any field qualifier is present. `issue_fields:` raises
16
+ `IssueFieldsNotEnabledError` when `config.issue_fields_enabled` is `false` (closes #54).
17
+
3
18
  ## 0.23.1
4
19
 
5
20
  ### Added
@@ -296,11 +296,29 @@ module PlanMyStuff
296
296
 
297
297
  # Lists GitHub issues with optional filters and pagination.
298
298
  #
299
- # @raise [ArgumentError] when +priority_list: false+ is passed
299
+ # +issue_fields:+ is a Hash keyed by GitHub Issue Field display name (String / Symbol). Each value is either
300
+ # a scalar (equality match -- Date / Time are emitted as ISO 8601, everything else as +to_s+) or a +Range+ for
301
+ # numeric / date bounds:
302
+ #
303
+ # - +Date.parse('2026-01-01')..Date.today+ -> +start-date:>=2026-01-01,start-date:<=2026-05-21+
304
+ # - +Date.parse('2026-01-01')...Date.today+ -> +start-date:>=2026-01-01,start-date:<2026-05-21+ (exclusive end)
305
+ # - +..Date.today+ (beginless) / +Date.parse('2026-01-01')..+ (endless) drop the unbounded side
306
+ #
307
+ # Multiple field constraints AND together. Composes with the existing +priority_list:+ filter: both feed the
308
+ # same +issue_field_values+ query param.
309
+ #
310
+ # @raise [ArgumentError] when +priority_list: false+ is passed, or when +issue_type:+ is an Array (GitHub's
311
+ # REST +type+ param only accepts a single value)
312
+ # @raise [PlanMyStuff::IssueFieldsNotEnabledError] when +issue_fields:+ is passed and
313
+ # +config.issue_fields_enabled+ is +false+
300
314
  #
301
315
  # @param repo [Symbol, String, nil] defaults to config.default_repo
302
316
  # @param state [Symbol] :open, :closed, or :all
303
317
  # @param labels [Array<String>]
318
+ # @param issue_type [String, Symbol, nil] a single GitHub issue type name. Symbols resolve through
319
+ # +ISSUE_TYPE_NICKNAMES+ then +config.issue_types+ for org-specific renames.
320
+ # @param issue_fields [Hash{String,Symbol => Object,Range,nil}, nil] GitHub Issue Field equality / range
321
+ # filters. See description for the value shapes the gem accepts.
304
322
  # @param priority_list [Boolean, nil] when +true+, restricts to issues whose +Priority List+ issue field is
305
323
  # +Yes+ (server-side filter via the +issue_field_values+ query param). +false+ raises +ArgumentError+ -- GitHub
306
324
  # has no negation qualifier. Silently dropped when +config.issue_fields_enabled+ is +false+.
@@ -309,19 +327,38 @@ module PlanMyStuff
309
327
  #
310
328
  # @return [Array<PlanMyStuff::Issue>]
311
329
  #
312
- def list(repo: nil, state: :open, labels: [], priority_list: nil, page: 1, per_page: 25)
330
+ def list(
331
+ repo: nil,
332
+ state: :open,
333
+ labels: [],
334
+ issue_type: nil,
335
+ issue_fields: nil,
336
+ priority_list: nil,
337
+ page: 1,
338
+ per_page: 25
339
+ )
313
340
  if priority_list == false
314
341
  raise(ArgumentError, 'priority_list: false is not supported (no GitHub negation qualifier)')
315
342
  end
343
+ if issue_fields.present? && !PlanMyStuff.configuration.issue_fields_enabled
344
+ raise(PlanMyStuff::IssueFieldsNotEnabledError)
345
+ end
316
346
 
317
347
  client = PlanMyStuff.client
318
348
  resolved_repo = client.resolve_repo!(repo)
319
349
 
320
350
  params = { state: state.to_s, page: page, per_page: per_page }
321
351
  params[:labels] = labels.sort.join(',') if labels.present?
352
+
353
+ resolved_type = resolve_issue_types_filter(issue_type)
354
+ params[:type] = resolved_type if resolved_type.present?
355
+
356
+ field_pairs = []
357
+ field_pairs.concat(build_issue_field_filter_pairs(issue_fields)) if issue_fields.present?
322
358
  if priority_list && PlanMyStuff.configuration.issue_fields_enabled
323
- params[:issue_field_values] = 'priority-list:Yes'
359
+ field_pairs << 'priority-list:Yes'
324
360
  end
361
+ params[:issue_field_values] = field_pairs.join(',') if field_pairs.present?
325
362
 
326
363
  github_issues = client.rest(:list_issues, resolved_repo, **params)
327
364
  filtered = github_issues.reject { |gi| gi.respond_to?(:pull_request) && gi.pull_request }
@@ -347,11 +384,18 @@ module PlanMyStuff
347
384
  # - The Search API has its own rate limit (30 req/min authenticated) separate from
348
385
  # the core REST API.
349
386
  #
350
- # @raise [ArgumentError] when +priority_list: false+ is passed
387
+ # @raise [ArgumentError] when +priority_list: false+ is passed, or when +issue_type:+ is an Array
388
+ # @raise [PlanMyStuff::IssueFieldsNotEnabledError] when +issue_fields:+ is passed and
389
+ # +config.issue_fields_enabled+ is +false+
351
390
  #
352
391
  # @param repo [Symbol, String, nil] defaults to config.default_repo
353
392
  # @param state [Symbol] :open, :closed, or :all
354
393
  # @param labels [Array<String>]
394
+ # @param issue_type [String, Symbol, nil] a single GitHub issue type name. Symbols resolve through
395
+ # +ISSUE_TYPE_NICKNAMES+ then +config.issue_types+.
396
+ # @param issue_fields [Hash{String,Symbol => Object,Range,nil}, nil] GitHub Issue Field equality / range
397
+ # filters. See +.list+ for the value shapes the gem accepts. Each pair is emitted as a
398
+ # +field.<slug>:<value>+ Search qualifier and triggers +advanced_search=true+.
355
399
  # @param priority_list [Boolean, nil] when +true+, restricts to issues whose +Priority List+ issue field is
356
400
  # +Yes+ (server-side filter via the +field.priority-list:Yes+ Search qualifier). +false+ raises
357
401
  # +ArgumentError+ -- GitHub has no negation qualifier. Silently dropped when
@@ -359,10 +403,13 @@ module PlanMyStuff
359
403
  #
360
404
  # @return [Integer]
361
405
  #
362
- def count(repo: nil, state: :open, labels: [], priority_list: nil)
406
+ def count(repo: nil, state: :open, labels: [], issue_type: nil, issue_fields: nil, priority_list: nil)
363
407
  if priority_list == false
364
408
  raise(ArgumentError, 'priority_list: false is not supported (no GitHub negation qualifier)')
365
409
  end
410
+ if issue_fields.present? && !PlanMyStuff.configuration.issue_fields_enabled
411
+ raise(PlanMyStuff::IssueFieldsNotEnabledError)
412
+ end
366
413
 
367
414
  client = PlanMyStuff.client
368
415
  resolved_repo = client.resolve_repo!(repo)
@@ -374,11 +421,19 @@ module PlanMyStuff
374
421
  qualifiers += labels_to_use.map do |label|
375
422
  "label:\"#{label}\""
376
423
  end
377
- search_options = { per_page: 1 }
424
+
425
+ resolved_type = resolve_issue_types_filter(issue_type)
426
+ qualifiers << "type:#{resolved_type}" if resolved_type.present?
427
+
428
+ field_pairs = []
429
+ field_pairs.concat(build_issue_field_filter_pairs(issue_fields)) if issue_fields.present?
378
430
  if priority_list && PlanMyStuff.configuration.issue_fields_enabled
379
- qualifiers << 'field.priority-list:Yes'
380
- search_options[:advanced_search] = true
431
+ field_pairs << 'priority-list:Yes'
381
432
  end
433
+ qualifiers += field_pairs.map { |pair| "field.#{pair}" }
434
+
435
+ search_options = { per_page: 1 }
436
+ search_options[:advanced_search] = true if field_pairs.present?
382
437
  client.rest(:search_issues, qualifiers.join(' '), **search_options).total_count
383
438
  end
384
439
 
@@ -528,6 +583,93 @@ module PlanMyStuff
528
583
  PlanMyStuff.configuration.issue_types[canonical] || canonical
529
584
  end
530
585
 
586
+ # Resolves an +issue_type:+ filter kwarg (used by +.list+ / +.count+) into a single canonical type name.
587
+ # Accepts a scalar (String / Symbol); +nil+ returns +nil+ so callers can skip the filter entirely. Runs
588
+ # through +resolve_issue_type!+ so symbol nicknames and +config.issue_types+ overrides apply consistently
589
+ # with +create!+ / +update!+. Arrays raise +ArgumentError+ -- GitHub's REST +type+ param and Search
590
+ # +type:+ qualifier each accept a single value at a time.
591
+ #
592
+ # @raise [ArgumentError] when +value+ is an Array
593
+ #
594
+ # @param value [String, Symbol, nil]
595
+ #
596
+ # @return [String, nil]
597
+ #
598
+ def resolve_issue_types_filter(value)
599
+ return if value.nil?
600
+ if value.is_a?(Array)
601
+ raise(ArgumentError, 'issue_type: must be a single String / Symbol; GitHub does not accept multiple types')
602
+ end
603
+
604
+ resolve_issue_type!(value)
605
+ end
606
+
607
+ # Slugifies a field name into the kebab-case form GitHub expects in +issue_field_values+ and the Search API's
608
+ # +field.<slug>:+ qualifier (e.g. +"Priority List"+ / +:priority_list+ -> +"priority-list"+). Lowercases and
609
+ # collapses runs of whitespace or underscores into a single hyphen.
610
+ #
611
+ # @param name [String, Symbol]
612
+ #
613
+ # @return [String]
614
+ #
615
+ def field_filter_slug(name)
616
+ name.to_s.downcase.gsub(/[\s_]+/, '-')
617
+ end
618
+
619
+ # Coerces an issue-field filter value to the literal string GitHub expects in the query (Date / Time -> ISO
620
+ # 8601, Numeric / scalar -> +to_s+). Range bounds are handled separately by
621
+ # +build_issue_field_filter_pairs+.
622
+ #
623
+ # @param value [Object]
624
+ #
625
+ # @return [String]
626
+ #
627
+ def format_field_filter_value(value)
628
+ case value
629
+ when Date, Time then value.iso8601
630
+ else value.to_s
631
+ end
632
+ end
633
+
634
+ # Expands an +issue_fields:+ kwarg hash into the flat +Array<String>+ of +slug:value+ pairs that both REST
635
+ # (+issue_field_values=...+) and Search (+field.slug:value...+) consume.
636
+ #
637
+ # Scalars become a single equality pair. +Range+ values expand into one or two comparison pairs:
638
+ # +>=+/+<=+ for inclusive bounds, +<+ for an exclusive end (+...+). Beginless / endless ranges emit only
639
+ # the bounded side. +nil+ values are skipped.
640
+ #
641
+ # @param hash [Hash{String,Symbol => Object,Range,nil}]
642
+ #
643
+ # @return [Array<String>]
644
+ #
645
+ def build_issue_field_filter_pairs(hash)
646
+ hash.flat_map do |name, value|
647
+ slug = field_filter_slug(name)
648
+ case value
649
+ when nil then []
650
+ when Range then range_field_filter_pairs(slug, value)
651
+ else ["#{slug}:#{format_field_filter_value(value)}"]
652
+ end
653
+ end
654
+ end
655
+
656
+ # Expands a +Range+ value into the +slug:>=begin+ / +slug:<=end+ (or +<end+ for +...+) pair(s) GitHub expects.
657
+ #
658
+ # @param slug [String]
659
+ # @param range [Range]
660
+ #
661
+ # @return [Array<String>]
662
+ #
663
+ def range_field_filter_pairs(slug, range)
664
+ pairs = []
665
+ pairs << "#{slug}:>=#{format_field_filter_value(range.begin)}" if range.begin.present?
666
+ if range.end.present?
667
+ end_op = range.exclude_end? ? '<' : '<='
668
+ pairs << "#{slug}:#{end_op}#{format_field_filter_value(range.end)}"
669
+ end
670
+ pairs
671
+ end
672
+
531
673
  # @raise [PlanMyStuff::APIError] when the GitHub API call fails
532
674
  #
533
675
  # @return [Hash]
@@ -3,8 +3,8 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 23
7
- TINY = 1
6
+ MINOR = 24
7
+ TINY = 0
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
10
10
  PRE = nil
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plan_my_stuff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.1
4
+ version: 0.24.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brands Insurance