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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c7e786c1b79b72e6ef52d8347d5d647f5275c714fd72699fd187032034ee0aae
4
- data.tar.gz: 6f18ea1176291ab1a6da20e90bc7e95d7ca3f727097c012a31bf3adb50faf1e3
3
+ metadata.gz: 740a1a64de7f9ab45fd7932682999972139392b4c8d8f48105cde4f49e9474b1
4
+ data.tar.gz: 6219653cb7f2303482e039f906ae788e4d9f9a89131df12e750ef35cc0f8a976
5
5
  SHA512:
6
- metadata.gz: 4379608b58b9cc396deeabfed41e01753e927792483000f3047e4c600eb0b2cf02e49ee53c3b8c34a7de494713899f9abc944013d344263961a20e13197a2cab
7
- data.tar.gz: 11da329ed7e764483a38934d552ad87dddbec88b9be0136de249fa45d5519604b176b9a3fe2f33d5a13c61e37c64c1af374fc2e8cdbe774e45065bae28cf8d89
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
- The following CRUD operations are available for the `issue-db` gem:
68
+ This section goes into details on the following CRUD operations are available for the `issue-db` gem.
69
69
 
70
- > Note: All methods return the `IssueDB::Record` of the object which was involved in the operation
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 three options for authentication when using the `issue-db` gem:
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. 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
203
- 3. Use a GitHub personal access token by setting the `ISSUE_DB_GITHUB_TOKEN` environment variable
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 a GitHub App
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
@@ -4,7 +4,7 @@ require_relative "lib/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "issue-db"
7
- spec.version = Version::VERSION
7
+ spec.version = IssueDB::Version::VERSION
8
8
  spec.authors = ["runwaylab", "GrantBirki"]
9
9
  spec.license = "MIT"
10
10
 
@@ -3,37 +3,45 @@
3
3
  require "octokit"
4
4
  require_relative "utils/github"
5
5
 
6
- class AuthenticationError < StandardError; end
6
+ module IssueDB
7
+ class AuthenticationError < StandardError; end
7
8
 
8
- module Authentication
9
- def self.login(client = nil, log = nil)
10
- # if the client is not nil, use the pre-provided client
11
- unless client.nil?
12
- log.debug("using pre-provided client") if log
13
- return client
14
- end
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
- # if the client is nil, check for GitHub App env vars first
17
- # first, check if all three of the following env vars are set and have values
18
- # ISSUE_DB_GITHUB_APP_ID, ISSUE_DB_GITHUB_APP_INSTALLATION_ID, ISSUE_DB_GITHUB_APP_KEY
19
- app_id = ENV.fetch("ISSUE_DB_GITHUB_APP_ID", nil)
20
- installation_id = ENV.fetch("ISSUE_DB_GITHUB_APP_INSTALLATION_ID", nil)
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
- # if the client is nil and no GitHub App env vars were found, check for the ISSUE_DB_GITHUB_TOKEN
28
- token = ENV.fetch("ISSUE_DB_GITHUB_TOKEN", nil)
29
- if token
30
- log.debug("using github token authentication") if log
31
- octokit = Octokit::Client.new(access_token: token, page_size: 100)
32
- octokit.auto_paginate = true
33
- return octokit
34
- end
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
- # if we make it here, no valid auth method succeeded
37
- raise AuthenticationError, "No valid GitHub authentication method was provided"
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
@@ -1,27 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module Cache
4
- # A helper method to update all issues in the cache
5
- # :return: The updated issue cache as a list of issues
6
- def update_issue_cache!
7
- @log.debug("updating issue cache")
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
- # find all issues in the repo that were created by this library
10
- query = "repo:#{@repo.full_name} label:#{@label}"
10
+ # find all issues in the repo that were created by this library
11
+ query = "repo:#{@repo.full_name} label:#{@label}"
11
12
 
12
- search_response = nil
13
- begin
14
- # issues structure: { "total_count": 0, "incomplete_results": false, "items": [<issues>] }
15
- search_response = @client.search_issues(query)
16
- rescue StandardError => e
17
- retry_err_msg = "error search_issues() call: #{e.message}"
18
- @log.error(retry_err_msg)
19
- raise retry_err_msg
20
- end
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
- @log.debug("issue cache updated - cached #{search_response.total_count} issues")
23
- @issues = search_response.items
24
- @issues_last_updated = Time.now
25
- return @issues
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
@@ -4,196 +4,220 @@ require_relative "cache"
4
4
  require_relative "models/record"
5
5
  require_relative "utils/generate"
6
6
 
7
- class RecordNotFound < StandardError; end
8
-
9
- class Database
10
- include Cache
11
- include Generate
12
-
13
- # :param: log [Logger] a logger object to use for logging
14
- # :param: client [Octokit::Client] an Octokit::Client object to use for interacting with the GitHub API
15
- # :param: repo [Repository] a Repository object that represents the GitHub repository to use as the datastore
16
- # :param: label [String] the label to use for issues managed in the datastore by this library
17
- # :param: cache_expiry [Integer] the number of seconds to cache issues in memory (default: 60)
18
- # :return: A new Database object
19
- def initialize(log, client, repo, label, cache_expiry)
20
- @log = log
21
- @client = client
22
- @repo = repo
23
- @label = label
24
- @cache_expiry = cache_expiry
25
- @issues = nil
26
- @issues_last_updated = nil
27
- end
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
- # Create a new issue/record in the database
30
- # This will return the newly created issue as a Record object (parsed)
31
- # :param: key [String] the key (issue title) to create
32
- # :param: data [Hash] the data to use for the issue body
33
- # :param: options [Hash] a hash of options containing extra data such as body_before and body_after
34
- # :return: The newly created issue as a Record object
35
- # usage example:
36
- # data = { color: "blue", cool: true, popularity: 100, tags: ["tag1", "tag2"] }
37
- # options = { body_before: "some text before the data", body_after: "some text after the data", include_closed: true }
38
- # db.create("event123", {cool: true, data: "here"}, options)
39
- def create(key, data, options = {})
40
- @log.debug("attempting to create: #{key}")
41
- issue = find_issue_by_key(key, options, create_mode: true)
42
- if issue
43
- @log.warn("skipping issue creation and returning existing issue - an issue already exists with the key: #{key}")
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
- # if we make it here, no existing issues were found so we can safely create one
48
-
49
- body = generate(data, body_before: options[:body_before], body_after: options[:body_after])
50
-
51
- # if we make it here, no existing issues were found so we can safely create one
52
- issue = @client.create_issue(@repo.full_name, key, body, { labels: @label })
53
-
54
- # append the newly created issue to the issues cache
55
- @issues << issue
56
-
57
- @log.debug("issue created: #{key}")
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
- # List all keys in the database
116
- # This will return an array of strings that represent the issue titles that are "keys" in the database
117
- # :param: options [Hash] a hash of options to pass through to the search method
118
- # :return: An array of strings that represent the issue titles that are "keys" in the database
119
- # usage example:
120
- # options = {include_closed: true}
121
- # keys = db.list_keys(options)
122
- def list_keys(options = {})
123
- keys = issues.select do |issue|
124
- options[:include_closed] || issue[:state] == "open"
125
- end.map do |issue|
126
- issue[:title]
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
- return keys
130
- end
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
- # List all issues/record in the database as Record objects (parsed)
133
- # This will return an array of Record objects that represent the issues in the database
134
- # :param: options [Hash] a hash of options to pass through to the search method
135
- # :return: An array of Record objects that represent the issues in the database
136
- # usage example:
137
- # options = {include_closed: true}
138
- # records = db.list(options)
139
- def list(options = {})
140
- records = issues.select do |issue|
141
- options[:include_closed] || issue[:state] == "open"
142
- end.map do |issue|
143
- Record.new(issue)
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
- return records
147
- end
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
- # Force a refresh of the issues cache
150
- # This will update the issues cache with the latest issues from the repo
151
- # :return: The updated issue cache as a list of issues (Hash objects not parsed)
152
- def refresh!
153
- update_issue_cache!
154
- end
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
- def not_found!(key)
159
- raise RecordNotFound, "no record found for key: #{key}"
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
- if issue.nil?
174
- @log.debug("no issue found in cache for: #{key}")
175
- return nil if create_mode
176
-
177
- not_found!(key)
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
- @log.debug("issue found in cache for: #{key}")
181
- return issue
182
- end
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
- # A helper method to fetch all issues from the repo and update the issues cache
185
- # It is cache aware
186
- def issues
187
- # update the issues cache if it is nil
188
- update_issue_cache! if @issues.nil?
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
- # update the cache if it has expired
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