plan_my_stuff 0.17.0 → 0.18.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/CHANGELOG.md +22 -0
- data/CONFIGURATION.md +5 -1
- data/app/controllers/plan_my_stuff/comments_controller.rb +8 -8
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +4 -4
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +6 -6
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +8 -8
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +11 -17
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +9 -9
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +8 -8
- data/app/controllers/plan_my_stuff/issues_controller.rb +5 -5
- data/app/controllers/plan_my_stuff/labels_controller.rb +8 -8
- data/app/views/plan_my_stuff/issues/index.html.erb +1 -1
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +5 -5
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +1 -1
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +3 -3
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +2 -2
- data/app/views/plan_my_stuff/issues/show.html.erb +8 -8
- data/app/views/plan_my_stuff/projects/show.html.erb +3 -1
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +1 -1
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +6 -0
- data/lib/plan_my_stuff/configuration.rb +61 -5
- data/lib/plan_my_stuff/issue.rb +91 -15
- data/lib/plan_my_stuff/repo.rb +28 -0
- data/lib/plan_my_stuff/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: bded03d75c7bd996c62c647b038a588a4286628e7f041e385d4464ea87955505
|
|
4
|
+
data.tar.gz: 5b778d271d85a1ad5681d8617dec41875d5664d0c3ea861ab9421bef962de4d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: db446cfa7623c4091f67cecd89ab7dfe84207237e601fd53f4a73401fc02d2019072801c326d281febb54373817cb87d35c60e2929a2ec425f1aca3e7623ef49
|
|
7
|
+
data.tar.gz: b7f4328f697271829202ee201f4c05a9e1553bc95d50b721cfc3132462c0401428b87e22bd79cdeb4962e249bea991b3f5d39cab9cb1a3c048ef13a647e31312
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.18.0
|
|
4
|
+
|
|
5
|
+
### Breaking
|
|
6
|
+
|
|
7
|
+
- `PMS::Issue#to_param` now returns `"Nickname-number"` (e.g. `"Rawr-1234"`) instead of the default ActiveModel id.
|
|
8
|
+
Single-issue URLs change from `/issues/1234?repo=rawr` to `/issues/Rawr-1234`. Consuming apps using
|
|
9
|
+
`youtrack_issue_path(@issue)` work natively; any hand-rolled URLs that passed `@issue.number` plus a `repo:`
|
|
10
|
+
query param must switch to `@issue` (or `@issue.to_param`). The mounted engine's controllers and views have
|
|
11
|
+
been updated to the new shape, as has the markdown link embedded in the GitHub issue body via
|
|
12
|
+
`Issue#user_link`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `config.repo_nicknames` (default `{}`) -- Symbol-keyed map from repo key to the human-readable label used as
|
|
17
|
+
the `Issue#to_param` prefix. Missing keys fall back to `key.to_s.titleize`, so `:rawr` -> `"Rawr"` is free;
|
|
18
|
+
only divergent ones (e.g. `safety: 'Compliance'`) need an entry.
|
|
19
|
+
- `Configuration#repo_nickname_for(key)` accessor, `Repo#nickname`, and `Repo.from_nickname!` for the inverse
|
|
20
|
+
lookup used by `Issue.from_param`.
|
|
21
|
+
- `Issue#to_param` and `Issue.from_param` -- the latter parses `"Nickname-1234"` back into `[Repo, Integer]`.
|
|
22
|
+
- `Issue.find` first arg now accepts a nickname-id String (e.g. `"Rawr-1234"`) in addition to Integer / digit-
|
|
23
|
+
String + `repo:` kwarg. The nickname form ignores `repo:`.
|
|
24
|
+
|
|
3
25
|
## 0.17.0
|
|
4
26
|
|
|
5
27
|
### Added
|
data/CONFIGURATION.md
CHANGED
|
@@ -37,13 +37,17 @@ config.organization = 'YourOrg'
|
|
|
37
37
|
|---|---|---|---|
|
|
38
38
|
| `repos` | `Hash{Symbol => String}` | `{}` | Named repo configs mapping a key to an `Org/Repo` string. |
|
|
39
39
|
| `default_repo` | `Symbol, nil` | `nil` | Repo key used when callers omit the `repo:` param. |
|
|
40
|
+
| `repo_nicknames` | `Hash{Symbol => String}` | `{}` | `Issue#to_param` prefix override (default `key.titleize`). |
|
|
40
41
|
|
|
41
42
|
```ruby
|
|
42
43
|
config.repos = { element: 'YourOrg/Element', underwriter: 'YourOrg/Underwriter' }
|
|
43
44
|
config.default_repo = :element
|
|
45
|
+
config.repo_nicknames = { safety: 'Compliance' } # :element -> "Element", :underwriter -> "Underwriter" come free
|
|
44
46
|
```
|
|
45
47
|
|
|
46
|
-
`repos` can be mutated via `config.repos[:key] = '...'` or set via `config.repos = { key: 'MyOrg/MyRepo' }
|
|
48
|
+
`repos` can be mutated via `config.repos[:key] = '...'` or set via `config.repos = { key: 'MyOrg/MyRepo' }`.
|
|
49
|
+
`Issue#to_param` then returns `"Element-1234"` / `"Compliance-567"`, encoding both repo and number in a single
|
|
50
|
+
URL segment so `youtrack_issue_path(@issue)` works without a `repo:` query param.
|
|
47
51
|
|
|
48
52
|
## Projects
|
|
49
53
|
|
|
@@ -4,7 +4,7 @@ module PlanMyStuff
|
|
|
4
4
|
class CommentsController < PlanMyStuff::ApplicationController
|
|
5
5
|
# POST /issues/:issue_id/comments
|
|
6
6
|
def create
|
|
7
|
-
@issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
7
|
+
@issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
8
8
|
|
|
9
9
|
PlanMyStuff::Comment.create!(
|
|
10
10
|
issue: @issue,
|
|
@@ -15,11 +15,11 @@ module PlanMyStuff
|
|
|
15
15
|
)
|
|
16
16
|
|
|
17
17
|
flash[:success] = 'Comment was successfully created.'
|
|
18
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
18
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
19
19
|
rescue PlanMyStuff::LockedIssueError => e
|
|
20
20
|
pms_handle_rescue(e)
|
|
21
21
|
flash[:error] = 'This issue is locked; no new comments can be posted.'
|
|
22
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
22
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# GET /issues/:issue_id/comments/:id/edit
|
|
@@ -29,7 +29,7 @@ module PlanMyStuff
|
|
|
29
29
|
return redirect_to_issue if issue_body_comment?
|
|
30
30
|
return if can_edit?(@comment)
|
|
31
31
|
|
|
32
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue
|
|
32
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue))
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
# PATCH/PUT /issues/:issue_id/comments/:id
|
|
@@ -39,7 +39,7 @@ module PlanMyStuff
|
|
|
39
39
|
return redirect_to_issue if issue_body_comment?
|
|
40
40
|
|
|
41
41
|
unless can_edit?(@comment)
|
|
42
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue
|
|
42
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(@issue))
|
|
43
43
|
|
|
44
44
|
return
|
|
45
45
|
end
|
|
@@ -50,7 +50,7 @@ module PlanMyStuff
|
|
|
50
50
|
@comment.update!(**update_attrs, user: pms_current_user)
|
|
51
51
|
|
|
52
52
|
flash[:success] = 'Comment was successfully updated.'
|
|
53
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
53
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
54
54
|
rescue PlanMyStuff::StaleObjectError => e
|
|
55
55
|
pms_handle_rescue(e)
|
|
56
56
|
flash.now[:error] = 'Comment was modified by someone else. Please review the latest changes and try again.'
|
|
@@ -69,7 +69,7 @@ module PlanMyStuff
|
|
|
69
69
|
# @return [void]
|
|
70
70
|
#
|
|
71
71
|
def load_comment
|
|
72
|
-
@issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
72
|
+
@issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
73
73
|
@comment = PlanMyStuff::Comment.find(params[:id].to_i, issue: @issue)
|
|
74
74
|
end
|
|
75
75
|
|
|
@@ -96,7 +96,7 @@ module PlanMyStuff
|
|
|
96
96
|
|
|
97
97
|
# @return [void]
|
|
98
98
|
def redirect_to_issue
|
|
99
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
99
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
100
100
|
end
|
|
101
101
|
end
|
|
102
102
|
end
|
|
@@ -23,7 +23,7 @@ module PlanMyStuff
|
|
|
23
23
|
return
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
26
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
27
27
|
issue.request_approvals!(user_ids: user_ids, user: pms_current_user)
|
|
28
28
|
|
|
29
29
|
flash[:success] = 'Approvers were successfully added.'
|
|
@@ -44,7 +44,7 @@ module PlanMyStuff
|
|
|
44
44
|
return
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
47
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
48
48
|
caller_id = pms_current_user.present? ? PlanMyStuff::UserResolver.user_id(pms_current_user) : nil
|
|
49
49
|
|
|
50
50
|
case status
|
|
@@ -85,7 +85,7 @@ module PlanMyStuff
|
|
|
85
85
|
return
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
88
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
89
89
|
issue.remove_approvers!(user_ids: [params[:id].to_i], user: pms_current_user)
|
|
90
90
|
|
|
91
91
|
flash[:success] = 'Approver was successfully removed.'
|
|
@@ -105,7 +105,7 @@ module PlanMyStuff
|
|
|
105
105
|
|
|
106
106
|
# @return [String]
|
|
107
107
|
def show_path
|
|
108
|
-
plan_my_stuff.issue_path(params[:issue_id]
|
|
108
|
+
plan_my_stuff.issue_path(params[:issue_id])
|
|
109
109
|
end
|
|
110
110
|
end
|
|
111
111
|
end
|
|
@@ -11,28 +11,28 @@ module PlanMyStuff
|
|
|
11
11
|
class ClosuresController < PlanMyStuff::ApplicationController
|
|
12
12
|
# POST /issues/:issue_id/closure
|
|
13
13
|
def create
|
|
14
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
14
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
15
15
|
issue.update!(state: :closed)
|
|
16
16
|
|
|
17
17
|
flash[:success] = 'Issue was successfully closed.'
|
|
18
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
18
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
19
19
|
rescue PlanMyStuff::Error, ArgumentError => e
|
|
20
20
|
pms_handle_rescue(e)
|
|
21
21
|
flash[:error] = e.message
|
|
22
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
22
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# DELETE /issues/:issue_id/closure
|
|
26
26
|
def destroy
|
|
27
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
27
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
28
28
|
issue.update!(state: :open)
|
|
29
29
|
|
|
30
30
|
flash[:success] = 'Issue was successfully reopened.'
|
|
31
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
31
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
32
32
|
rescue PlanMyStuff::Error, ArgumentError => e
|
|
33
33
|
pms_handle_rescue(e)
|
|
34
34
|
flash[:error] = e.message
|
|
35
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
35
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
end
|
|
@@ -21,38 +21,38 @@ module PlanMyStuff
|
|
|
21
21
|
def create
|
|
22
22
|
type = link_params[:type].to_s
|
|
23
23
|
unless dispatch_allowed?(type, :add)
|
|
24
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
24
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
25
25
|
return
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
28
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
29
29
|
link = add_link(issue, type)
|
|
30
30
|
|
|
31
31
|
flash[:success] = "Linked #{link}"
|
|
32
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
32
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
33
33
|
rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
|
|
34
34
|
pms_handle_rescue(e)
|
|
35
35
|
flash[:error] = e.message
|
|
36
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
36
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
# DELETE /issues/:issue_id/links/:id
|
|
40
40
|
def destroy
|
|
41
41
|
type, repo, number = parse_composite_id(params[:id])
|
|
42
42
|
unless dispatch_allowed?(type, :remove)
|
|
43
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
43
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
44
44
|
return
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
47
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
48
48
|
remove_link(issue, type, repo: repo, number: number)
|
|
49
49
|
|
|
50
50
|
flash[:success] = "Unlinked #{repo}##{number}"
|
|
51
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
51
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
52
52
|
rescue PlanMyStuff::ValidationError, ActiveModel::ValidationError, ArgumentError => e
|
|
53
53
|
pms_handle_rescue(e)
|
|
54
54
|
flash[:error] = e.message
|
|
55
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
55
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
private
|
|
@@ -11,37 +11,31 @@ module PlanMyStuff
|
|
|
11
11
|
|
|
12
12
|
# POST /issues/:issue_id/take
|
|
13
13
|
def create
|
|
14
|
-
|
|
15
|
-
repo = params[:repo]
|
|
16
|
-
|
|
17
|
-
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
14
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
18
15
|
guard_already_taken!(issue)
|
|
19
16
|
|
|
20
|
-
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(
|
|
17
|
+
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue.number)
|
|
21
18
|
project_item ||= add_to_pipeline(issue)
|
|
22
19
|
|
|
23
20
|
PlanMyStuff::Pipeline.take!(project_item)
|
|
24
21
|
assign_current_user(project_item)
|
|
25
|
-
flash[:success] ||= "Issue ##{
|
|
22
|
+
flash[:success] ||= "Issue ##{issue.number} taken."
|
|
26
23
|
|
|
27
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
24
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
28
25
|
rescue ArgumentError, PlanMyStuff::Error => e
|
|
29
26
|
pms_handle_rescue(e)
|
|
30
27
|
flash[:error] = e.message
|
|
31
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
28
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
32
29
|
end
|
|
33
30
|
|
|
34
31
|
# DELETE /issues/:issue_id/take
|
|
35
32
|
def destroy
|
|
36
|
-
|
|
37
|
-
repo = params[:repo]
|
|
38
|
-
|
|
39
|
-
issue = PlanMyStuff::Issue.find(issue_number, repo: repo)
|
|
33
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
40
34
|
login = current_user_login
|
|
41
35
|
guard_release!(issue, login)
|
|
42
36
|
|
|
43
37
|
remaining = issue.assignees - [login]
|
|
44
|
-
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(
|
|
38
|
+
project_item = PlanMyStuff::Pipeline::IssueLinker.find_project_item(issue.number)
|
|
45
39
|
|
|
46
40
|
if project_item.present?
|
|
47
41
|
if remaining.empty?
|
|
@@ -54,12 +48,12 @@ module PlanMyStuff
|
|
|
54
48
|
issue.update!(assignees: remaining)
|
|
55
49
|
end
|
|
56
50
|
|
|
57
|
-
flash[:success] = "Issue ##{
|
|
58
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
51
|
+
flash[:success] = "Issue ##{issue.number} released."
|
|
52
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
59
53
|
rescue ArgumentError, PlanMyStuff::Error => e
|
|
60
54
|
pms_handle_rescue(e)
|
|
61
55
|
flash[:error] = e.message
|
|
62
|
-
redirect_to(plan_my_stuff.issue_path(
|
|
56
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
63
57
|
end
|
|
64
58
|
|
|
65
59
|
private
|
|
@@ -72,7 +66,7 @@ module PlanMyStuff
|
|
|
72
66
|
return if support_user?
|
|
73
67
|
|
|
74
68
|
redirect_to_unauthorized(
|
|
75
|
-
plan_my_stuff.issue_path(params[:issue_id]
|
|
69
|
+
plan_my_stuff.issue_path(params[:issue_id]),
|
|
76
70
|
)
|
|
77
71
|
end
|
|
78
72
|
|
|
@@ -12,44 +12,44 @@ module PlanMyStuff
|
|
|
12
12
|
# POST /issues/:issue_id/viewers
|
|
13
13
|
def create
|
|
14
14
|
unless support_user?
|
|
15
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
15
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
viewer_ids = parse_viewer_ids(params[:viewer_ids])
|
|
20
20
|
if viewer_ids.blank?
|
|
21
21
|
flash[:error] = 'No valid viewer IDs provided.'
|
|
22
|
-
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]
|
|
22
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
|
|
23
23
|
return
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
26
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
27
27
|
issue.add_viewers!(user_ids: viewer_ids, user: pms_current_user)
|
|
28
28
|
|
|
29
29
|
flash[:success] = 'Viewers were successfully added.'
|
|
30
|
-
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]
|
|
30
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
|
|
31
31
|
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
32
32
|
pms_handle_rescue(e)
|
|
33
33
|
flash[:error] = e.message
|
|
34
|
-
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]
|
|
34
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
# DELETE /issues/:issue_id/viewers/:id
|
|
38
38
|
def destroy
|
|
39
39
|
unless support_user?
|
|
40
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
40
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
41
41
|
return
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
44
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
45
45
|
issue.remove_viewers!(user_ids: [params[:id].to_i], user: pms_current_user)
|
|
46
46
|
|
|
47
47
|
flash[:success] = 'Viewer was successfully removed.'
|
|
48
|
-
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]
|
|
48
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
|
|
49
49
|
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
50
50
|
pms_handle_rescue(e)
|
|
51
51
|
flash[:error] = e.message
|
|
52
|
-
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]
|
|
52
|
+
redirect_to(plan_my_stuff.edit_issue_path(params[:issue_id]))
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -12,37 +12,37 @@ module PlanMyStuff
|
|
|
12
12
|
# POST /issues/:issue_id/waiting
|
|
13
13
|
def create
|
|
14
14
|
unless support_user?
|
|
15
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
15
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
16
16
|
return
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
19
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
20
20
|
issue.enter_waiting_on_user!(user: pms_current_user)
|
|
21
21
|
|
|
22
22
|
flash[:success] = 'Issue marked as waiting on user reply.'
|
|
23
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
23
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
24
24
|
rescue PlanMyStuff::Error, ArgumentError => e
|
|
25
25
|
pms_handle_rescue(e)
|
|
26
26
|
flash[:error] = e.message
|
|
27
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
27
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
# DELETE /issues/:issue_id/waiting
|
|
31
31
|
def destroy
|
|
32
32
|
unless support_user?
|
|
33
|
-
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]
|
|
33
|
+
redirect_to_unauthorized(plan_my_stuff.issue_path(params[:issue_id]))
|
|
34
34
|
return
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
37
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
38
38
|
issue.clear_waiting_on_user!
|
|
39
39
|
|
|
40
40
|
flash[:success] = 'Waiting-on-user state cleared.'
|
|
41
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
41
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
42
42
|
rescue PlanMyStuff::Error, ArgumentError => e
|
|
43
43
|
pms_handle_rescue(e)
|
|
44
44
|
flash[:error] = e.message
|
|
45
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
45
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
end
|
|
@@ -34,7 +34,7 @@ module PlanMyStuff
|
|
|
34
34
|
)
|
|
35
35
|
|
|
36
36
|
flash[:success] = 'Issue was successfully created.'
|
|
37
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
37
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
38
38
|
rescue PlanMyStuff::ValidationError => e
|
|
39
39
|
pms_handle_rescue(e)
|
|
40
40
|
@issue = PlanMyStuff::Issue.new(title: issue_params[:title], body: issue_params[:body])
|
|
@@ -44,7 +44,7 @@ module PlanMyStuff
|
|
|
44
44
|
|
|
45
45
|
# GET /issues/:id
|
|
46
46
|
def show
|
|
47
|
-
@issue = PlanMyStuff::Issue.find(params[:id]
|
|
47
|
+
@issue = PlanMyStuff::Issue.find(params[:id])
|
|
48
48
|
@comments = filter_visible_comments(@issue.comments)
|
|
49
49
|
@current_user_id = pms_current_user.present? ? PlanMyStuff::UserResolver.user_id(pms_current_user) : nil
|
|
50
50
|
@current_user_login = PlanMyStuff.configuration.github_login_for[@current_user_id]
|
|
@@ -54,12 +54,12 @@ module PlanMyStuff
|
|
|
54
54
|
|
|
55
55
|
# GET /issues/:id/edit
|
|
56
56
|
def edit
|
|
57
|
-
@issue = PlanMyStuff::Issue.find(params[:id]
|
|
57
|
+
@issue = PlanMyStuff::Issue.find(params[:id])
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
# PATCH/PUT /issues/:id
|
|
61
61
|
def update
|
|
62
|
-
@issue = PlanMyStuff::Issue.find(params[:id]
|
|
62
|
+
@issue = PlanMyStuff::Issue.find(params[:id])
|
|
63
63
|
|
|
64
64
|
@issue.update!(
|
|
65
65
|
title: issue_params[:title],
|
|
@@ -68,7 +68,7 @@ module PlanMyStuff
|
|
|
68
68
|
)
|
|
69
69
|
|
|
70
70
|
flash[:success] = 'Issue was successfully updated.'
|
|
71
|
-
redirect_to(plan_my_stuff.issue_path(@issue
|
|
71
|
+
redirect_to(plan_my_stuff.issue_path(@issue))
|
|
72
72
|
rescue PlanMyStuff::StaleObjectError => e
|
|
73
73
|
pms_handle_rescue(e)
|
|
74
74
|
flash.now[:error] = 'Issue was modified by someone else. Please review the latest changes and try again.'
|
|
@@ -7,40 +7,40 @@ module PlanMyStuff
|
|
|
7
7
|
labels = parse_labels(params[:label_name])
|
|
8
8
|
if labels.blank?
|
|
9
9
|
flash[:error] = 'Label name is required.'
|
|
10
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
10
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
11
11
|
return
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
14
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
15
15
|
|
|
16
16
|
missing = labels.reject { |label| PlanMyStuff::Label.exists?(repo: issue.repo, name: label) }
|
|
17
17
|
if missing.any?
|
|
18
18
|
flash[:error] = "Label#{'s' if missing.size > 1} not found in repo: #{missing.join(', ')}"
|
|
19
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
19
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
20
20
|
return
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
PlanMyStuff::Label.add!(issue: issue, labels: labels)
|
|
24
24
|
|
|
25
25
|
flash[:success] = 'Label was successfully added.'
|
|
26
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
26
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
27
27
|
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
28
28
|
pms_handle_rescue(e)
|
|
29
29
|
flash[:error] = e.message
|
|
30
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
30
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
# DELETE /issues/:issue_id/labels/:id
|
|
34
34
|
def destroy
|
|
35
|
-
issue = PlanMyStuff::Issue.find(params[:issue_id]
|
|
35
|
+
issue = PlanMyStuff::Issue.find(params[:issue_id])
|
|
36
36
|
PlanMyStuff::Label.remove!(issue: issue, labels: [params[:id]])
|
|
37
37
|
|
|
38
38
|
flash[:success] = 'Label was successfully removed.'
|
|
39
|
-
redirect_to(plan_my_stuff.issue_path(issue
|
|
39
|
+
redirect_to(plan_my_stuff.issue_path(issue))
|
|
40
40
|
rescue PlanMyStuff::Error, Octokit::Error => e
|
|
41
41
|
pms_handle_rescue(e)
|
|
42
42
|
flash[:error] = e.message
|
|
43
|
-
redirect_to(plan_my_stuff.issue_path(params[:issue_id]
|
|
43
|
+
redirect_to(plan_my_stuff.issue_path(params[:issue_id]))
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<% @issues.each do |issue| %>
|
|
15
15
|
<tr>
|
|
16
16
|
<td><%= issue.number %></td>
|
|
17
|
-
<td><%= link_to(issue.title, plan_my_stuff.issue_path(issue
|
|
17
|
+
<td><%= link_to(issue.title, plan_my_stuff.issue_path(issue)) %></td>
|
|
18
18
|
<td><%= issue.state %></td>
|
|
19
19
|
<td><%= issue.labels.join(', ') %></td>
|
|
20
20
|
</tr>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<%=
|
|
45
45
|
button_to(
|
|
46
46
|
'Approve',
|
|
47
|
-
plan_my_stuff.issue_approval_path(issue
|
|
47
|
+
plan_my_stuff.issue_approval_path(issue, approval.user_id),
|
|
48
48
|
method: :patch,
|
|
49
49
|
params: { approval: { status: 'approved' } },
|
|
50
50
|
form: { style: 'display:inline' },
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
<%=
|
|
54
54
|
button_to(
|
|
55
55
|
'Reject',
|
|
56
|
-
plan_my_stuff.issue_approval_path(issue
|
|
56
|
+
plan_my_stuff.issue_approval_path(issue, approval.user_id),
|
|
57
57
|
method: :patch,
|
|
58
58
|
params: { approval: { status: 'rejected' } },
|
|
59
59
|
form: { style: 'display:inline' },
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
<%=
|
|
66
66
|
button_to(
|
|
67
67
|
'Revoke',
|
|
68
|
-
plan_my_stuff.issue_approval_path(issue
|
|
68
|
+
plan_my_stuff.issue_approval_path(issue, approval.user_id),
|
|
69
69
|
method: :patch,
|
|
70
70
|
params: { approval: { status: 'pending' } },
|
|
71
71
|
form: { style: 'display:inline' },
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
<%=
|
|
78
78
|
button_to(
|
|
79
79
|
'Remove',
|
|
80
|
-
plan_my_stuff.issue_approval_path(issue
|
|
80
|
+
plan_my_stuff.issue_approval_path(issue, approval.user_id),
|
|
81
81
|
method: :delete,
|
|
82
82
|
form: { style: 'display:inline' },
|
|
83
83
|
)
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
<%=
|
|
95
95
|
form_with(
|
|
96
96
|
scope: :approval,
|
|
97
|
-
url: plan_my_stuff.issue_approvals_path(issue
|
|
97
|
+
url: plan_my_stuff.issue_approvals_path(issue),
|
|
98
98
|
method: :post,
|
|
99
99
|
local: true,
|
|
100
100
|
) do |form|
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
<%=
|
|
69
69
|
link_to(
|
|
70
70
|
"#{target.repo.full_name}##{target.number} - #{target.title}",
|
|
71
|
-
plan_my_stuff.issue_path(target
|
|
71
|
+
plan_my_stuff.issue_path(target),
|
|
72
72
|
)
|
|
73
73
|
%>
|
|
74
74
|
<% if section[:removable] %>
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
<%=
|
|
79
79
|
button_to(
|
|
80
80
|
'Remove',
|
|
81
|
-
plan_my_stuff.issue_link_path(issue
|
|
81
|
+
plan_my_stuff.issue_link_path(issue, link_id),
|
|
82
82
|
method: :delete,
|
|
83
83
|
form: { style: 'display:inline' },
|
|
84
84
|
)
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
<%=
|
|
96
96
|
form_with(
|
|
97
97
|
scope: :link,
|
|
98
|
-
url: plan_my_stuff.issue_links_path(issue
|
|
98
|
+
url: plan_my_stuff.issue_links_path(issue),
|
|
99
99
|
method: :post,
|
|
100
100
|
local: true,
|
|
101
101
|
) do |form|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<%=
|
|
11
11
|
button_to(
|
|
12
12
|
'Remove',
|
|
13
|
-
plan_my_stuff.issue_viewer_path(issue
|
|
13
|
+
plan_my_stuff.issue_viewer_path(issue, viewer_id),
|
|
14
14
|
method: :delete
|
|
15
15
|
)
|
|
16
16
|
%>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<p>No viewers added.</p>
|
|
22
22
|
<% end %>
|
|
23
23
|
|
|
24
|
-
<%= form_with(url: plan_my_stuff.issue_viewers_path(issue
|
|
24
|
+
<%= form_with(url: plan_my_stuff.issue_viewers_path(issue), method: :post) do |form| %>
|
|
25
25
|
<div>
|
|
26
26
|
<%= form.label(:viewer_ids, 'Add viewer IDs (comma-separated)') %>
|
|
27
27
|
<%= form.text_field(:viewer_ids) %>
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
<h1><%= @issue.title %> <small>#<%= @issue.number %></small></h1>
|
|
2
2
|
|
|
3
3
|
<p>
|
|
4
|
-
<%= link_to('Edit', plan_my_stuff.edit_issue_path(@issue
|
|
4
|
+
<%= link_to('Edit', plan_my_stuff.edit_issue_path(@issue)) %>
|
|
5
5
|
<% if @support_user && @issue.html_url.present? %>
|
|
6
6
|
<%= link_to('View on GitHub', @issue.html_url, target: '_blank', rel: 'noopener') %>
|
|
7
7
|
<% end %>
|
|
8
8
|
<% if @issue.state == 'open' %>
|
|
9
|
-
<%= button_to('Close Issue', plan_my_stuff.issue_closure_path(@issue
|
|
9
|
+
<%= button_to('Close Issue', plan_my_stuff.issue_closure_path(@issue), method: :post) %>
|
|
10
10
|
<% else %>
|
|
11
|
-
<%= button_to('Reopen Issue', plan_my_stuff.issue_closure_path(@issue
|
|
11
|
+
<%= button_to('Reopen Issue', plan_my_stuff.issue_closure_path(@issue), method: :delete) %>
|
|
12
12
|
<% end %>
|
|
13
13
|
<% if @support_user && @issue.state == 'open' %>
|
|
14
14
|
<% if @issue.metadata.waiting_on_user_at.present? %>
|
|
15
|
-
<%= button_to('Mark replied', plan_my_stuff.issue_waiting_path(@issue
|
|
15
|
+
<%= button_to('Mark replied', plan_my_stuff.issue_waiting_path(@issue), method: :delete) %>
|
|
16
16
|
<% else %>
|
|
17
|
-
<%= button_to('Mark waiting', plan_my_stuff.issue_waiting_path(@issue
|
|
17
|
+
<%= button_to('Mark waiting', plan_my_stuff.issue_waiting_path(@issue), method: :post) %>
|
|
18
18
|
<% end %>
|
|
19
19
|
<% end %>
|
|
20
20
|
<% if @support_user && @pipeline_enabled && @pipeline_item.nil? && @issue.assignees.blank? %>
|
|
21
|
-
<%= button_to('Take', plan_my_stuff.issue_take_path(@issue
|
|
21
|
+
<%= button_to('Take', plan_my_stuff.issue_take_path(@issue), method: :post) %>
|
|
22
22
|
<% end %>
|
|
23
23
|
<% if @support_user && @pipeline_enabled && @current_user_login.present? && @issue.assignees.include?(@current_user_login) %>
|
|
24
|
-
<%= button_to('Unassign', plan_my_stuff.issue_take_path(@issue
|
|
24
|
+
<%= button_to('Unassign', plan_my_stuff.issue_take_path(@issue), method: :delete) %>
|
|
25
25
|
<% end %>
|
|
26
26
|
<% if @support_user %>
|
|
27
27
|
<%= link_to('Start Testing Project', plan_my_stuff.new_testing_project_path(subject_url: @issue.html_url)) %>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<%= PlanMyStuff::Markdown.render(comment.body || '').html_safe %>
|
|
77
77
|
<% if comment.pms_comment? && (@support_user || comment.metadata.created_by == @current_user_id) %>
|
|
78
78
|
<p>
|
|
79
|
-
<%= link_to('Edit', plan_my_stuff.edit_issue_comment_path(@issue
|
|
79
|
+
<%= link_to('Edit', plan_my_stuff.edit_issue_comment_path(@issue, comment.id)) %>
|
|
80
80
|
</p>
|
|
81
81
|
<% end %>
|
|
82
82
|
<% if @support_user && comment.html_url.present? %>
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
<% items.each do |item| %>
|
|
25
25
|
<div style="border: 1px solid black; margin: 1em">
|
|
26
26
|
<% unless item.draft? %>
|
|
27
|
-
<strong
|
|
27
|
+
<strong>
|
|
28
|
+
<%= link_to(item.title, plan_my_stuff.issue_path(item.issue.to_param)) %>
|
|
29
|
+
</strong>
|
|
28
30
|
<% else %>
|
|
29
31
|
<strong><%= item.title %></strong>
|
|
30
32
|
<% end %>
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<% if item.draft? %>
|
|
4
4
|
<strong><%= item.title %></strong>
|
|
5
5
|
<% else %>
|
|
6
|
-
<strong><%= link_to(item.title, plan_my_stuff.issue_path(item.
|
|
6
|
+
<strong><%= link_to(item.title, plan_my_stuff.issue_path(item.issue.to_param)) %></strong>
|
|
7
7
|
<small>#<%= item.number %></small>
|
|
8
8
|
<% end %>
|
|
9
9
|
|
|
@@ -24,6 +24,12 @@ PlanMyStuff.configure do |config|
|
|
|
24
24
|
# config.repos[:underwriter] = 'YourOrganization/Underwriter'
|
|
25
25
|
# config.default_repo = :element
|
|
26
26
|
|
|
27
|
+
# Human-readable prefix used as the `Issue#to_param` so URLs read
|
|
28
|
+
# `/issues/Element-1234` instead of `/issues/1234`. Missing keys fall back
|
|
29
|
+
# to `key.to_s.titleize`; only repos whose key doesn't titleize cleanly
|
|
30
|
+
# need an entry.
|
|
31
|
+
# config.repo_nicknames = { safety: 'Compliance' }
|
|
32
|
+
|
|
27
33
|
# --------------------------------------------------------------------------
|
|
28
34
|
# Projects
|
|
29
35
|
# --------------------------------------------------------------------------
|
|
@@ -348,6 +348,14 @@ module PlanMyStuff
|
|
|
348
348
|
#
|
|
349
349
|
attr_accessor :repos
|
|
350
350
|
|
|
351
|
+
# Human-readable repo names used as the +to_param+ prefix on +PlanMyStuff::Issue+ instances. Symbol-keyed
|
|
352
|
+
# against +repos+ -- missing keys fall back to +key.to_s.titleize+, so only entries that diverge from a simple
|
|
353
|
+
# +titleize+ of the key (e.g. +:safety+ -> +"Compliance"+) need to be listed.
|
|
354
|
+
#
|
|
355
|
+
# @return [Hash{Symbol => String}]
|
|
356
|
+
#
|
|
357
|
+
attr_accessor :repo_nicknames
|
|
358
|
+
|
|
351
359
|
# Bare repo name (under +config.organization+) that stores uploaded attachment binaries. Defaults to
|
|
352
360
|
# +'pms-attachments'+. The repo must exist; the uploader does not create it. Attachments commit onto
|
|
353
361
|
# +config.main_branch+ and live under +<repo_key_or_name>/issue-<number>/<uuid>.<ext>+.
|
|
@@ -368,6 +376,7 @@ module PlanMyStuff
|
|
|
368
376
|
# @return [Configuration]
|
|
369
377
|
def initialize
|
|
370
378
|
@repos = {}
|
|
379
|
+
@repo_nicknames = {}
|
|
371
380
|
@attachment_repo = 'pms-attachments'
|
|
372
381
|
@user_class = 'User'
|
|
373
382
|
@display_name_method = :to_s
|
|
@@ -435,12 +444,14 @@ module PlanMyStuff
|
|
|
435
444
|
missing << 'access_token' if access_token.nil? || access_token.to_s.strip.empty?
|
|
436
445
|
missing << 'organization' if organization.nil? || organization.to_s.strip.empty?
|
|
437
446
|
|
|
438
|
-
|
|
447
|
+
if missing.present?
|
|
448
|
+
raise(
|
|
449
|
+
PlanMyStuff::ConfigurationError,
|
|
450
|
+
"Missing required PlanMyStuff configuration: #{missing.join(', ')}",
|
|
451
|
+
)
|
|
452
|
+
end
|
|
439
453
|
|
|
440
|
-
|
|
441
|
-
PlanMyStuff::ConfigurationError,
|
|
442
|
-
"Missing required PlanMyStuff configuration: #{missing.join(', ')}",
|
|
443
|
-
)
|
|
454
|
+
validate_repo_nicknames!
|
|
444
455
|
end
|
|
445
456
|
|
|
446
457
|
# Returns the merged custom fields schema for the given context. Context-specific fields deep-merge on top of
|
|
@@ -477,6 +488,51 @@ module PlanMyStuff
|
|
|
477
488
|
path = controllers[key] || DEFAULT_CONTROLLERS.fetch(key)
|
|
478
489
|
path.start_with?('/') ? path : "/#{path}"
|
|
479
490
|
end
|
|
491
|
+
|
|
492
|
+
# Human-readable nickname for a repo key, used as the +to_param+ prefix on +PlanMyStuff::Issue+ instances. Falls
|
|
493
|
+
# back to +key.to_s.titleize+ when no explicit entry exists in +repo_nicknames+.
|
|
494
|
+
#
|
|
495
|
+
# @param key [Symbol, String]
|
|
496
|
+
#
|
|
497
|
+
# @return [String]
|
|
498
|
+
#
|
|
499
|
+
def repo_nickname_for(key)
|
|
500
|
+
repo_nicknames[key&.to_sym] || key.to_s.titleize
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
private
|
|
504
|
+
|
|
505
|
+
# Resolved nicknames feed directly into +Issue#to_param+ and route +:id+ tokens, so collisions or chars outside
|
|
506
|
+
# +[A-Za-z0-9_]+ (notably +-+, the nickname/number separator) break URL round-tripping.
|
|
507
|
+
#
|
|
508
|
+
# @raise [ConfigurationError] when any resolved nickname collides with another or contains non-token chars
|
|
509
|
+
#
|
|
510
|
+
# @return [void]
|
|
511
|
+
#
|
|
512
|
+
def validate_repo_nicknames!
|
|
513
|
+
resolved = repos.keys.index_with { |key| repo_nickname_for(key).to_s }
|
|
514
|
+
|
|
515
|
+
invalid = resolved.reject { |_key, nickname| nickname.match?(/\A[A-Za-z0-9_]+\z/) }
|
|
516
|
+
if invalid.present?
|
|
517
|
+
pairs = invalid.map { |key, nickname| "#{key.inspect} => #{nickname.inspect}" }
|
|
518
|
+
raise(
|
|
519
|
+
PlanMyStuff::ConfigurationError,
|
|
520
|
+
"Invalid repo nickname(s) (must match /\\A[A-Za-z0-9_]+\\z/): #{pairs.join(', ')}",
|
|
521
|
+
)
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
dupes = resolved.group_by { |_key, nickname| nickname }.select { |_n, entries| entries.size > 1 }
|
|
525
|
+
return if dupes.blank?
|
|
526
|
+
|
|
527
|
+
details = dupes.map do |nickname, entries|
|
|
528
|
+
keys = entries.map { |entry| entry.first.inspect }.join(', ')
|
|
529
|
+
"#{nickname.inspect} (#{keys})"
|
|
530
|
+
end
|
|
531
|
+
raise(
|
|
532
|
+
PlanMyStuff::ConfigurationError,
|
|
533
|
+
"Duplicate repo nickname(s): #{details.join('; ')}",
|
|
534
|
+
)
|
|
535
|
+
end
|
|
480
536
|
end
|
|
481
537
|
|
|
482
538
|
class ConfigurationError < StandardError
|
data/lib/plan_my_stuff/issue.rb
CHANGED
|
@@ -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
|
|
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(
|
|
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 (
|
|
447
|
-
#
|
|
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
|
-
|
|
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
|
-
#
|
|
530
|
-
#
|
|
531
|
-
#
|
|
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?
|
|
610
|
+
return if prefix.blank?
|
|
535
611
|
|
|
536
|
-
|
|
537
|
-
return
|
|
612
|
+
to_par = to_param
|
|
613
|
+
return if to_par.blank?
|
|
538
614
|
|
|
539
|
-
"#{
|
|
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,
|
data/lib/plan_my_stuff/repo.rb
CHANGED
|
@@ -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
|
|