issue-db 1.0.0 → 1.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 +4 -4
- data/README.md +34 -6
- data/issue-db.gemspec +1 -1
- data/lib/issue_db/authentication.rb +36 -28
- data/lib/issue_db/cache.rb +28 -20
- data/lib/issue_db/database.rb +194 -170
- data/lib/issue_db/models/record.rb +25 -23
- data/lib/issue_db/models/repository.rb +15 -13
- data/lib/issue_db/utils/generate.rb +38 -36
- data/lib/issue_db/utils/github.rb +285 -268
- data/lib/issue_db/utils/init.rb +19 -16
- data/lib/issue_db/utils/parse.rb +33 -31
- data/lib/issue_db.rb +59 -47
- data/lib/version.rb +4 -2
- 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: 740a1a64de7f9ab45fd7932682999972139392b4c8d8f48105cde4f49e9474b1
|
4
|
+
data.tar.gz: 6219653cb7f2303482e039f906ae788e4d9f9a89131df12e750ef35cc0f8a976
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 743c37f2ca18320e7ce68f4c5f88b2275a19c93f2239d10c7a1b5102ffb089444fb79ca947efef6dd47792bfeeafe82ead49812e92605f2ccafa0b351ce9ac43
|
7
|
+
data.tar.gz: cb0eb761ee2dfa89c3fc0f8d096957269905052778756a09fc755c3a5975264c690e725e38f707e4a10be618e8255f6b2cde26d1755d4ddac3d06c919e7499b7
|
data/README.md
CHANGED
@@ -65,9 +65,12 @@ gem install issue-db --version "X.X.X"
|
|
65
65
|
|
66
66
|
## Usage 💻
|
67
67
|
|
68
|
-
|
68
|
+
This section goes into details on the following CRUD operations are available for the `issue-db` gem.
|
69
69
|
|
70
|
-
|
70
|
+
Note: All methods return the `IssueDB::Record` of the object which was involved in the operation
|
71
|
+
|
72
|
+
> [!IMPORTANT]
|
73
|
+
> The key for the record is the title of the GitHub issue. This means that the key **must be unique** within the database. If you try to do any sort of duplicate operation on a key that already exists (like creating it again), the `issue-db` gem will return the existing record without modifying it. Here is an example log message where someone calls `db.create("order_number_123", { location: "London", items: [ "cookies", "espresso" ] })` but the key already exists: `skipping issue creation and returning existing issue - an issue already exists with the key: order_number_123`. Additionally, if there are duplicates (same issue titles), then the latest issue will be returned (ex: issue 15 will be returned instead of issue 14). Basically, if you use fully unique keys, you won't ever run into this issue so please make sure to use unique keys for your records!
|
71
74
|
|
72
75
|
### `db.create(key, data, options = {})`
|
73
76
|
|
@@ -194,13 +197,14 @@ This section will go into detail around how you can configure the `issue-db` gem
|
|
194
197
|
|
195
198
|
## Authentication 🔒
|
196
199
|
|
197
|
-
The `issue-db` gem uses the [`Octokit.rb`](https://github.com/octokit/octokit.rb) library under the hood for interactions with the GitHub API. You have
|
200
|
+
The `issue-db` gem uses the [`Octokit.rb`](https://github.com/octokit/octokit.rb) library under the hood for interactions with the GitHub API. You have four options for authentication when using the `issue-db` gem:
|
198
201
|
|
199
202
|
> Note: The order displayed below is also the order of priority that this Gem uses to authenticate.
|
200
203
|
|
201
204
|
1. Pass in your own authenticated `Octokit.rb` instance to the `IssueDB.new` method
|
202
|
-
2.
|
203
|
-
3. Use a GitHub
|
205
|
+
2. Pass GitHub App authentication parameters directly to the `IssueDB.new` method
|
206
|
+
3. Use a GitHub App by setting the `ISSUE_DB_GITHUB_APP_ID`, `ISSUE_DB_GITHUB_APP_INSTALLATION_ID`, and `ISSUE_DB_GITHUB_APP_KEY` environment variables
|
207
|
+
4. Use a GitHub personal access token by setting the `ISSUE_DB_GITHUB_TOKEN` environment variable
|
204
208
|
|
205
209
|
> Using a GitHub App is the suggested method
|
206
210
|
|
@@ -215,7 +219,31 @@ require "issue_db"
|
|
215
219
|
db = IssueDB.new("<org>/<repo>") # THAT'S IT! 🎉
|
216
220
|
```
|
217
221
|
|
218
|
-
### Using
|
222
|
+
### Using GitHub App Parameters Directly
|
223
|
+
|
224
|
+
You can now pass GitHub App authentication parameters directly to the `IssueDB.new` method. This is especially useful when you want to manage authentication credentials in your application code or when you have multiple GitHub Apps for different purposes:
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
require "issue_db"
|
228
|
+
|
229
|
+
# Pass GitHub App credentials directly to IssueDB.new
|
230
|
+
db = IssueDB.new(
|
231
|
+
"<org>/<repo>",
|
232
|
+
app_id: 12345, # Your GitHub App ID
|
233
|
+
installation_id: 56789, # Your GitHub App Installation ID
|
234
|
+
app_key: "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----", # Your GitHub App private key
|
235
|
+
app_algo: "RS256" # Optional: defaults to RS256
|
236
|
+
)
|
237
|
+
```
|
238
|
+
|
239
|
+
**Parameters:**
|
240
|
+
|
241
|
+
- `app_id` (Integer) - Your GitHub App ID (found on the App's settings page)
|
242
|
+
- `installation_id` (Integer) - Your GitHub App Installation ID (found in the installation URL: `https://github.com/organizations/<org>/settings/installations/<installation_id>`)
|
243
|
+
- `app_key` (String) - Your GitHub App private key (can be the key content as a string or a file path ending in `.pem`)
|
244
|
+
- `app_algo` (String, optional) - The algorithm to use for JWT signing (defaults to "RS256")
|
245
|
+
|
246
|
+
### Using a GitHub App with Environment Variables
|
219
247
|
|
220
248
|
This is the single best way to use the `issue-db` gem because GitHub Apps have increased rate limits, fine-grained permissions, and are more secure than using a personal access token. All you have to do is provide three environment variables and the `issue-db` gem will take care of the rest:
|
221
249
|
|
data/issue-db.gemspec
CHANGED
@@ -3,37 +3,45 @@
|
|
3
3
|
require "octokit"
|
4
4
|
require_relative "utils/github"
|
5
5
|
|
6
|
-
|
6
|
+
module IssueDB
|
7
|
+
class AuthenticationError < StandardError; end
|
7
8
|
|
8
|
-
module Authentication
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
module Authentication
|
10
|
+
def self.login(client = nil, log = nil, app_id: nil, installation_id: nil, app_key: nil, app_algo: nil)
|
11
|
+
# if the client is not nil, use the pre-provided client
|
12
|
+
unless client.nil?
|
13
|
+
log.debug("using pre-provided client") if log
|
14
|
+
return client
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
app_key = ENV.fetch("ISSUE_DB_GITHUB_APP_KEY", nil)
|
22
|
-
if app_id && installation_id && app_key
|
23
|
-
log.debug("using github app authentication") if log
|
24
|
-
return GitHub.new(log:, app_id:, installation_id:, app_key:)
|
25
|
-
end
|
17
|
+
# if GitHub App parameters are provided, use them first
|
18
|
+
if app_id && installation_id && app_key
|
19
|
+
log.debug("using provided github app authentication parameters") if log
|
20
|
+
return IssueDB::Utils::GitHub.new(log:, app_id:, installation_id:, app_key:, app_algo:)
|
21
|
+
end
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
23
|
+
# if the client is nil, check for GitHub App env vars
|
24
|
+
# first, check if all three of the following env vars are set and have values
|
25
|
+
# ISSUE_DB_GITHUB_APP_ID, ISSUE_DB_GITHUB_APP_INSTALLATION_ID, ISSUE_DB_GITHUB_APP_KEY
|
26
|
+
env_app_id = ENV.fetch("ISSUE_DB_GITHUB_APP_ID", nil)
|
27
|
+
env_installation_id = ENV.fetch("ISSUE_DB_GITHUB_APP_INSTALLATION_ID", nil)
|
28
|
+
env_app_key = ENV.fetch("ISSUE_DB_GITHUB_APP_KEY", nil)
|
29
|
+
if env_app_id && env_installation_id && env_app_key
|
30
|
+
log.debug("using github app authentication from environment variables") if log
|
31
|
+
return IssueDB::Utils::GitHub.new(log:, app_id: env_app_id, installation_id: env_installation_id, app_key: env_app_key)
|
32
|
+
end
|
35
33
|
|
36
|
-
|
37
|
-
|
34
|
+
# if the client is nil and no GitHub App env vars were found, check for the ISSUE_DB_GITHUB_TOKEN
|
35
|
+
token = ENV.fetch("ISSUE_DB_GITHUB_TOKEN", nil)
|
36
|
+
if token
|
37
|
+
log.debug("using github token authentication") if log
|
38
|
+
octokit = Octokit::Client.new(access_token: token, page_size: 100)
|
39
|
+
octokit.auto_paginate = true
|
40
|
+
return octokit
|
41
|
+
end
|
42
|
+
|
43
|
+
# if we make it here, no valid auth method succeeded
|
44
|
+
raise AuthenticationError, "No valid GitHub authentication method was provided"
|
45
|
+
end
|
38
46
|
end
|
39
47
|
end
|
data/lib/issue_db/cache.rb
CHANGED
@@ -1,27 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module IssueDB
|
4
|
+
module Cache
|
5
|
+
# A helper method to update all issues in the cache
|
6
|
+
# :return: The updated issue cache as a list of issues
|
7
|
+
def update_issue_cache!
|
8
|
+
@log.debug("updating issue cache")
|
8
9
|
|
9
|
-
|
10
|
-
|
10
|
+
# find all issues in the repo that were created by this library
|
11
|
+
query = "repo:#{@repo.full_name} label:#{@label}"
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
13
|
+
search_response = nil
|
14
|
+
begin
|
15
|
+
# issues structure: { "total_count": 0, "incomplete_results": false, "items": [<issues>] }
|
16
|
+
search_response = @client.search_issues(query)
|
17
|
+
rescue StandardError => e
|
18
|
+
retry_err_msg = "error search_issues() call: #{e.message}"
|
19
|
+
@log.error(retry_err_msg)
|
20
|
+
raise StandardError, retry_err_msg
|
21
|
+
end
|
22
|
+
|
23
|
+
# Safety check to ensure search_response and items are not nil
|
24
|
+
if search_response.nil? || search_response.items.nil?
|
25
|
+
@log.error("search_issues returned nil response or nil items")
|
26
|
+
raise StandardError, "search_issues returned invalid response"
|
27
|
+
end
|
21
28
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
29
|
+
@log.debug("issue cache updated - cached #{search_response.total_count} issues")
|
30
|
+
@issues = search_response.items
|
31
|
+
@issues_last_updated = Time.now
|
32
|
+
return @issues
|
33
|
+
end
|
26
34
|
end
|
27
35
|
end
|
data/lib/issue_db/database.rb
CHANGED
@@ -4,196 +4,220 @@ require_relative "cache"
|
|
4
4
|
require_relative "models/record"
|
5
5
|
require_relative "utils/generate"
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
7
|
+
module IssueDB
|
8
|
+
class RecordNotFound < StandardError; end
|
9
|
+
|
10
|
+
class Database
|
11
|
+
include Cache
|
12
|
+
include Generate
|
13
|
+
|
14
|
+
# :param: log [Logger] a logger object to use for logging
|
15
|
+
# :param: client [Octokit::Client] an Octokit::Client object to use for interacting with the GitHub API
|
16
|
+
# :param: repo [Repository] a Repository object that represents the GitHub repository to use as the datastore
|
17
|
+
# :param: label [String] the label to use for issues managed in the datastore by this library
|
18
|
+
# :param: cache_expiry [Integer] the number of seconds to cache issues in memory (default: 60)
|
19
|
+
# :return: A new Database object
|
20
|
+
def initialize(log, client, repo, label, cache_expiry)
|
21
|
+
@log = log
|
22
|
+
@client = client
|
23
|
+
@repo = repo
|
24
|
+
@label = label
|
25
|
+
@cache_expiry = cache_expiry
|
26
|
+
@issues = nil
|
27
|
+
@issues_last_updated = nil
|
28
|
+
end
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
30
|
+
# Create a new issue/record in the database
|
31
|
+
# This will return the newly created issue as a Record object (parsed)
|
32
|
+
# :param: key [String] the key (issue title) to create
|
33
|
+
# :param: data [Hash] the data to use for the issue body
|
34
|
+
# :param: options [Hash] a hash of options containing extra data such as body_before and body_after
|
35
|
+
# :return: The newly created issue as a Record object
|
36
|
+
# usage example:
|
37
|
+
# data = { color: "blue", cool: true, popularity: 100, tags: ["tag1", "tag2"] }
|
38
|
+
# options = { body_before: "some text before the data", body_after: "some text after the data", include_closed: true }
|
39
|
+
# db.create("event123", {cool: true, data: "here"}, options)
|
40
|
+
def create(key, data, options = {})
|
41
|
+
@log.debug("attempting to create: #{key}")
|
42
|
+
issue = find_issue_by_key(key, options, create_mode: true)
|
43
|
+
if issue
|
44
|
+
@log.warn("skipping issue creation and returning existing issue - an issue already exists with the key: #{key}")
|
45
|
+
return Record.new(issue)
|
46
|
+
end
|
47
|
+
|
48
|
+
# if we make it here, no existing issues were found so we can safely create one
|
49
|
+
|
50
|
+
body = generate(data, body_before: options[:body_before], body_after: options[:body_after])
|
51
|
+
|
52
|
+
# if we make it here, no existing issues were found so we can safely create one
|
53
|
+
issue = @client.create_issue(@repo.full_name, key, body, { labels: @label })
|
54
|
+
|
55
|
+
# ensure the cache is initialized before appending and handle race conditions
|
56
|
+
current_issues = issues
|
57
|
+
if current_issues && !current_issues.include?(issue)
|
58
|
+
@issues << issue
|
59
|
+
end
|
60
|
+
|
61
|
+
@log.debug("issue created: #{key}")
|
44
62
|
return Record.new(issue)
|
45
63
|
end
|
46
64
|
|
47
|
-
#
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
return Record.new(issue)
|
59
|
-
end
|
60
|
-
|
61
|
-
# Read an issue/record from the database
|
62
|
-
# This will return the issue as a Record object (parsed)
|
63
|
-
# :param: key [String] the key (issue title) to read
|
64
|
-
# :param: options [Hash] a hash of options to pass through to the search method
|
65
|
-
# :return: The issue as a Record object
|
66
|
-
def read(key, options = {})
|
67
|
-
@log.debug("attempting to read: #{key}")
|
68
|
-
issue = find_issue_by_key(key, options)
|
69
|
-
@log.debug("issue found: #{key}")
|
70
|
-
return Record.new(issue)
|
71
|
-
end
|
72
|
-
|
73
|
-
# Update an issue/record in the database
|
74
|
-
# This will return the updated issue as a Record object (parsed)
|
75
|
-
# :param: key [String] the key (issue title) to update
|
76
|
-
# :param: data [Hash] the data to use for the issue body
|
77
|
-
# :param: options [Hash] a hash of options containing extra data such as body_before and body_after
|
78
|
-
# :return: The updated issue as a Record object
|
79
|
-
# usage example:
|
80
|
-
# data = { color: "blue", cool: true, popularity: 100, tags: ["tag1", "tag2"] }
|
81
|
-
# options = { body_before: "some text before the data", body_after: "some text after the data", include_closed: true }
|
82
|
-
# db.update("event123", {cool: true, data: "here"}, options)
|
83
|
-
def update(key, data, options = {})
|
84
|
-
@log.debug("attempting to update: #{key}")
|
85
|
-
issue = find_issue_by_key(key, options)
|
86
|
-
|
87
|
-
body = generate(data, body_before: options[:body_before], body_after: options[:body_after])
|
88
|
-
|
89
|
-
updated_issue = @client.update_issue(@repo.full_name, issue.number, key, body)
|
90
|
-
|
91
|
-
# update the issue in the cache using the reference we have
|
92
|
-
@issues[@issues.index(issue)] = updated_issue
|
93
|
-
|
94
|
-
@log.debug("issue updated: #{key}")
|
95
|
-
return Record.new(updated_issue)
|
96
|
-
end
|
97
|
-
|
98
|
-
# Delete an issue/record from the database - in this context, "delete" means to close the issue as "completed"
|
99
|
-
# :param: key [String] the key (issue title) to delete
|
100
|
-
# :param: options [Hash] a hash of options to pass through to the search method
|
101
|
-
# :return: The deleted issue as a Record object (parsed) - it may contain useful data
|
102
|
-
def delete(key, options = {})
|
103
|
-
@log.debug("attempting to delete: #{key}")
|
104
|
-
issue = find_issue_by_key(key, options)
|
105
|
-
|
106
|
-
deleted_issue = @client.close_issue(@repo.full_name, issue.number)
|
107
|
-
|
108
|
-
# remove the issue from the cache
|
109
|
-
@issues.delete(issue)
|
110
|
-
|
111
|
-
# return the deleted issue as a Record object as it may contain useful data
|
112
|
-
return Record.new(deleted_issue)
|
113
|
-
end
|
65
|
+
# Read an issue/record from the database
|
66
|
+
# This will return the issue as a Record object (parsed)
|
67
|
+
# :param: key [String] the key (issue title) to read
|
68
|
+
# :param: options [Hash] a hash of options to pass through to the search method
|
69
|
+
# :return: The issue as a Record object
|
70
|
+
def read(key, options = {})
|
71
|
+
@log.debug("attempting to read: #{key}")
|
72
|
+
issue = find_issue_by_key(key, options)
|
73
|
+
@log.debug("issue found: #{key}")
|
74
|
+
return Record.new(issue)
|
75
|
+
end
|
114
76
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
77
|
+
# Update an issue/record in the database
|
78
|
+
# This will return the updated issue as a Record object (parsed)
|
79
|
+
# :param: key [String] the key (issue title) to update
|
80
|
+
# :param: data [Hash] the data to use for the issue body
|
81
|
+
# :param: options [Hash] a hash of options containing extra data such as body_before and body_after
|
82
|
+
# :return: The updated issue as a Record object
|
83
|
+
# usage example:
|
84
|
+
# data = { color: "blue", cool: true, popularity: 100, tags: ["tag1", "tag2"] }
|
85
|
+
# options = { body_before: "some text before the data", body_after: "some text after the data", include_closed: true }
|
86
|
+
# db.update("event123", {cool: true, data: "here"}, options)
|
87
|
+
def update(key, data, options = {})
|
88
|
+
@log.debug("attempting to update: #{key}")
|
89
|
+
issue = find_issue_by_key(key, options)
|
90
|
+
|
91
|
+
body = generate(data, body_before: options[:body_before], body_after: options[:body_after])
|
92
|
+
|
93
|
+
updated_issue = @client.update_issue(@repo.full_name, issue.number, key, body)
|
94
|
+
|
95
|
+
# update the issue in the cache using the reference we have
|
96
|
+
index = @issues.index(issue)
|
97
|
+
if index
|
98
|
+
@issues[index] = updated_issue
|
99
|
+
else
|
100
|
+
@log.warn("issue not found in cache during update: #{key}")
|
101
|
+
# Force a cache refresh to ensure consistency
|
102
|
+
update_issue_cache!
|
103
|
+
end
|
104
|
+
|
105
|
+
@log.debug("issue updated: #{key}")
|
106
|
+
return Record.new(updated_issue)
|
127
107
|
end
|
128
108
|
|
129
|
-
|
130
|
-
|
109
|
+
# Delete an issue/record from the database - in this context, "delete" means to close the issue as "completed"
|
110
|
+
# :param: key [String] the key (issue title) to delete
|
111
|
+
# :param: options [Hash] a hash of options to pass through to the search method
|
112
|
+
# :return: The deleted issue as a Record object (parsed) - it may contain useful data
|
113
|
+
def delete(key, options = {})
|
114
|
+
@log.debug("attempting to delete: #{key}")
|
115
|
+
issue = find_issue_by_key(key, options)
|
116
|
+
|
117
|
+
deleted_issue = @client.close_issue(@repo.full_name, issue.number)
|
118
|
+
|
119
|
+
# update the issue in the cache using the reference we have
|
120
|
+
index = @issues.index(issue)
|
121
|
+
if index
|
122
|
+
@issues[index] = deleted_issue
|
123
|
+
else
|
124
|
+
@log.warn("issue not found in cache during delete: #{key}")
|
125
|
+
# Force a cache refresh to ensure consistency
|
126
|
+
update_issue_cache!
|
127
|
+
end
|
128
|
+
|
129
|
+
# return the deleted issue as a Record object as it may contain useful data
|
130
|
+
return Record.new(deleted_issue)
|
131
|
+
end
|
131
132
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
133
|
+
# List all keys in the database
|
134
|
+
# This will return an array of strings that represent the issue titles that are "keys" in the database
|
135
|
+
# :param: options [Hash] a hash of options to pass through to the search method
|
136
|
+
# :return: An array of strings that represent the issue titles that are "keys" in the database
|
137
|
+
# usage example:
|
138
|
+
# options = {include_closed: true}
|
139
|
+
# keys = db.list_keys(options)
|
140
|
+
def list_keys(options = {})
|
141
|
+
current_issues = issues
|
142
|
+
return [] if current_issues.nil?
|
143
|
+
|
144
|
+
keys = current_issues.select do |issue|
|
145
|
+
options[:include_closed] || issue[:state] == "open"
|
146
|
+
end.map do |issue|
|
147
|
+
issue[:title]
|
148
|
+
end
|
149
|
+
|
150
|
+
return keys
|
144
151
|
end
|
145
152
|
|
146
|
-
|
147
|
-
|
153
|
+
# List all issues/record in the database as Record objects (parsed)
|
154
|
+
# This will return an array of Record objects that represent the issues in the database
|
155
|
+
# :param: options [Hash] a hash of options to pass through to the search method
|
156
|
+
# :return: An array of Record objects that represent the issues in the database
|
157
|
+
# usage example:
|
158
|
+
# options = {include_closed: true}
|
159
|
+
# records = db.list(options)
|
160
|
+
def list(options = {})
|
161
|
+
current_issues = issues
|
162
|
+
return [] if current_issues.nil?
|
163
|
+
|
164
|
+
records = current_issues.select do |issue|
|
165
|
+
options[:include_closed] || issue[:state] == "open"
|
166
|
+
end.map do |issue|
|
167
|
+
Record.new(issue)
|
168
|
+
end
|
169
|
+
|
170
|
+
return records
|
171
|
+
end
|
148
172
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
173
|
+
# Force a refresh of the issues cache
|
174
|
+
# This will update the issues cache with the latest issues from the repo
|
175
|
+
# :return: The updated issue cache as a list of issues (Hash objects not parsed)
|
176
|
+
def refresh!
|
177
|
+
update_issue_cache!
|
178
|
+
end
|
155
179
|
|
156
180
|
protected
|
157
181
|
|
158
|
-
|
159
|
-
|
160
|
-
end
|
161
|
-
|
162
|
-
# A helper method to search through the issues cache and return the first issue that matches the given key
|
163
|
-
# :param: key [String] the key (issue title) to search for
|
164
|
-
# :param: options [Hash] a hash of options to pass through to the search method
|
165
|
-
# :param: create_mode [Boolean] a flag to indicate whether or not we are in create mode
|
166
|
-
# :return: A direct reference to the issue as a Hash object if found, otherwise throws a RecordNotFound error
|
167
|
-
# ... unless create_mode is true, in which case it returns nil as a signal to proceed with creating the issue
|
168
|
-
def find_issue_by_key(key, options = {}, create_mode: false)
|
169
|
-
issue = issues.find do |issue|
|
170
|
-
issue[:title] == key && (options[:include_closed] || issue[:state] == "open")
|
182
|
+
def not_found!(key)
|
183
|
+
raise RecordNotFound, "no record found for key: #{key}"
|
171
184
|
end
|
172
185
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
186
|
+
# A helper method to search through the issues cache and return the first issue that matches the given key
|
187
|
+
# :param: key [String] the key (issue title) to search for
|
188
|
+
# :param: options [Hash] a hash of options to pass through to the search method
|
189
|
+
# :param: create_mode [Boolean] a flag to indicate whether or not we are in create mode
|
190
|
+
# :return: A direct reference to the issue as a Hash object if found, otherwise throws a RecordNotFound error
|
191
|
+
# ... unless create_mode is true, in which case it returns nil as a signal to proceed with creating the issue
|
192
|
+
def find_issue_by_key(key, options = {}, create_mode: false)
|
193
|
+
issue = issues.find do |issue|
|
194
|
+
issue[:title] == key && (options[:include_closed] || issue[:state] == "open")
|
195
|
+
end
|
196
|
+
|
197
|
+
if issue.nil?
|
198
|
+
@log.debug("no issue found in cache for: #{key}")
|
199
|
+
return nil if create_mode
|
200
|
+
|
201
|
+
not_found!(key)
|
202
|
+
end
|
203
|
+
|
204
|
+
@log.debug("issue found in cache for: #{key}")
|
205
|
+
return issue
|
178
206
|
end
|
179
207
|
|
180
|
-
|
181
|
-
|
182
|
-
|
208
|
+
# A helper method to fetch all issues from the repo and update the issues cache
|
209
|
+
# It is cache aware
|
210
|
+
def issues
|
211
|
+
# update the issues cache if it is nil
|
212
|
+
update_issue_cache! if @issues.nil?
|
183
213
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
214
|
+
# update the cache if it has expired (with nil safety)
|
215
|
+
if !@issues_last_updated.nil? && (Time.now - @issues_last_updated) > @cache_expiry
|
216
|
+
@log.debug("issue cache expired - last updated: #{@issues_last_updated} - refreshing now")
|
217
|
+
update_issue_cache!
|
218
|
+
end
|
189
219
|
|
190
|
-
|
191
|
-
issues_cache_expired = (Time.now - @issues_last_updated) > @cache_expiry
|
192
|
-
if issues_cache_expired
|
193
|
-
@log.debug("issue cache expired - last updated: #{@issues_last_updated} - refreshing now")
|
194
|
-
update_issue_cache!
|
220
|
+
return @issues
|
195
221
|
end
|
196
|
-
|
197
|
-
return @issues
|
198
222
|
end
|
199
223
|
end
|