plan_my_stuff 0.1.0 → 1.0.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 +595 -0
- data/CONFIGURATION.md +487 -0
- data/README.md +612 -88
- data/app/controllers/plan_my_stuff/application_controller.rb +27 -5
- data/app/controllers/plan_my_stuff/comments_controller.rb +50 -19
- data/app/controllers/plan_my_stuff/issues/approvals_controller.rb +127 -0
- data/app/controllers/plan_my_stuff/issues/closures_controller.rb +53 -0
- data/app/controllers/plan_my_stuff/issues/links_controller.rb +129 -0
- data/app/controllers/plan_my_stuff/issues/takes_controller.rb +161 -0
- data/app/controllers/plan_my_stuff/issues/testings_controller.rb +82 -0
- data/app/controllers/plan_my_stuff/issues/viewers_controller.rb +62 -0
- data/app/controllers/plan_my_stuff/issues/waitings_controller.rb +55 -0
- data/app/controllers/plan_my_stuff/issues_controller.rb +53 -70
- data/app/controllers/plan_my_stuff/labels_controller.rb +32 -10
- data/app/controllers/plan_my_stuff/project_items/assignments_controller.rb +88 -0
- data/app/controllers/plan_my_stuff/project_items/statuses_controller.rb +44 -0
- data/app/controllers/plan_my_stuff/project_items_controller.rb +32 -69
- data/app/controllers/plan_my_stuff/projects_controller.rb +81 -3
- data/app/controllers/plan_my_stuff/testing_project_items/results_controller.rb +67 -0
- data/app/controllers/plan_my_stuff/testing_project_items_controller.rb +49 -0
- data/app/controllers/plan_my_stuff/testing_projects_controller.rb +121 -0
- data/app/controllers/plan_my_stuff/webhooks/aws_controller.rb +202 -0
- data/app/controllers/plan_my_stuff/webhooks/github_controller.rb +371 -0
- data/app/jobs/plan_my_stuff/application_job.rb +8 -0
- data/app/jobs/plan_my_stuff/reminders_sweep_job.rb +75 -0
- data/app/views/plan_my_stuff/comments/edit.html.erb +1 -3
- data/app/views/plan_my_stuff/comments/partials/_form.html.erb +8 -0
- data/app/views/plan_my_stuff/issues/edit.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/index.html.erb +5 -5
- data/app/views/plan_my_stuff/issues/new.html.erb +2 -4
- data/app/views/plan_my_stuff/issues/partials/_approvals.html.erb +108 -0
- data/app/views/plan_my_stuff/issues/partials/_form.html.erb +11 -6
- data/app/views/plan_my_stuff/issues/partials/_labels.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/partials/_links.html.erb +113 -0
- data/app/views/plan_my_stuff/issues/partials/_viewers.html.erb +4 -3
- data/app/views/plan_my_stuff/issues/show.html.erb +67 -6
- data/app/views/plan_my_stuff/partials/_flash.html.erb +3 -0
- data/app/views/plan_my_stuff/projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/index.html.erb +18 -2
- data/app/views/plan_my_stuff/projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/projects/partials/_form.html.erb +30 -0
- data/app/views/plan_my_stuff/projects/show.html.erb +30 -11
- data/app/views/plan_my_stuff/testing_project_items/new.html.erb +10 -0
- data/app/views/plan_my_stuff/testing_project_items/results/new.html.erb +20 -0
- data/app/views/plan_my_stuff/testing_projects/edit.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/new.html.erb +5 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_form.html.erb +40 -0
- data/app/views/plan_my_stuff/testing_projects/partials/_item.html.erb +52 -0
- data/app/views/plan_my_stuff/testing_projects/partials/items/_form.html.erb +36 -0
- data/app/views/plan_my_stuff/testing_projects/show.html.erb +65 -0
- data/config/routes.rb +43 -15
- data/lib/generators/plan_my_stuff/install/templates/initializer.rb +302 -20
- data/lib/plan_my_stuff/application_record.rb +158 -1
- data/lib/plan_my_stuff/approval.rb +88 -0
- data/lib/plan_my_stuff/archive/sweep.rb +85 -0
- data/lib/plan_my_stuff/archive.rb +12 -0
- data/lib/plan_my_stuff/attachment.rb +83 -0
- data/lib/plan_my_stuff/attachment_uploader.rb +245 -0
- data/lib/plan_my_stuff/aws_sns_simulator.rb +116 -0
- data/lib/plan_my_stuff/base_metadata.rb +25 -28
- data/lib/plan_my_stuff/base_project.rb +502 -0
- data/lib/plan_my_stuff/base_project_extractions/graphql_hydration.rb +186 -0
- data/lib/plan_my_stuff/base_project_item.rb +588 -0
- data/lib/plan_my_stuff/base_project_metadata.rb +16 -0
- data/lib/plan_my_stuff/cache.rb +197 -0
- data/lib/plan_my_stuff/client.rb +139 -64
- data/lib/plan_my_stuff/comment.rb +225 -100
- data/lib/plan_my_stuff/comment_metadata.rb +68 -5
- data/lib/plan_my_stuff/configuration.rb +459 -28
- data/lib/plan_my_stuff/custom_fields.rb +96 -12
- data/lib/plan_my_stuff/engine.rb +14 -2
- data/lib/plan_my_stuff/errors.rb +65 -5
- data/lib/plan_my_stuff/graphql/queries.rb +454 -0
- data/lib/plan_my_stuff/issue.rb +1097 -166
- data/lib/plan_my_stuff/issue_extractions/approvals.rb +370 -0
- data/lib/plan_my_stuff/issue_extractions/links.rb +525 -0
- data/lib/plan_my_stuff/issue_extractions/viewers.rb +75 -0
- data/lib/plan_my_stuff/issue_extractions/waiting.rb +171 -0
- data/lib/plan_my_stuff/issue_field.rb +126 -0
- data/lib/plan_my_stuff/issue_field_translation.rb +67 -0
- data/lib/plan_my_stuff/issue_field_value_set.rb +68 -0
- data/lib/plan_my_stuff/issue_metadata.rb +132 -21
- data/lib/plan_my_stuff/label.rb +100 -13
- data/lib/plan_my_stuff/link.rb +144 -0
- data/lib/plan_my_stuff/markdown.rb +13 -7
- data/lib/plan_my_stuff/metadata_parser.rb +51 -12
- data/lib/plan_my_stuff/notifications.rb +148 -0
- data/lib/plan_my_stuff/pipeline/completed_sweep.rb +46 -0
- data/lib/plan_my_stuff/pipeline/issue_linker.rb +62 -0
- data/lib/plan_my_stuff/pipeline/status.rb +40 -0
- data/lib/plan_my_stuff/pipeline/testing.rb +23 -0
- data/lib/plan_my_stuff/pipeline.rb +310 -0
- data/lib/plan_my_stuff/project.rb +63 -465
- data/lib/plan_my_stuff/project_item.rb +3 -409
- data/lib/plan_my_stuff/project_item_metadata.rb +55 -0
- data/lib/plan_my_stuff/project_metadata.rb +47 -0
- data/lib/plan_my_stuff/reminders/closer.rb +70 -0
- data/lib/plan_my_stuff/reminders/fire.rb +129 -0
- data/lib/plan_my_stuff/reminders/sweep.rb +54 -0
- data/lib/plan_my_stuff/reminders.rb +12 -0
- data/lib/plan_my_stuff/repo.rb +145 -0
- data/lib/plan_my_stuff/test_helpers.rb +265 -25
- data/lib/plan_my_stuff/testing_project.rb +292 -0
- data/lib/plan_my_stuff/testing_project_item.rb +218 -0
- data/lib/plan_my_stuff/testing_project_metadata.rb +94 -0
- data/lib/plan_my_stuff/user_resolver.rb +24 -3
- data/lib/plan_my_stuff/verifier.rb +10 -0
- data/lib/plan_my_stuff/version.rb +2 -2
- data/lib/plan_my_stuff/webhook_replayer.rb +292 -0
- data/lib/plan_my_stuff.rb +55 -20
- data/lib/tasks/plan_my_stuff.rake +331 -0
- metadata +99 -4
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlanMyStuff
|
|
4
|
+
module Reminders
|
|
5
|
+
# Emits +issue_reminder_due.plan_my_stuff+ for a single waiting issue
|
|
6
|
+
# and advances its +next_reminder_at+ to the next milestone in the
|
|
7
|
+
# effective +reminder_days+ schedule (or +nil+ when the last milestone
|
|
8
|
+
# has passed).
|
|
9
|
+
class Fire
|
|
10
|
+
# Returns +true+ when the issue has a +next_reminder_at+ in the
|
|
11
|
+
# past relative to +now+.
|
|
12
|
+
#
|
|
13
|
+
# @param issue [PlanMyStuff::Issue]
|
|
14
|
+
# @param now [Time]
|
|
15
|
+
#
|
|
16
|
+
# @return [Boolean]
|
|
17
|
+
#
|
|
18
|
+
def self.ready?(issue, now: Time.now.utc)
|
|
19
|
+
due = issue.metadata.next_reminder_at
|
|
20
|
+
due.present? && due <= now
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# @param issue [PlanMyStuff::Issue] candidate issue from the sweep
|
|
24
|
+
# @param now [Time] clock reference (defaults to +Time.now.utc+)
|
|
25
|
+
#
|
|
26
|
+
def initialize(issue, now: Time.now.utc)
|
|
27
|
+
@issue = issue
|
|
28
|
+
@now = now.utc
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Emits the reminder event and advances the schedule.
|
|
32
|
+
#
|
|
33
|
+
# @return [void]
|
|
34
|
+
#
|
|
35
|
+
def call
|
|
36
|
+
payload = build_payload
|
|
37
|
+
PlanMyStuff::Notifications.instrument('issue_reminder_due', @issue, **payload)
|
|
38
|
+
|
|
39
|
+
@issue.update!(
|
|
40
|
+
metadata: { next_reminder_at: next_reminder_at_value },
|
|
41
|
+
skip_notification: true,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# @return [Hash]
|
|
48
|
+
def build_payload
|
|
49
|
+
payload = {
|
|
50
|
+
waiting_kind: waiting_kind,
|
|
51
|
+
days_waiting: days_waiting,
|
|
52
|
+
reminder_day: reminder_day,
|
|
53
|
+
last_activity_at: last_activity_at,
|
|
54
|
+
}
|
|
55
|
+
payload[:pending_approvers] = pending_approvers if waiting_kind == :approval
|
|
56
|
+
payload
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @return [Symbol] +:user+ or +:approval+
|
|
60
|
+
def waiting_kind
|
|
61
|
+
@issue.metadata.waiting_on_user_at.present? ? :user : :approval
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @return [Time] the timestamp waiting started for the active kind
|
|
65
|
+
def starting_clock
|
|
66
|
+
@issue.metadata.waiting_on_user_at || @issue.metadata.waiting_on_approval_at
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [Integer]
|
|
70
|
+
def days_waiting
|
|
71
|
+
((@now - starting_clock) / 1.day).to_i
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Most recent milestone passed, used as the +reminder_day+ label in
|
|
75
|
+
# the event payload.
|
|
76
|
+
#
|
|
77
|
+
# @return [Integer, nil]
|
|
78
|
+
#
|
|
79
|
+
def reminder_day
|
|
80
|
+
effective_reminder_days.select { |d| d <= days_waiting }.max
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Informational: last comment timestamp (falling back to the
|
|
84
|
+
# issue's GitHub +updated_at+ when there are no comments). Uses
|
|
85
|
+
# +updated_at+ on Comment since that's the only timestamp PMS
|
|
86
|
+
# exposes on comment records.
|
|
87
|
+
#
|
|
88
|
+
# @return [Time, nil]
|
|
89
|
+
#
|
|
90
|
+
def last_activity_at
|
|
91
|
+
last_comment = @issue.comments.max_by { |c| c.updated_at || Time.at(0).utc }
|
|
92
|
+
last_comment&.updated_at || @issue.updated_at
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Resolves pending-approval user IDs via +UserResolver+. IDs that
|
|
96
|
+
# fail to resolve (e.g. deleted users) are dropped.
|
|
97
|
+
#
|
|
98
|
+
# @return [Array<Object>]
|
|
99
|
+
#
|
|
100
|
+
def pending_approvers
|
|
101
|
+
@issue.pending_approvals.filter_map do |approval|
|
|
102
|
+
PlanMyStuff::UserResolver.resolve(approval.user_id)
|
|
103
|
+
rescue ActiveRecord::RecordNotFound
|
|
104
|
+
next
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @return [Array<Integer>]
|
|
109
|
+
def effective_reminder_days
|
|
110
|
+
(@issue.metadata.reminder_days.presence || PlanMyStuff.configuration.reminder_days).sort
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Time of the next milestone past +now+, or +nil+ when the last
|
|
114
|
+
# milestone has passed. Returned as a native +Time+ (not ISO
|
|
115
|
+
# string) so +IssueMetadata#to_h+'s +PlanMyStuff.format_time+ serializes it
|
|
116
|
+
# cleanly.
|
|
117
|
+
#
|
|
118
|
+
# @return [Time, nil]
|
|
119
|
+
#
|
|
120
|
+
def next_reminder_at_value
|
|
121
|
+
start = starting_clock
|
|
122
|
+
remaining = effective_reminder_days.select { |d| start + d.days > @now }
|
|
123
|
+
return if remaining.empty?
|
|
124
|
+
|
|
125
|
+
(start + remaining.first.days).utc
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlanMyStuff
|
|
4
|
+
module Reminders
|
|
5
|
+
# Walks a single repo's waiting issues, dispatching each to +Closer+
|
|
6
|
+
# when past the inactivity ceiling or +Fire+ when a reminder is due.
|
|
7
|
+
# Called from +RemindersSweepJob+; no ActiveJob dependency here so
|
|
8
|
+
# the logic stays unit-testable and callable from a plain rake task.
|
|
9
|
+
class Sweep
|
|
10
|
+
# @param repo [Symbol, String] repo key or full name
|
|
11
|
+
# @param now [Time] clock reference
|
|
12
|
+
#
|
|
13
|
+
def initialize(repo:, now: Time.now.utc)
|
|
14
|
+
@repo = repo
|
|
15
|
+
@now = now.utc
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Runs the sweep. No-op when +config.reminders_enabled+ is false.
|
|
19
|
+
#
|
|
20
|
+
# @return [void]
|
|
21
|
+
#
|
|
22
|
+
def call
|
|
23
|
+
return unless PlanMyStuff.configuration.reminders_enabled
|
|
24
|
+
|
|
25
|
+
candidates.each do |issue|
|
|
26
|
+
if PlanMyStuff::Reminders::Closer.should_close?(issue, now: @now)
|
|
27
|
+
PlanMyStuff::Reminders::Closer.new(issue, now: @now).call
|
|
28
|
+
elsif PlanMyStuff::Reminders::Fire.ready?(issue, now: @now)
|
|
29
|
+
PlanMyStuff::Reminders::Fire.new(issue, now: @now).call
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# Issues carrying either waiting label, deduplicated by number.
|
|
37
|
+
# GitHub's labels filter is AND across multiple labels, so we
|
|
38
|
+
# query each label separately and merge.
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<PlanMyStuff::Issue>]
|
|
41
|
+
#
|
|
42
|
+
def candidates
|
|
43
|
+
user_label = PlanMyStuff.configuration.waiting_on_user_label
|
|
44
|
+
approval_label = PlanMyStuff.configuration.waiting_on_approval_label
|
|
45
|
+
|
|
46
|
+
by_label = [user_label, approval_label].flat_map do |label|
|
|
47
|
+
PlanMyStuff::Issue.list(repo: @repo, labels: [label], state: :open)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
by_label.uniq(&:number)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlanMyStuff
|
|
4
|
+
# Follow-up reminder engine. The +Sweep+ class walks the waiting issues
|
|
5
|
+
# in a repo and dispatches to +Fire+ for reminders that are due or
|
|
6
|
+
# +Closer+ for issues that have exceeded the inactivity threshold.
|
|
7
|
+
#
|
|
8
|
+
# Entry point for the sweep lives in +RemindersSweepJob+; this module
|
|
9
|
+
# holds the POROs so they can be unit-tested without ActiveJob.
|
|
10
|
+
module Reminders
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PlanMyStuff
|
|
4
|
+
class Repo
|
|
5
|
+
# @return [Symbol, nil] configured key (e.g. :my_repo)
|
|
6
|
+
attr_reader :key
|
|
7
|
+
|
|
8
|
+
# @return [String] repo name (e.g. "MyRepository")
|
|
9
|
+
attr_reader :name
|
|
10
|
+
|
|
11
|
+
# @return [String] organization name (e.g. "YourOrgName")
|
|
12
|
+
attr_reader :organization
|
|
13
|
+
|
|
14
|
+
delegate :split, to: :to_s
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
# Builds a Repo instance from a Symbol key, full name String, or nil (default).
|
|
18
|
+
#
|
|
19
|
+
# @raise [PlanMyStuff::ConfigurationError] if repo is not provided and cannot be resolved from config
|
|
20
|
+
# @raise [ArgumentError] if repo cannot be resolved
|
|
21
|
+
# @raise [ArgumentError] if repo is invalid format
|
|
22
|
+
#
|
|
23
|
+
# @param repo [Symbol, String, PlanMyStuff::Repo, nil]
|
|
24
|
+
#
|
|
25
|
+
# @return [PlanMyStuff::Repo]
|
|
26
|
+
#
|
|
27
|
+
def resolve!(repo = nil)
|
|
28
|
+
return repo if repo.is_a?(PlanMyStuff::Repo)
|
|
29
|
+
|
|
30
|
+
repo ||= PlanMyStuff.configuration.default_repo
|
|
31
|
+
|
|
32
|
+
if repo.nil?
|
|
33
|
+
raise(
|
|
34
|
+
PlanMyStuff::ConfigurationError,
|
|
35
|
+
'No repo provided and config.default_repo is not set. ' \
|
|
36
|
+
'Either pass repo: explicitly or set config.default_repo in your initializer.',
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
case repo
|
|
41
|
+
when Symbol
|
|
42
|
+
full_name = PlanMyStuff.configuration.repos[repo]
|
|
43
|
+
raise(ArgumentError, "Unknown repo key: #{repo.inspect}") if full_name.nil?
|
|
44
|
+
|
|
45
|
+
from_full_name!(full_name, key: repo)
|
|
46
|
+
when String
|
|
47
|
+
if PlanMyStuff.configuration.repos.has_key?(repo.to_sym)
|
|
48
|
+
resolve!(repo.to_sym)
|
|
49
|
+
else
|
|
50
|
+
key = PlanMyStuff.configuration.repos.key(repo)
|
|
51
|
+
from_full_name!(repo, key: key)
|
|
52
|
+
end
|
|
53
|
+
else
|
|
54
|
+
raise(ArgumentError, "Cannot resolve repo: #{repo.inspect}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
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
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
# @raise [ArgumentError] if full_name is not in "Org/Repo" format
|
|
78
|
+
#
|
|
79
|
+
# @param full_name [String] e.g. "YourOrgName/MyRepository"
|
|
80
|
+
# @param key [Symbol, nil]
|
|
81
|
+
#
|
|
82
|
+
# @return [PlanMyStuff::Repo]
|
|
83
|
+
#
|
|
84
|
+
def from_full_name!(full_name, key: nil)
|
|
85
|
+
org, name = full_name.split('/', 2)
|
|
86
|
+
|
|
87
|
+
raise(ArgumentError, "Invalid repo full_name: #{full_name.inspect}") if name.nil?
|
|
88
|
+
|
|
89
|
+
new(name: name, organization: org, key: key)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @param name [String]
|
|
94
|
+
# @param organization [String]
|
|
95
|
+
# @param key [Symbol, nil]
|
|
96
|
+
#
|
|
97
|
+
def initialize(name:, organization:, key: nil)
|
|
98
|
+
@key = key
|
|
99
|
+
@name = name
|
|
100
|
+
@organization = organization
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# @return [String] full repo path (e.g. "YourOrgName/MyRepository")
|
|
104
|
+
def full_name
|
|
105
|
+
"#{organization}/#{name}"
|
|
106
|
+
end
|
|
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
|
+
|
|
119
|
+
# @see #full_name
|
|
120
|
+
alias to_s full_name
|
|
121
|
+
|
|
122
|
+
# Enables implicit string coercion so Repo instances behave as strings when passed to Octokit or compared with
|
|
123
|
+
# String#==.
|
|
124
|
+
#
|
|
125
|
+
# @see #full_name
|
|
126
|
+
alias to_str full_name
|
|
127
|
+
|
|
128
|
+
# Compares by full_name. Accepts another Repo or a String.
|
|
129
|
+
#
|
|
130
|
+
# @param other [PlanMyStuff::Repo, String, Object]
|
|
131
|
+
#
|
|
132
|
+
# @return [Boolean]
|
|
133
|
+
#
|
|
134
|
+
def ==(other)
|
|
135
|
+
case other
|
|
136
|
+
when PlanMyStuff::Repo
|
|
137
|
+
full_name == other.full_name
|
|
138
|
+
when String
|
|
139
|
+
full_name == other
|
|
140
|
+
else
|
|
141
|
+
super
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|