plan_my_stuff 0.17.0 → 0.19.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.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/CONFIGURATION.md +53 -1
  4. data/README.md +2 -30
  5. data/app/controllers/plan_my_stuff/comments_controller.rb +21 -10
  6. data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +13 -4
  7. data/app/controllers/plan_my_stuff/issues/closures_controller.rb +12 -6
  8. data/app/controllers/plan_my_stuff/issues/links_controller.rb +14 -8
  9. data/app/controllers/plan_my_stuff/issues/takes_controller.rb +19 -17
  10. data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +15 -9
  11. data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +14 -8
  12. data/app/controllers/plan_my_stuff/issues_controller.rb +19 -5
  13. data/app/controllers/plan_my_stuff/labels_controller.rb +14 -8
  14. data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +6 -0
  15. data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +3 -0
  16. data/app/controllers/plan_my_stuff/project_items_controller.rb +11 -4
  17. data/app/controllers/plan_my_stuff/projects_controller.rb +14 -0
  18. data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +3 -0
  19. data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +5 -0
  20. data/app/controllers/plan_my_stuff/testing_projects_controller.rb +12 -0
  21. data/app/views/plan_my_stuff/issues/index.html.erb +1 -1
  22. data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +5 -5
  23. data/app/views/plan_my_stuff/issues/partials/_form.html.erb +1 -1
  24. data/app/views/plan_my_stuff/issues/partials/_links.html.erb +3 -3
  25. data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -2
  26. data/app/views/plan_my_stuff/issues/show.html.erb +8 -8
  27. data/app/views/plan_my_stuff/projects/show.html.erb +3 -1
  28. data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +1 -1
  29. data/lib/generators/plan_my_stuff/install/templates/initializer.rb +6 -0
  30. data/lib/plan_my_stuff/configuration.rb +61 -5
  31. data/lib/plan_my_stuff/issue.rb +91 -15
  32. data/lib/plan_my_stuff/repo.rb +28 -0
  33. data/lib/plan_my_stuff/version.rb +1 -1
  34. metadata +2 -2
@@ -255,16 +255,20 @@ module PlanMyStuff
255
255
 
256
256
  # Finds a single GitHub issue by number and parses its PMS metadata.
257
257
  #
258
+ # Accepts a numeric id (Integer or all-digit String) plus an optional +repo:+ kwarg, or a nickname-id String
259
+ # (e.g. +"Rawr-1234"+) where the repo is encoded in the prefix and +repo:+ is ignored.
260
+ #
258
261
  # @raise [Octokit::NotFound] when the issue number resolves to a pull request
262
+ # @raise [ArgumentError] when a nickname-id String references an unknown repo nickname
259
263
  #
260
- # @param number [Integer]
261
- # @param repo [Symbol, String, nil] defaults to config.default_repo
264
+ # @param id_or_number [Integer, String]
265
+ # @param repo [Symbol, String, nil] defaults to config.default_repo; ignored when +id_or_number+ is a nickname id
262
266
  #
263
267
  # @return [PlanMyStuff::Issue]
264
268
  #
265
- def find(number, repo: nil)
269
+ def find(id_or_number, repo: nil)
270
+ number, resolved_repo = resolve_find_args(id_or_number, repo)
266
271
  client = PlanMyStuff.client
267
- resolved_repo = client.resolve_repo!(repo)
268
272
 
269
273
  github_issue =
270
274
  fetch_with_etag_cache(
@@ -393,8 +397,68 @@ module PlanMyStuff
393
397
  raise(PlanMyStuff::APIError.new(e.message, status: e.respond_to?(:response_status) ? e.response_status : nil))
394
398
  end
395
399
 
400
+ # @raise [ArgumentError] when +repo+ resolves to a Repo with no configured key (cannot reverse-resolve through
401
+ # +Repo.from_nickname!+, so the resulting token would not round-trip through +Issue.find+ / +from_param+)
402
+ #
403
+ # @param number [Integer]
404
+ # @param repo [String] full repo path, e.g. +"BrandsInsurance/Element"+
405
+ #
406
+ # @return [String]
407
+ #
408
+ def to_param(number, repo)
409
+ return if number.blank?
410
+ return if repo.blank?
411
+
412
+ repo_obj = PlanMyStuff::Repo.resolve!(repo)
413
+ if repo_obj.key.nil?
414
+ raise(
415
+ ArgumentError,
416
+ "Repo #{repo_obj.full_name.inspect} is not configured in config.repos; " \
417
+ 'cannot build reversible Issue#to_param token',
418
+ )
419
+ end
420
+
421
+ "#{repo_obj.nickname}-#{number}"
422
+ end
423
+
424
+ # Parses an +Issue#to_param+ string of the form +"Nickname-1234"+ back into +[Repo, Integer]+. The repo is
425
+ # looked up via +PlanMyStuff::Repo.from_nickname!+, which scans +config.repos+ for the key whose
426
+ # +config.repo_nickname_for+ matches.
427
+ #
428
+ # @raise [ArgumentError] when +param+ does not match the +"Prefix-1234"+ shape or the prefix is not a known
429
+ # repo nickname
430
+ #
431
+ # @param param [String]
432
+ #
433
+ # @return [Array(PlanMyStuff::Repo, Integer)]
434
+ #
435
+ def from_param(param)
436
+ match = param.to_s.match(/\A(?<nickname>.+)-(?<number>\d+)\z/)
437
+ raise(ArgumentError, "Invalid issue param: #{param.inspect}") if match.nil?
438
+
439
+ [PlanMyStuff::Repo.from_nickname!(match[:nickname]), match[:number].to_i]
440
+ end
441
+
396
442
  private
397
443
 
444
+ # Splits the +Issue.find+ first arg into +[number, resolved_repo_full_name]+. A nickname-id String like
445
+ # +"Rawr-1234"+ is decoded via +from_param+ (repo derived from the prefix; +repo:+ kwarg ignored). All other
446
+ # inputs (Integer, all-digit String) fall through to the existing +client.resolve_repo!+ path with the kwarg.
447
+ #
448
+ # @param id_or_number [Integer, String]
449
+ # @param repo [Symbol, String, PlanMyStuff::Repo, nil]
450
+ #
451
+ # @return [Array(Integer, String)]
452
+ #
453
+ def resolve_find_args(id_or_number, repo)
454
+ if id_or_number.is_a?(String) && !id_or_number.match?(/\A\d+\z/)
455
+ repo_obj, number = from_param(id_or_number)
456
+ [number, repo_obj.full_name]
457
+ else
458
+ [id_or_number.to_i, PlanMyStuff.client.resolve_repo!(repo)]
459
+ end
460
+ end
461
+
398
462
  # Resolves an +issue_type:+ kwarg to the literal display name GitHub expects. Two stages: a Symbol is first
399
463
  # looked up in the gem-side +ISSUE_TYPE_NICKNAMES+ to get a canonical name; then the canonical (or
400
464
  # directly-provided String) name is passed through +config.issue_types+ for org-specific renames. Missing
@@ -443,9 +507,8 @@ module PlanMyStuff
443
507
  end
444
508
 
445
509
  # Builds the visible body string written to GitHub for an issue: a markdown link to the consuming-app
446
- # per-issue URL (carrying the repo as a +?repo=+ query param so the consuming app knows which repo this issue
447
- # lives in), labelled with the GitHub +Org/Repo#number+. Returns +""+ when either +config.issues_url_prefix+
448
- # or +number+ is missing.
510
+ # per-issue URL (in +Issue#to_param+ form, e.g. +"/issues/Rawr-1234"+), labelled with the GitHub
511
+ # +Org/Repo#number+. Returns +""+ when either +config.issues_url_prefix+ or +number+ is missing.
449
512
  #
450
513
  # @param number [Integer]
451
514
  # @param repo [String] full repo path, e.g. +"BrandsInsurance/Element"+
@@ -456,7 +519,8 @@ module PlanMyStuff
456
519
  prefix = PlanMyStuff.configuration.issues_url_prefix
457
520
  return '' if prefix.blank? || number.blank?
458
521
 
459
- url = "#{prefix.to_s.chomp('/')}/#{number}?repo=#{URI.encode_www_form_component(repo)}"
522
+ to_par = to_param(number, repo)
523
+ url = "#{prefix.to_s.chomp('/')}/#{to_par}"
460
524
  "[#{repo}##{number}](#{url})"
461
525
  end
462
526
 
@@ -526,17 +590,29 @@ module PlanMyStuff
526
590
  @body_dirty = true
527
591
  end
528
592
 
529
- # @return [String, nil] per-issue URL in the consuming app (+config.issues_url_prefix+ + +"/"+ + +number+ +
530
- # +"?repo=Org/Repo"+, or +nil+ when either prefix or number is missing). Also rendered as the destination of
531
- # the markdown link in the GitHub issue body.
593
+ # Single-segment URL token combining repo nickname and issue number, used by Rails route helpers
594
+ # (+youtrack_issue_path(@issue)+ -> +"/issues/Rawr-1234"+). Returns +nil+ for new records or when +number+ or
595
+ # +repo+ is unset; +Issue.from_param+ parses the same shape back into +[Repo, Integer]+.
596
+ #
597
+ # @return [String, nil]
598
+ #
599
+ def to_param
600
+ return if new_record?
601
+
602
+ self.class.to_param(number, repo)
603
+ end
604
+
605
+ # @return [String, nil] per-issue URL in the consuming app (+config.issues_url_prefix+ + +"/"+ + +to_param+, or
606
+ # +nil+ when prefix, number, or repo is missing). Also rendered as the destination of the markdown link in
607
+ # the GitHub issue body.
532
608
  def user_link
533
609
  prefix = PlanMyStuff.configuration.issues_url_prefix
534
- return if prefix.blank? || number.blank?
610
+ return if prefix.blank?
535
611
 
536
- base = "#{prefix.to_s.chomp('/')}/#{number}"
537
- return base if repo.blank?
612
+ to_par = to_param
613
+ return if to_par.blank?
538
614
 
539
- "#{base}?repo=#{URI.encode_www_form_component(repo.full_name)}"
615
+ "#{prefix.to_s.chomp('/')}/#{to_par}"
540
616
  end
541
617
 
542
618
  # Tags the issue with the configured +archived_label+, removes it from every Projects V2 board it belongs to,
@@ -55,6 +55,23 @@ module PlanMyStuff
55
55
  end
56
56
  end
57
57
 
58
+ # Reverse lookup for the +Issue#to_param+ prefix: finds the configured repo whose nickname (per
59
+ # +config.repo_nickname_for+) matches +nickname+ and returns its +Repo+ instance.
60
+ #
61
+ # @raise [ArgumentError] if no configured repo has the given nickname
62
+ #
63
+ # @param nickname [String]
64
+ #
65
+ # @return [PlanMyStuff::Repo]
66
+ #
67
+ def from_nickname!(nickname)
68
+ config = PlanMyStuff.configuration
69
+ match = config.repos.keys.find { |key| config.repo_nickname_for(key) == nickname }
70
+ raise(ArgumentError, "Unknown repo nickname: #{nickname.inspect}") if match.nil?
71
+
72
+ resolve!(match)
73
+ end
74
+
58
75
  private
59
76
 
60
77
  # @raise [ArgumentError] if full_name is not in "Org/Repo" format
@@ -88,6 +105,17 @@ module PlanMyStuff
88
105
  "#{organization}/#{name}"
89
106
  end
90
107
 
108
+ # Human-readable repo label used as the +Issue#to_param+ prefix. Resolves through +config.repo_nickname_for+
109
+ # when this repo carries a configured +key+; falls back to the bare repo +name+ for unconfigured repos.
110
+ #
111
+ # @return [String]
112
+ #
113
+ def nickname
114
+ return PlanMyStuff.configuration.repo_nickname_for(key) if key
115
+
116
+ name
117
+ end
118
+
91
119
  # @see #full_name
92
120
  alias to_s full_name
93
121
 
@@ -3,7 +3,7 @@
3
3
  module PlanMyStuff
4
4
  module VERSION
5
5
  MAJOR = 0
6
- MINOR = 17
6
+ MINOR = 19
7
7
  TINY = 0
8
8
 
9
9
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plan_my_stuff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.19.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brands Insurance
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-05-19 00:00:00.000000000 Z
11
+ date: 2026-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails