linear_api 0.3.2 → 0.5.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 +46 -0
- data/README.md +113 -11
- data/app/models/linear_api/synced_issue.rb +38 -4
- data/lib/linear_api/cache_sync.rb +101 -65
- data/lib/linear_api/client.rb +235 -228
- data/lib/linear_api/engine.rb +1 -0
- data/lib/linear_api/issue.rb +219 -11
- data/lib/linear_api/issue_tracker.rb +51 -17
- data/lib/linear_api/label.rb +14 -0
- data/lib/linear_api/project.rb +24 -2
- data/lib/linear_api/result.rb +22 -10
- data/lib/linear_api/team.rb +60 -0
- data/lib/linear_api/version.rb +1 -1
- data/lib/linear_api.rb +42 -5
- data/lib/tasks/linear_api.rake +29 -12
- metadata +22 -2
data/lib/linear_api.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'logger'
|
|
3
4
|
require_relative 'linear_api/version'
|
|
4
5
|
require_relative 'linear_api/client'
|
|
5
6
|
require_relative 'linear_api/result'
|
|
@@ -19,23 +20,59 @@ module LinearApi
|
|
|
19
20
|
class Error < StandardError; end
|
|
20
21
|
class AuthenticationError < Error; end
|
|
21
22
|
class NotFoundError < Error; end
|
|
22
|
-
|
|
23
|
+
|
|
24
|
+
class RateLimitError < Error
|
|
25
|
+
attr_reader :retry_after
|
|
26
|
+
|
|
27
|
+
def initialize(message = 'Rate limited', retry_after: nil)
|
|
28
|
+
@retry_after = retry_after
|
|
29
|
+
super(message)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Sensitive key patterns to redact from issue descriptions
|
|
34
|
+
SENSITIVE_KEY_PATTERN = /password|secret|token|key|credential|ssn|api_key|authorization/i
|
|
23
35
|
|
|
24
36
|
class << self
|
|
25
|
-
attr_accessor :api_key, :team_id
|
|
37
|
+
attr_accessor :api_key, :team_id, :workspace_slug
|
|
38
|
+
attr_writer :logger
|
|
26
39
|
|
|
27
40
|
def configure
|
|
28
41
|
yield self
|
|
29
42
|
end
|
|
30
43
|
|
|
44
|
+
def logger
|
|
45
|
+
@logger ||= if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
|
46
|
+
Rails.logger
|
|
47
|
+
else
|
|
48
|
+
Logger.new($stdout, level: Logger::WARN)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
31
52
|
def client
|
|
32
|
-
|
|
53
|
+
@client_mutex ||= Mutex.new
|
|
54
|
+
@client_mutex.synchronize do
|
|
55
|
+
raise AuthenticationError, 'API key not configured' unless api_key
|
|
33
56
|
|
|
34
|
-
|
|
57
|
+
@client ||= Client.new(api_key: api_key, team_id: team_id)
|
|
58
|
+
end
|
|
35
59
|
end
|
|
36
60
|
|
|
37
61
|
def reset_client!
|
|
38
|
-
@
|
|
62
|
+
@client_mutex ||= Mutex.new
|
|
63
|
+
@client_mutex.synchronize do
|
|
64
|
+
@client = nil
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Quick health check against the Linear API
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean] true if the API is reachable and authenticated
|
|
71
|
+
def healthy?
|
|
72
|
+
result = client.query('query { viewer { id } }')
|
|
73
|
+
result.success?
|
|
74
|
+
rescue StandardError
|
|
75
|
+
false
|
|
39
76
|
end
|
|
40
77
|
|
|
41
78
|
# Rails calls eager_load! on all registered eager_load_namespaces.
|
data/lib/tasks/linear_api.rake
CHANGED
|
@@ -24,7 +24,7 @@ namespace :linear do
|
|
|
24
24
|
|
|
25
25
|
desc 'Sync all metadata from Linear API (labels, projects, states, users)'
|
|
26
26
|
task sync_cache: :environment do
|
|
27
|
-
puts 'Syncing Linear metadata from API...'
|
|
27
|
+
puts 'Syncing Linear metadata from API (single batched request)...'
|
|
28
28
|
|
|
29
29
|
results = LinearApi::CacheSync.sync_all
|
|
30
30
|
|
|
@@ -70,12 +70,17 @@ namespace :linear do
|
|
|
70
70
|
task export_cache: :environment do
|
|
71
71
|
output_path = ENV['LINEAR_EXPORT_PATH'] || Rails.root.join('linear_cache.json')
|
|
72
72
|
|
|
73
|
+
# Fetch team info for export (avoids hardcoded values)
|
|
74
|
+
team_data = { id: LinearApi.team_id }
|
|
75
|
+
team_result = LinearApi.client.get_team
|
|
76
|
+
if team_result.success?
|
|
77
|
+
team_data[:key] = team_result.data.key
|
|
78
|
+
team_data[:name] = team_result.data.name
|
|
79
|
+
end
|
|
80
|
+
|
|
73
81
|
data = {
|
|
74
82
|
last_updated: Time.current.iso8601,
|
|
75
|
-
team:
|
|
76
|
-
id: LinearApi.team_id,
|
|
77
|
-
key: 'TOS'
|
|
78
|
-
},
|
|
83
|
+
team: team_data,
|
|
79
84
|
workflow_states: export_states,
|
|
80
85
|
labels: export_labels,
|
|
81
86
|
projects: export_projects,
|
|
@@ -86,10 +91,10 @@ namespace :linear do
|
|
|
86
91
|
puts "Exported cache to #{output_path}"
|
|
87
92
|
end
|
|
88
93
|
|
|
89
|
-
desc 'Refresh stale synced issues from Linear'
|
|
94
|
+
desc 'Refresh stale synced issues from Linear (batched)'
|
|
90
95
|
task refresh_issues: :environment do
|
|
91
96
|
stale_count = LinearApi::SyncedIssue.stale.count
|
|
92
|
-
puts "Refreshing #{stale_count} stale issues..."
|
|
97
|
+
puts "Refreshing #{stale_count} stale issues (batched)..."
|
|
93
98
|
|
|
94
99
|
LinearApi::SyncedIssue.refresh_stale_issues!
|
|
95
100
|
|
|
@@ -119,8 +124,20 @@ namespace :linear do
|
|
|
119
124
|
end
|
|
120
125
|
end
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
desc 'Health check: verify Linear API connectivity'
|
|
128
|
+
task health: :environment do
|
|
129
|
+
puts 'Checking Linear API connectivity...'
|
|
130
|
+
|
|
131
|
+
if LinearApi.healthy?
|
|
132
|
+
puts ' ✓ Linear API is reachable and authenticated'
|
|
133
|
+
else
|
|
134
|
+
puts ' ✗ Cannot reach Linear API (check LINEAR_API_KEY)'
|
|
135
|
+
exit 1
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Helper methods for export (defined as module methods to avoid polluting global scope)
|
|
140
|
+
def self.export_states
|
|
124
141
|
LinearApi::CachedMetadata.states.each_with_object({}) do |state, hash|
|
|
125
142
|
hash[state.key] = {
|
|
126
143
|
id: state.linear_id,
|
|
@@ -130,7 +147,7 @@ namespace :linear do
|
|
|
130
147
|
end
|
|
131
148
|
end
|
|
132
149
|
|
|
133
|
-
def export_labels
|
|
150
|
+
def self.export_labels
|
|
134
151
|
LinearApi::CachedMetadata.labels.group_by(&:category).transform_values do |labels|
|
|
135
152
|
labels.each_with_object({}) do |label, hash|
|
|
136
153
|
key = label.key.split(':').last
|
|
@@ -143,7 +160,7 @@ namespace :linear do
|
|
|
143
160
|
end
|
|
144
161
|
end
|
|
145
162
|
|
|
146
|
-
def export_projects
|
|
163
|
+
def self.export_projects
|
|
147
164
|
LinearApi::CachedMetadata.projects.each_with_object({}) do |project, hash|
|
|
148
165
|
hash[project.key] = {
|
|
149
166
|
id: project.linear_id,
|
|
@@ -153,7 +170,7 @@ namespace :linear do
|
|
|
153
170
|
end
|
|
154
171
|
end
|
|
155
172
|
|
|
156
|
-
def export_users
|
|
173
|
+
def self.export_users
|
|
157
174
|
LinearApi::CachedMetadata.users.each_with_object({}) do |user, hash|
|
|
158
175
|
hash[user.key] = {
|
|
159
176
|
id: user.linear_id,
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: linear_api
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- TheOwnerStack
|
|
@@ -29,6 +29,26 @@ dependencies:
|
|
|
29
29
|
- - "<"
|
|
30
30
|
- !ruby/object:Gem::Version
|
|
31
31
|
version: '3.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: faraday-retry
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '1.0'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '3.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '1.0'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '3.0'
|
|
32
52
|
- !ruby/object:Gem::Dependency
|
|
33
53
|
name: rails
|
|
34
54
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -36,7 +56,7 @@ dependencies:
|
|
|
36
56
|
- - ">="
|
|
37
57
|
- !ruby/object:Gem::Version
|
|
38
58
|
version: '7.0'
|
|
39
|
-
type: :
|
|
59
|
+
type: :development
|
|
40
60
|
prerelease: false
|
|
41
61
|
version_requirements: !ruby/object:Gem::Requirement
|
|
42
62
|
requirements:
|