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/issue.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
module LinearApi
|
|
4
4
|
# Represents a Linear issue
|
|
5
5
|
class Issue
|
|
6
|
+
# ==========================================================================
|
|
7
|
+
# GraphQL Mutations
|
|
8
|
+
# ==========================================================================
|
|
9
|
+
|
|
6
10
|
CREATE_MUTATION = <<~GRAPHQL
|
|
7
11
|
mutation CreateIssue($input: IssueCreateInput!) {
|
|
8
12
|
issueCreate(input: $input) {
|
|
@@ -12,6 +16,9 @@ module LinearApi
|
|
|
12
16
|
identifier
|
|
13
17
|
title
|
|
14
18
|
url
|
|
19
|
+
priority
|
|
20
|
+
dueDate
|
|
21
|
+
estimate
|
|
15
22
|
state {
|
|
16
23
|
id
|
|
17
24
|
name
|
|
@@ -29,6 +36,9 @@ module LinearApi
|
|
|
29
36
|
id
|
|
30
37
|
identifier
|
|
31
38
|
title
|
|
39
|
+
priority
|
|
40
|
+
dueDate
|
|
41
|
+
estimate
|
|
32
42
|
state {
|
|
33
43
|
id
|
|
34
44
|
name
|
|
@@ -44,9 +54,55 @@ module LinearApi
|
|
|
44
54
|
}
|
|
45
55
|
GRAPHQL
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
ADD_COMMENT_MUTATION = <<~GRAPHQL
|
|
58
|
+
mutation AddComment($issueId: String!, $body: String!) {
|
|
59
|
+
commentCreate(input: { issueId: $issueId, body: $body }) {
|
|
60
|
+
success
|
|
61
|
+
comment {
|
|
62
|
+
id
|
|
63
|
+
body
|
|
64
|
+
createdAt
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
GRAPHQL
|
|
69
|
+
|
|
70
|
+
ARCHIVE_MUTATION = <<~GRAPHQL
|
|
71
|
+
mutation ArchiveIssue($id: String!) {
|
|
72
|
+
issueArchive(id: $id) {
|
|
73
|
+
success
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
GRAPHQL
|
|
77
|
+
|
|
78
|
+
UNARCHIVE_MUTATION = <<~GRAPHQL
|
|
79
|
+
mutation UnarchiveIssue($id: String!) {
|
|
80
|
+
issueUnarchive(id: $id) {
|
|
81
|
+
success
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
GRAPHQL
|
|
85
|
+
|
|
86
|
+
LINK_PR_MUTATION = <<~GRAPHQL
|
|
87
|
+
mutation LinkPR($issueId: String!, $url: String!) {
|
|
88
|
+
attachmentLinkURL(issueId: $issueId, url: $url) {
|
|
89
|
+
success
|
|
90
|
+
attachment {
|
|
91
|
+
id
|
|
92
|
+
url
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
GRAPHQL
|
|
97
|
+
|
|
98
|
+
# ==========================================================================
|
|
99
|
+
# GraphQL Queries
|
|
100
|
+
# ==========================================================================
|
|
101
|
+
|
|
102
|
+
# Direct lookup by identifier (exact match, replaces search-based lookup)
|
|
103
|
+
GET_BY_IDENTIFIER_QUERY = <<~GRAPHQL
|
|
104
|
+
query GetIssueByIdentifier($filter: IssueFilter!) {
|
|
105
|
+
issues(filter: $filter, first: 1) {
|
|
50
106
|
nodes {
|
|
51
107
|
id
|
|
52
108
|
identifier
|
|
@@ -54,6 +110,8 @@ module LinearApi
|
|
|
54
110
|
description
|
|
55
111
|
url
|
|
56
112
|
priority
|
|
113
|
+
dueDate
|
|
114
|
+
estimate
|
|
57
115
|
state {
|
|
58
116
|
id
|
|
59
117
|
name
|
|
@@ -88,6 +146,43 @@ module LinearApi
|
|
|
88
146
|
}
|
|
89
147
|
GRAPHQL
|
|
90
148
|
|
|
149
|
+
# Full-text search (use search_issues for fuzzy matching)
|
|
150
|
+
SEARCH_QUERY = <<~GRAPHQL
|
|
151
|
+
query SearchIssues($term: String!, $first: Int!) {
|
|
152
|
+
searchIssues(term: $term, first: $first) {
|
|
153
|
+
nodes {
|
|
154
|
+
id
|
|
155
|
+
identifier
|
|
156
|
+
title
|
|
157
|
+
description
|
|
158
|
+
url
|
|
159
|
+
priority
|
|
160
|
+
dueDate
|
|
161
|
+
estimate
|
|
162
|
+
state {
|
|
163
|
+
id
|
|
164
|
+
name
|
|
165
|
+
type
|
|
166
|
+
}
|
|
167
|
+
labels {
|
|
168
|
+
nodes {
|
|
169
|
+
id
|
|
170
|
+
name
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
assignee {
|
|
174
|
+
id
|
|
175
|
+
name
|
|
176
|
+
}
|
|
177
|
+
project {
|
|
178
|
+
id
|
|
179
|
+
name
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
GRAPHQL
|
|
185
|
+
|
|
91
186
|
LIST_QUERY = <<~GRAPHQL
|
|
92
187
|
query ListIssues($teamId: String!, $first: Int!) {
|
|
93
188
|
team(id: $teamId) {
|
|
@@ -97,6 +192,8 @@ module LinearApi
|
|
|
97
192
|
identifier
|
|
98
193
|
title
|
|
99
194
|
priority
|
|
195
|
+
dueDate
|
|
196
|
+
estimate
|
|
100
197
|
state {
|
|
101
198
|
id
|
|
102
199
|
name
|
|
@@ -114,21 +211,128 @@ module LinearApi
|
|
|
114
211
|
}
|
|
115
212
|
GRAPHQL
|
|
116
213
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
214
|
+
LIST_PAGINATED_QUERY = <<~GRAPHQL
|
|
215
|
+
query ListIssuesPaginated($teamId: String!, $first: Int!, $after: String) {
|
|
216
|
+
team(id: $teamId) {
|
|
217
|
+
issues(first: $first, after: $after, orderBy: updatedAt) {
|
|
218
|
+
nodes {
|
|
219
|
+
id
|
|
220
|
+
identifier
|
|
221
|
+
title
|
|
222
|
+
priority
|
|
223
|
+
dueDate
|
|
224
|
+
estimate
|
|
225
|
+
state {
|
|
226
|
+
id
|
|
227
|
+
name
|
|
228
|
+
type
|
|
229
|
+
}
|
|
230
|
+
labels {
|
|
231
|
+
nodes {
|
|
232
|
+
id
|
|
233
|
+
name
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
pageInfo {
|
|
238
|
+
hasNextPage
|
|
239
|
+
endCursor
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
GRAPHQL
|
|
245
|
+
|
|
246
|
+
GET_BY_ID_QUERY = <<~GRAPHQL
|
|
247
|
+
query GetIssueById($id: String!) {
|
|
248
|
+
issue(id: $id) {
|
|
249
|
+
id
|
|
250
|
+
identifier
|
|
251
|
+
title
|
|
252
|
+
description
|
|
253
|
+
url
|
|
254
|
+
priority
|
|
255
|
+
dueDate
|
|
256
|
+
estimate
|
|
257
|
+
state {
|
|
122
258
|
id
|
|
123
|
-
|
|
124
|
-
|
|
259
|
+
name
|
|
260
|
+
type
|
|
261
|
+
}
|
|
262
|
+
labels {
|
|
263
|
+
nodes {
|
|
264
|
+
id
|
|
265
|
+
name
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
assignee {
|
|
269
|
+
id
|
|
270
|
+
name
|
|
271
|
+
}
|
|
272
|
+
project {
|
|
273
|
+
id
|
|
274
|
+
name
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
GRAPHQL
|
|
279
|
+
|
|
280
|
+
BATCH_GET_QUERY = <<~GRAPHQL
|
|
281
|
+
query BatchGetIssues($filter: IssueFilter!) {
|
|
282
|
+
issues(filter: $filter) {
|
|
283
|
+
nodes {
|
|
284
|
+
id
|
|
285
|
+
identifier
|
|
286
|
+
title
|
|
287
|
+
priority
|
|
288
|
+
dueDate
|
|
289
|
+
estimate
|
|
290
|
+
state {
|
|
291
|
+
id
|
|
292
|
+
name
|
|
293
|
+
type
|
|
294
|
+
}
|
|
295
|
+
labels {
|
|
296
|
+
nodes {
|
|
297
|
+
id
|
|
298
|
+
name
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
assignee {
|
|
302
|
+
id
|
|
303
|
+
name
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
GRAPHQL
|
|
309
|
+
|
|
310
|
+
SUB_ISSUES_QUERY = <<~GRAPHQL
|
|
311
|
+
query GetSubIssues($id: String!) {
|
|
312
|
+
issue(id: $id) {
|
|
313
|
+
children {
|
|
314
|
+
nodes {
|
|
315
|
+
id
|
|
316
|
+
identifier
|
|
317
|
+
title
|
|
318
|
+
state {
|
|
319
|
+
id
|
|
320
|
+
name
|
|
321
|
+
}
|
|
322
|
+
priority
|
|
323
|
+
}
|
|
125
324
|
}
|
|
126
325
|
}
|
|
127
326
|
}
|
|
128
327
|
GRAPHQL
|
|
129
328
|
|
|
329
|
+
# ==========================================================================
|
|
330
|
+
# Model
|
|
331
|
+
# ==========================================================================
|
|
332
|
+
|
|
130
333
|
attr_reader :id, :identifier, :title, :description, :url, :priority,
|
|
131
|
-
:state, :labels, :assignee, :project, :comments,
|
|
334
|
+
:state, :labels, :assignee, :project, :comments,
|
|
335
|
+
:due_date, :estimate, :raw
|
|
132
336
|
|
|
133
337
|
def initialize(data)
|
|
134
338
|
@raw = data
|
|
@@ -138,6 +342,8 @@ module LinearApi
|
|
|
138
342
|
@description = data['description']
|
|
139
343
|
@url = data['url']
|
|
140
344
|
@priority = data['priority']
|
|
345
|
+
@due_date = data['dueDate']
|
|
346
|
+
@estimate = data['estimate']
|
|
141
347
|
@state = data['state']
|
|
142
348
|
@labels = (data.dig('labels', 'nodes') || [])
|
|
143
349
|
@assignee = data['assignee']
|
|
@@ -161,6 +367,8 @@ module LinearApi
|
|
|
161
367
|
description: description,
|
|
162
368
|
url: url,
|
|
163
369
|
priority: priority,
|
|
370
|
+
due_date: due_date,
|
|
371
|
+
estimate: estimate,
|
|
164
372
|
state: state_name,
|
|
165
373
|
labels: label_names
|
|
166
374
|
}
|
|
@@ -23,7 +23,7 @@ module LinearApi
|
|
|
23
23
|
existing = SyncedIssue.find_open_for_fingerprint(fingerprint)
|
|
24
24
|
return add_occurrence_comment(existing, exception, context) if existing
|
|
25
25
|
|
|
26
|
-
# Ensure auto-tracked project and label exist
|
|
26
|
+
# Ensure auto-tracked project and label exist (uses in-memory cache after first call)
|
|
27
27
|
project_id = ensure_auto_tracked_project
|
|
28
28
|
auto_label_id = ensure_auto_tracked_label
|
|
29
29
|
|
|
@@ -57,6 +57,10 @@ module LinearApi
|
|
|
57
57
|
)
|
|
58
58
|
|
|
59
59
|
Result.new(success: true, data: synced_issue)
|
|
60
|
+
rescue StandardError => e
|
|
61
|
+
# Never let error tracking failures propagate - log and return failure
|
|
62
|
+
LinearApi.logger.error { "LinearApi::IssueTracker: failed to track error: #{e.class} - #{e.message}" }
|
|
63
|
+
Result.new(success: false, error: "Error tracking failed: #{e.message}")
|
|
60
64
|
end
|
|
61
65
|
|
|
62
66
|
# Track a custom issue (not from exception)
|
|
@@ -98,10 +102,17 @@ module LinearApi
|
|
|
98
102
|
)
|
|
99
103
|
|
|
100
104
|
Result.new(success: true, data: synced_issue)
|
|
105
|
+
rescue StandardError => e
|
|
106
|
+
LinearApi.logger.error { "LinearApi::IssueTracker: failed to track issue: #{e.class} - #{e.message}" }
|
|
107
|
+
Result.new(success: false, error: "Issue tracking failed: #{e.message}")
|
|
101
108
|
end
|
|
102
109
|
|
|
103
110
|
# Setup auto-tracking infrastructure (project + label)
|
|
104
111
|
def setup!
|
|
112
|
+
# Clear in-memory cache to force fresh lookup
|
|
113
|
+
@auto_tracked_project_id = nil
|
|
114
|
+
@auto_tracked_label_id = nil
|
|
115
|
+
|
|
105
116
|
{
|
|
106
117
|
project_id: ensure_auto_tracked_project,
|
|
107
118
|
label_id: ensure_auto_tracked_label
|
|
@@ -127,11 +138,16 @@ module LinearApi
|
|
|
127
138
|
labels.filter_map { |label| CachedMetadata.resolve_label_id(label) }
|
|
128
139
|
end
|
|
129
140
|
|
|
130
|
-
# Ensure the "Auto-Tracked Errors" project exists
|
|
141
|
+
# Ensure the "Auto-Tracked Errors" project exists (with in-memory memoization)
|
|
131
142
|
def ensure_auto_tracked_project
|
|
132
|
-
|
|
143
|
+
return @auto_tracked_project_id if @auto_tracked_project_id
|
|
144
|
+
|
|
145
|
+
# Check DB cache first
|
|
133
146
|
cached = CachedMetadata.projects.find_by(key: AUTO_TRACKED_PROJECT_KEY)
|
|
134
|
-
|
|
147
|
+
if cached
|
|
148
|
+
@auto_tracked_project_id = cached.linear_id
|
|
149
|
+
return @auto_tracked_project_id
|
|
150
|
+
end
|
|
135
151
|
|
|
136
152
|
# Check if project exists in Linear
|
|
137
153
|
result = LinearApi.client.list_projects
|
|
@@ -139,7 +155,8 @@ module LinearApi
|
|
|
139
155
|
existing = result.data.find { |p| p.name == AUTO_TRACKED_PROJECT_NAME }
|
|
140
156
|
if existing
|
|
141
157
|
cache_project(existing)
|
|
142
|
-
|
|
158
|
+
@auto_tracked_project_id = existing.id
|
|
159
|
+
return @auto_tracked_project_id
|
|
143
160
|
end
|
|
144
161
|
end
|
|
145
162
|
|
|
@@ -153,14 +170,19 @@ module LinearApi
|
|
|
153
170
|
return nil unless create_result.success?
|
|
154
171
|
|
|
155
172
|
cache_project(create_result.data)
|
|
156
|
-
create_result.data.id
|
|
173
|
+
@auto_tracked_project_id = create_result.data.id
|
|
157
174
|
end
|
|
158
175
|
|
|
159
|
-
# Ensure the "auto:tracked" label exists
|
|
176
|
+
# Ensure the "auto:tracked" label exists (with in-memory memoization)
|
|
160
177
|
def ensure_auto_tracked_label
|
|
161
|
-
|
|
178
|
+
return @auto_tracked_label_id if @auto_tracked_label_id
|
|
179
|
+
|
|
180
|
+
# Check DB cache first
|
|
162
181
|
cached = CachedMetadata.labels.find_by(key: AUTO_TRACKED_LABEL_NAME)
|
|
163
|
-
|
|
182
|
+
if cached
|
|
183
|
+
@auto_tracked_label_id = cached.linear_id
|
|
184
|
+
return @auto_tracked_label_id
|
|
185
|
+
end
|
|
164
186
|
|
|
165
187
|
# Check if label exists in Linear
|
|
166
188
|
result = LinearApi.client.list_labels
|
|
@@ -168,7 +190,8 @@ module LinearApi
|
|
|
168
190
|
existing = result.data.find { |l| l.name == AUTO_TRACKED_LABEL_NAME }
|
|
169
191
|
if existing
|
|
170
192
|
cache_label(existing)
|
|
171
|
-
|
|
193
|
+
@auto_tracked_label_id = existing.id
|
|
194
|
+
return @auto_tracked_label_id
|
|
172
195
|
end
|
|
173
196
|
end
|
|
174
197
|
|
|
@@ -182,7 +205,7 @@ module LinearApi
|
|
|
182
205
|
return nil unless create_result.success?
|
|
183
206
|
|
|
184
207
|
cache_label(create_result.data)
|
|
185
|
-
create_result.data.id
|
|
208
|
+
@auto_tracked_label_id = create_result.data.id
|
|
186
209
|
end
|
|
187
210
|
|
|
188
211
|
def cache_project(project)
|
|
@@ -253,16 +276,27 @@ module LinearApi
|
|
|
253
276
|
end
|
|
254
277
|
|
|
255
278
|
def sanitize_context(context)
|
|
256
|
-
context.
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
279
|
+
context.each_with_object({}) do |(key, value), hash|
|
|
280
|
+
key_str = key.to_s
|
|
281
|
+
if LinearApi::SENSITIVE_KEY_PATTERN.match?(key_str)
|
|
282
|
+
hash[key_str] = '[REDACTED]'
|
|
283
|
+
else
|
|
284
|
+
hash[key_str] = sanitize_value(value)
|
|
262
285
|
end
|
|
263
286
|
end
|
|
264
287
|
end
|
|
265
288
|
|
|
289
|
+
def sanitize_value(value)
|
|
290
|
+
case value
|
|
291
|
+
when Time, DateTime then value.iso8601
|
|
292
|
+
when Date then value.to_s
|
|
293
|
+
when ActiveRecord::Base then "#{value.class.name}##{value.id}"
|
|
294
|
+
when Hash then sanitize_context(value)
|
|
295
|
+
when Array then value.map { |v| sanitize_value(v) }
|
|
296
|
+
else value
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
266
300
|
def add_occurrence_comment(synced_issue, exception, context)
|
|
267
301
|
comment_body = <<~MARKDOWN
|
|
268
302
|
## New Occurrence
|
data/lib/linear_api/label.rb
CHANGED
|
@@ -18,6 +18,20 @@ module LinearApi
|
|
|
18
18
|
}
|
|
19
19
|
GRAPHQL
|
|
20
20
|
|
|
21
|
+
CREATE_MUTATION = <<~GRAPHQL
|
|
22
|
+
mutation CreateLabel($input: IssueLabelCreateInput!) {
|
|
23
|
+
issueLabelCreate(input: $input) {
|
|
24
|
+
success
|
|
25
|
+
issueLabel {
|
|
26
|
+
id
|
|
27
|
+
name
|
|
28
|
+
color
|
|
29
|
+
description
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
GRAPHQL
|
|
34
|
+
|
|
21
35
|
attr_reader :id, :name, :color, :description, :raw
|
|
22
36
|
|
|
23
37
|
def initialize(data)
|
data/lib/linear_api/project.rb
CHANGED
|
@@ -13,13 +13,31 @@ module LinearApi
|
|
|
13
13
|
description
|
|
14
14
|
state
|
|
15
15
|
progress
|
|
16
|
+
url
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
21
|
GRAPHQL
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
CREATE_MUTATION = <<~GRAPHQL
|
|
24
|
+
mutation CreateProject($input: ProjectCreateInput!) {
|
|
25
|
+
projectCreate(input: $input) {
|
|
26
|
+
success
|
|
27
|
+
project {
|
|
28
|
+
id
|
|
29
|
+
name
|
|
30
|
+
description
|
|
31
|
+
url
|
|
32
|
+
color
|
|
33
|
+
state
|
|
34
|
+
progress
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
GRAPHQL
|
|
39
|
+
|
|
40
|
+
attr_reader :id, :name, :description, :state, :progress, :url, :color, :raw
|
|
23
41
|
|
|
24
42
|
def initialize(data)
|
|
25
43
|
@raw = data
|
|
@@ -28,6 +46,8 @@ module LinearApi
|
|
|
28
46
|
@description = data['description']
|
|
29
47
|
@state = data['state']
|
|
30
48
|
@progress = data['progress']
|
|
49
|
+
@url = data['url']
|
|
50
|
+
@color = data['color']
|
|
31
51
|
end
|
|
32
52
|
|
|
33
53
|
def to_h
|
|
@@ -36,7 +56,9 @@ module LinearApi
|
|
|
36
56
|
name: name,
|
|
37
57
|
description: description,
|
|
38
58
|
state: state,
|
|
39
|
-
progress: progress
|
|
59
|
+
progress: progress,
|
|
60
|
+
url: url,
|
|
61
|
+
color: color
|
|
40
62
|
}
|
|
41
63
|
end
|
|
42
64
|
end
|
data/lib/linear_api/result.rb
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
module LinearApi
|
|
4
4
|
# Result object for API operations
|
|
5
|
+
#
|
|
6
|
+
# Always check success?/failure? before accessing data/error:
|
|
7
|
+
#
|
|
8
|
+
# result = client.create_issue(title: 'Bug')
|
|
9
|
+
# if result.success?
|
|
10
|
+
# puts result.data.identifier
|
|
11
|
+
# else
|
|
12
|
+
# puts result.error
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# Or use value! to raise on failure:
|
|
16
|
+
#
|
|
17
|
+
# issue = client.create_issue(title: 'Bug').value!
|
|
18
|
+
# puts issue.identifier
|
|
19
|
+
#
|
|
5
20
|
class Result
|
|
6
21
|
attr_reader :data, :error, :raw_response
|
|
7
22
|
|
|
@@ -20,17 +35,14 @@ module LinearApi
|
|
|
20
35
|
!@success
|
|
21
36
|
end
|
|
22
37
|
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
30
|
-
end
|
|
38
|
+
# Returns data on success, raises LinearApi::Error on failure
|
|
39
|
+
#
|
|
40
|
+
# @return [Object] the result data
|
|
41
|
+
# @raise [LinearApi::Error] if the result is a failure
|
|
42
|
+
def value!
|
|
43
|
+
raise LinearApi::Error, error if failure?
|
|
31
44
|
|
|
32
|
-
|
|
33
|
-
data.respond_to?(method, include_private) || super
|
|
45
|
+
data
|
|
34
46
|
end
|
|
35
47
|
end
|
|
36
48
|
end
|
data/lib/linear_api/team.rb
CHANGED
|
@@ -23,6 +23,66 @@ module LinearApi
|
|
|
23
23
|
name
|
|
24
24
|
type
|
|
25
25
|
position
|
|
26
|
+
color
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
GRAPHQL
|
|
32
|
+
|
|
33
|
+
USERS_QUERY = <<~GRAPHQL
|
|
34
|
+
query ListUsers($teamId: String!) {
|
|
35
|
+
team(id: $teamId) {
|
|
36
|
+
members {
|
|
37
|
+
nodes {
|
|
38
|
+
id
|
|
39
|
+
name
|
|
40
|
+
email
|
|
41
|
+
displayName
|
|
42
|
+
active
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
GRAPHQL
|
|
48
|
+
|
|
49
|
+
# Fetch all team metadata in a single query (for CacheSync)
|
|
50
|
+
ALL_METADATA_QUERY = <<~GRAPHQL
|
|
51
|
+
query FetchAllMetadata($teamId: String!) {
|
|
52
|
+
team(id: $teamId) {
|
|
53
|
+
labels {
|
|
54
|
+
nodes {
|
|
55
|
+
id
|
|
56
|
+
name
|
|
57
|
+
color
|
|
58
|
+
description
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
projects {
|
|
62
|
+
nodes {
|
|
63
|
+
id
|
|
64
|
+
name
|
|
65
|
+
description
|
|
66
|
+
state
|
|
67
|
+
progress
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
states {
|
|
71
|
+
nodes {
|
|
72
|
+
id
|
|
73
|
+
name
|
|
74
|
+
type
|
|
75
|
+
position
|
|
76
|
+
color
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
members {
|
|
80
|
+
nodes {
|
|
81
|
+
id
|
|
82
|
+
name
|
|
83
|
+
email
|
|
84
|
+
displayName
|
|
85
|
+
active
|
|
26
86
|
}
|
|
27
87
|
}
|
|
28
88
|
}
|
data/lib/linear_api/version.rb
CHANGED