issue-db 1.1.0 → 1.3.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: 6f4349ba13a8c9f2971aae3c557c6bcd8d0b1b3fc51ecb4a901e3db2a30538db
4
- data.tar.gz: a59e1724a2f9068bd4596a311b051224ad48a8df158e0607f5dc51374f096033
3
+ metadata.gz: 8c165f569fbb2648a4c73a0bf4007f67e6da2ff2dae28132af424d9eb9844575
4
+ data.tar.gz: 224bc98fcc029e417e620e4d8efaa79030fdf1a767558d126ab22145fec66ac7
5
5
  SHA512:
6
- metadata.gz: e64935039edab751270a2fe8d924d32fff52035364baaa0a6455cf2fd0931fd4c51d39164b283435646c44cde628d3fb5cd98908673d8a1c0138a028855750d3
7
- data.tar.gz: 28e242676837588f0dbadf5dc831569bc26e703d2533938daebe62e0eb45d59c3d243d8678d4f91dd03baeba371f40cc3272a4290d23af6b6bafd88aac8b2976
6
+ metadata.gz: e870e848ab6765beb5470008c38c03c9d0133fa22c9bd74a93888bbef7e59fb001dc18f9311106bdc8e43c4ef442eeb8f9f18fd8e18df37fcc7f9e9e5e4a1ad2
7
+ data.tar.gz: 5932e25bbc9bd9f1bf68638d38b61675ac5a497c4cc2fc2e59e121bdc838a3c2759d45a32c3e47014c8e88f6000116d33227379336fc3fbe1370107a4613402f
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
 
@@ -85,6 +88,23 @@ record = db.create("order_number_123", { location: "London", items: [ "cookies",
85
88
  # more on this in another section of the README below
86
89
  options = { body_before: "some markdown text before the data", body_after: "some markdown text after the data" }
87
90
  record = db.create("order_number_123", { location: "London", items: [ "cookies", "espresso" ] }, options)
91
+
92
+ # with the `labels` option to add additional GitHub labels to the issue (in addition to the library-managed label)
93
+ options = { labels: ["priority:high", "customer:premium"] }
94
+ record = db.create("order_number_123", { location: "London", items: [ "cookies", "espresso" ] }, options)
95
+
96
+ # with the `assignees` option to assign GitHub users to the issue
97
+ options = { assignees: ["alice", "bob"] }
98
+ record = db.create("order_number_123", { location: "London", items: [ "cookies", "espresso" ] }, options)
99
+
100
+ # with multiple options combined
101
+ options = {
102
+ labels: ["priority:high", "customer:premium"],
103
+ assignees: ["alice", "bob"],
104
+ body_before: "some markdown text before the data",
105
+ body_after: "some markdown text after the data"
106
+ }
107
+ record = db.create("order_number_123", { location: "London", items: [ "cookies", "espresso" ] }, options)
88
108
  ```
89
109
 
90
110
  Notes:
@@ -122,6 +142,23 @@ record = db.update("order_number_123", { location: "London", items: [ "cookies",
122
142
  # more on this in another section of the README below
123
143
  options = { body_before: "# Order 123\n\nData:", body_after: "Please do not edit the body of this issue" }
124
144
  record = db.update("order_number_123", { location: "London", items: [ "cookies", "espresso", "chips" ] }, options)
145
+
146
+ # with the `labels` option to add additional GitHub labels to the issue (in addition to the library-managed label)
147
+ options = { labels: ["status:processed", "priority:low"] }
148
+ record = db.update("order_number_123", { location: "London", items: [ "cookies", "espresso", "chips" ] }, options)
149
+
150
+ # with the `assignees` option to assign GitHub users to the issue
151
+ options = { assignees: ["charlie", "diana"] }
152
+ record = db.update("order_number_123", { location: "London", items: [ "cookies", "espresso", "chips" ] }, options)
153
+
154
+ # with multiple options combined
155
+ options = {
156
+ labels: ["status:processed", "priority:low"],
157
+ assignees: ["charlie", "diana"],
158
+ body_before: "# Order 123\n\nData:",
159
+ body_after: "Please do not edit the body of this issue"
160
+ }
161
+ record = db.update("order_number_123", { location: "London", items: [ "cookies", "espresso", "chips" ] }, options)
125
162
  ```
126
163
 
127
164
  ### `db.delete(key, options = {})`
@@ -133,6 +170,21 @@ Example:
133
170
 
134
171
  ```ruby
135
172
  record = db.delete("order_number_123")
173
+
174
+ # with the `labels` option to add additional GitHub labels to the issue before closing it
175
+ options = { labels: ["archived", "completed"] }
176
+ record = db.delete("order_number_123", options)
177
+
178
+ # with the `assignees` option to assign GitHub users to the issue before closing it
179
+ options = { assignees: ["alice"] }
180
+ record = db.delete("order_number_123", options)
181
+
182
+ # with multiple options combined
183
+ options = {
184
+ labels: ["archived", "completed"],
185
+ assignees: ["alice"]
186
+ }
187
+ record = db.delete("order_number_123", options)
136
188
  ```
137
189
 
138
190
  ### `db.list_keys(options = {})`
@@ -192,15 +244,163 @@ This section will go into detail around how you can configure the `issue-db` gem
192
244
  | `GH_APP_ALGO` | The algo to use for your GitHub App if providing a private key | `RS256` |
193
245
  | `ISSUE_DB_GITHUB_TOKEN` | The GitHub personal access token to use for authenticating with the GitHub API. You can also use a GitHub app or pass in your own authenticated Octokit.rb instance | `nil` |
194
246
 
247
+ ## Labels 🏷️
248
+
249
+ The `issue-db` gem uses GitHub issue labels for organization and management. Here's how labels work:
250
+
251
+ ### Library-Managed Label
252
+
253
+ The gem automatically applies a library-managed label (default: `issue-db`) to all issues it creates. This label:
254
+
255
+ - **Cannot be modified or removed** by users (the gem will always ensure it's present)
256
+ - Is used to identify which issues in the repository are managed by the `issue-db` gem
257
+ - Can be customized by setting the `ISSUE_DB_LABEL` environment variable or passing the `label` parameter to `IssueDB.new()`
258
+
259
+ ### Additional Custom Labels
260
+
261
+ You can add your own custom labels to issues when creating, updating, or deleting records by using the `labels` option:
262
+
263
+ ```ruby
264
+ # Add custom labels when creating a record
265
+ options = { labels: ["priority:high", "customer:premium", "region:europe"] }
266
+ record = db.create("order_123", { product: "laptop" }, options)
267
+
268
+ # Add custom labels when updating a record
269
+ options = { labels: ["status:processed", "priority:low"] }
270
+ record = db.update("order_123", { product: "laptop", status: "shipped" }, options)
271
+
272
+ # Add custom labels before deleting (closing) a record
273
+ options = { labels: ["archived", "completed", "Q4-2024"] }
274
+ record = db.delete("order_123", options)
275
+ ```
276
+
277
+ **Important Notes:**
278
+
279
+ - Custom labels are **added in addition** to the library-managed label, not instead of it
280
+ - If you accidentally include the library-managed label in your custom labels array, it will be automatically filtered out to prevent duplicates
281
+ - Custom labels follow GitHub's label naming conventions and restrictions
282
+ - Labels help with organization, filtering, and automation workflows in GitHub
283
+
284
+ ### Label Preservation
285
+
286
+ When performing update or delete operations, the gem preserves existing labels by default:
287
+
288
+ ```ruby
289
+ # Create a record with custom labels
290
+ options = { labels: ["priority:high", "customer:premium"] }
291
+ record = db.create("order_123", { product: "laptop" }, options)
292
+ # Result: Issue has labels ["issue-db", "priority:high", "customer:premium"]
293
+
294
+ # Update the record WITHOUT specifying labels - existing labels are preserved
295
+ record = db.update("order_123", { product: "laptop", status: "shipped" })
296
+ # Result: Issue STILL has labels ["issue-db", "priority:high", "customer:premium"]
297
+
298
+ # Update the record WITH new labels - replaces all labels (except library-managed)
299
+ options = { labels: ["status:processed", "priority:low"] }
300
+ record = db.update("order_123", { product: "laptop", status: "delivered" }, options)
301
+ # Result: Issue now has labels ["issue-db", "status:processed", "priority:low"]
302
+ ```
303
+
304
+ **Key Behavior:**
305
+
306
+ - **Labels specified** = Replace all labels with library-managed label + specified labels
307
+ - **No labels specified** = Preserve existing labels exactly as they are
308
+
309
+ ### Example with Multiple Options
310
+
311
+ You can combine labels with other options:
312
+
313
+ ```ruby
314
+ options = {
315
+ labels: ["priority:high", "customer:vip"],
316
+ body_before: "## Order Details\n\nCustomer: VIP\n\n",
317
+ body_after: "\n\n---\n*This order requires special handling*"
318
+ }
319
+ record = db.create("vip_order_456", { items: ["premium_service"] }, options)
320
+ ```
321
+
322
+ ## Assignees 👥
323
+
324
+ The `issue-db` gem supports GitHub issue assignees for task ownership and responsibility tracking. Here's how assignees work:
325
+
326
+ ### Basic Assignee Usage
327
+
328
+ You can assign GitHub users to issues when creating, updating, or deleting records by using the `assignees` option:
329
+
330
+ ```ruby
331
+ # Assign users when creating a record
332
+ options = { assignees: ["alice", "bob"] }
333
+ record = db.create("task_123", { type: "code_review" }, options)
334
+
335
+ # Assign users when updating a record
336
+ options = { assignees: ["charlie", "diana"] }
337
+ record = db.update("task_123", { type: "code_review", status: "in_progress" }, options)
338
+
339
+ # Assign users before deleting (closing) a record
340
+ options = { assignees: ["alice"] }
341
+ record = db.delete("task_123", options)
342
+ ```
343
+
344
+ ### Assignee Preservation
345
+
346
+ Just like labels, the gem preserves existing assignees by default when no assignees are specified:
347
+
348
+ ```ruby
349
+ # Create a record with assignees
350
+ options = { assignees: ["alice", "bob"] }
351
+ record = db.create("task_123", { type: "code_review" }, options)
352
+ # Result: Issue is assigned to alice and bob
353
+
354
+ # Update the record WITHOUT specifying assignees - existing assignees are preserved
355
+ record = db.update("task_123", { type: "code_review", status: "in_progress" })
356
+ # Result: Issue is STILL assigned to alice and bob
357
+
358
+ # Update the record WITH new assignees - replaces all assignees
359
+ options = { assignees: ["charlie"] }
360
+ record = db.update("task_123", { type: "code_review", status: "completed" }, options)
361
+ # Result: Issue is now only assigned to charlie
362
+ ```
363
+
364
+ **Key Behavior:**
365
+
366
+ - **Assignees specified** = Replace all assignees with the specified assignees
367
+ - **No assignees specified** = Preserve existing assignees exactly as they are
368
+ - **Empty array specified** = Remove all assignees from the issue
369
+
370
+ ### Combining Labels and Assignees
371
+
372
+ You can use both labels and assignees together for comprehensive issue management:
373
+
374
+ ```ruby
375
+ options = {
376
+ labels: ["priority:high", "type:bug", "team:backend"],
377
+ assignees: ["alice", "bob"],
378
+ body_before: "## Bug Report\n\nPriority: High\nTeam: Backend\n\n",
379
+ body_after: "\n\n---\n*Assigned to backend team leads*"
380
+ }
381
+ record = db.create("bug_456", {
382
+ error: "Database timeout",
383
+ severity: "critical"
384
+ }, options)
385
+ ```
386
+
387
+ **Important Notes:**
388
+
389
+ - Assignees must be valid GitHub usernames with access to the repository
390
+ - You can assign up to 10 users to a single issue (GitHub's limit)
391
+ - Invalid or inaccessible usernames will cause the API call to fail
392
+ - Assignees help with responsibility tracking, notifications, and project management workflows
393
+
195
394
  ## Authentication 🔒
196
395
 
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:
396
+ 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
397
 
199
398
  > Note: The order displayed below is also the order of priority that this Gem uses to authenticate.
200
399
 
201
400
  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
401
+ 2. Pass GitHub App authentication parameters directly to the `IssueDB.new` method
402
+ 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
403
+ 4. Use a GitHub personal access token by setting the `ISSUE_DB_GITHUB_TOKEN` environment variable
204
404
 
205
405
  > Using a GitHub App is the suggested method
206
406
 
@@ -215,7 +415,31 @@ require "issue_db"
215
415
  db = IssueDB.new("<org>/<repo>") # THAT'S IT! 🎉
216
416
  ```
217
417
 
218
- ### Using a GitHub App
418
+ ### Using GitHub App Parameters Directly
419
+
420
+ 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:
421
+
422
+ ```ruby
423
+ require "issue_db"
424
+
425
+ # Pass GitHub App credentials directly to IssueDB.new
426
+ db = IssueDB.new(
427
+ "<org>/<repo>",
428
+ app_id: 12345, # Your GitHub App ID
429
+ installation_id: 56789, # Your GitHub App Installation ID
430
+ app_key: "-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----", # Your GitHub App private key
431
+ app_algo: "RS256" # Optional: defaults to RS256
432
+ )
433
+ ```
434
+
435
+ **Parameters:**
436
+
437
+ - `app_id` (Integer) - Your GitHub App ID (found on the App's settings page)
438
+ - `installation_id` (Integer) - Your GitHub App Installation ID (found in the installation URL: `https://github.com/organizations/<org>/settings/installations/<installation_id>`)
439
+ - `app_key` (String) - Your GitHub App private key (can be the key content as a string or a file path ending in `.pem`)
440
+ - `app_algo` (String, optional) - The algorithm to use for JWT signing (defaults to "RS256")
441
+
442
+ ### Using a GitHub App with Environment Variables
219
443
 
220
444
  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
445
 
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,33 +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 StandardError, 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
21
22
 
22
- # Safety check to ensure search_response and items are not nil
23
- if search_response.nil? || search_response.items.nil?
24
- @log.error("search_issues returned nil response or nil items")
25
- raise StandardError, "search_issues returned invalid response"
26
- end
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
27
28
 
28
- @log.debug("issue cache updated - cached #{search_response.total_count} issues")
29
- @issues = search_response.items
30
- @issues_last_updated = Time.now
31
- 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
32
34
  end
33
35
  end