Class: CollavreLinear::Client
- Inherits:
-
Object
- Object
- CollavreLinear::Client
- Defined in:
- app/services/collavre_linear/client.rb
Defined Under Namespace
Classes: Error
Constant Summary collapse
- DEFAULT_ENDPOINT =
"https://api.linear.app/graphql"- ISSUE_CREATE =
IssueCreateInput fields used: teamId, title, description, parentId, projectId, stateId, assigneeId, labelIds, priority
<<~GQL.freeze mutation IssueCreate($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier updatedAt } } } GQL
- ISSUE_UPDATE =
IssueUpdateInput fields used: title, description, parentId, projectId, stateId, assigneeId, labelIds, priority (any subset passed as **fields)
<<~GQL.freeze mutation IssueUpdate($id: String!, $input: IssueUpdateInput!) { issueUpdate(id: $id, input: $input) { success issue { id identifier updatedAt } } } GQL
- PROJECT_CREATE =
ProjectCreateInput fields used: name, teamIds
<<~GQL.freeze mutation ProjectCreate($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id } } } GQL
- PROJECT_UPDATE =
ProjectUpdateInput fields used: name (any subset passed as **fields)
<<~GQL.freeze mutation ProjectUpdate($id: String!, $input: ProjectUpdateInput!) { projectUpdate(id: $id, input: $input) { success project { id } } } GQL
- COMMENT_CREATE =
CommentCreateInput fields used: issueId, body
<<~GQL.freeze mutation CommentCreate($input: CommentCreateInput!) { commentCreate(input: $input) { success comment { id updatedAt } } } GQL
- COMMENT_UPDATE =
CommentUpdateInput fields used: body
<<~GQL.freeze mutation CommentUpdate($id: String!, $input: CommentUpdateInput!) { commentUpdate(id: $id, input: $input) { success comment { id updatedAt } } } GQL
- COMMENT_DELETE =
<<~GQL.freeze mutation CommentDelete($id: String!) { commentDelete(id: $id) { success } } GQL
- WEBHOOK_CREATE =
WebhookCreateInput fields used: url, secret, teamId, resourceTypes
<<~GQL.freeze mutation WebhookCreate($input: WebhookCreateInput!) { webhookCreate(input: $input) { success webhook { id } } } GQL
- ISSUE_ARCHIVE =
Archive (soft-delete) a Linear issue by id.
<<~GQL.freeze mutation IssueArchive($id: String!) { issueArchive(id: $id) { success } } GQL
- WEBHOOK_DELETE =
Delete (deregister) a Linear webhook by id.
<<~GQL.freeze mutation WebhookDelete($id: String!) { webhookDelete(id: $id) { success } } GQL
- VIEWER =
Viewer identity only. Linear's schema exposes no Query field for the OAuth app actor id of the current token (
applicationWithAuthorization { appActor }is not valid — that field is absent and its type carries no appActor), so app_actor_id stays nil and EchoGuard no-ops. Loops are already prevented independently by InboundApplier (skip_linear_sync on inbound writes, IssueLink dedup on create, content_hash on update). <<~GQL.freeze query Viewer { viewer { id organization { id } } } GQL
- TEAMS =
List the workspace teams the token can see (for the link-a-project picker).
<<~GQL.freeze query Teams { teams(first: 250) { nodes { id name key } } } GQL
- PROJECTS =
List projects with their owning team ids so the picker can scope projects to the chosen team (a Linear project may belong to more than one team).
<<~GQL.freeze query Projects { projects(first: 250) { nodes { id name teams { nodes { id } } } } } GQL
- WORKFLOW_STATES =
List workflow states with their owning team id and type so the link modal can offer a "done state" combobox scoped to the chosen team.
typeis Linear's state category (triage/backlog/unstarted/started/completed/ canceled) — the picker defaults to the team's "completed" state. <<~GQL.freeze query WorkflowStates { workflowStates(first: 250) { nodes { id name type position team { id } } } } GQL
- ISSUE_QUERY =
Fetch a single issue's current workflow state (for seeding the pre-done snapshot when a leaf is completed with no locally-known Linear state).
<<~GQL.freeze query Issue($id: String!) { issue(id: $id) { id state { id name type } } } GQL
Instance Method Summary collapse
-
#archive_issue(id) ⇒ Boolean
Archive a Linear issue (soft-delete).
-
#create_comment(issue_id:, body:) ⇒ Hash
Create a comment on a Linear issue.
-
#create_issue(team_id:, title:, description: nil, parent_id: nil, project_id: nil, state_id: nil, assignee_id: nil, label_ids: [], priority: nil) ⇒ Hash
Create a Linear issue.
-
#create_project(name:, team_ids:) ⇒ Hash
Create a Linear project.
-
#delete_comment(id) ⇒ Boolean
Delete a Linear comment.
-
#delete_webhook(id) ⇒ Boolean
Delete (deregister) a webhook from Linear.
-
#fetch_issue_state(id) ⇒ Hash?
Fetch a single Linear issue's current workflow state.
-
#initialize(account) ⇒ Client
constructor
A new instance of Client.
-
#list_projects ⇒ Array<Hash>
List projects for the link picker, each with the ids of its owning teams so the UI can scope projects to the selected team.
-
#list_teams ⇒ Array<Hash>
List workspace teams for the link picker.
-
#list_workflow_states ⇒ Array<Hash>
List workflow states for the "done state" picker, each with its owning team id and category type so the UI can scope states to the selected team and default to the completed one.
-
#register_webhook(url:, secret:, team_id:, resource_types:) ⇒ Hash
Register a webhook with Linear.
-
#update_comment(id:, body:) ⇒ Hash
Edit an existing Linear comment's body.
-
#update_issue(id, **fields) ⇒ Hash
Update a Linear issue by id.
-
#update_project(id, **fields) ⇒ Hash
Update a Linear project by id.
-
#viewer_and_app_actor ⇒ Hash
Fetch the authenticated viewer identity.
Constructor Details
#initialize(account) ⇒ Client
Returns a new instance of Client.
229 230 231 232 |
# File 'app/services/collavre_linear/client.rb', line 229 def initialize(account) @account = account @endpoint = resolve_endpoint end |
Instance Method Details
#archive_issue(id) ⇒ Boolean
Archive a Linear issue (soft-delete).
301 302 303 304 |
# File 'app/services/collavre_linear/client.rb', line 301 def archive_issue(id) data = post!(ISSUE_ARCHIVE, { id: id }) data.dig("issueArchive", "success") == true end |
#create_comment(issue_id:, body:) ⇒ Hash
Create a comment on a Linear issue.
308 309 310 311 312 313 |
# File 'app/services/collavre_linear/client.rb', line 308 def create_comment(issue_id:, body:) input = { issueId: issue_id, body: body } data = post!(COMMENT_CREATE, { input: input }) node = data.dig("commentCreate", "comment") symbolize(node) end |
#create_issue(team_id:, title:, description: nil, parent_id: nil, project_id: nil, state_id: nil, assignee_id: nil, label_ids: [], priority: nil) ⇒ Hash
Create a Linear issue.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 |
# File 'app/services/collavre_linear/client.rb', line 249 def create_issue(team_id:, title:, description: nil, parent_id: nil, project_id: nil, state_id: nil, assignee_id: nil, label_ids: [], priority: nil) input = { teamId: team_id, title: title }.tap do |h| h[:description] = description if description h[:parentId] = parent_id if parent_id h[:projectId] = project_id if project_id h[:stateId] = state_id if state_id h[:assigneeId] = assignee_id if assignee_id h[:labelIds] = label_ids if label_ids.any? h[:priority] = priority if priority end data = post!(ISSUE_CREATE, { input: input }) node = data.dig("issueCreate", "issue") symbolize(node) end |
#create_project(name:, team_ids:) ⇒ Hash
Create a Linear project.
280 281 282 283 284 285 |
# File 'app/services/collavre_linear/client.rb', line 280 def create_project(name:, team_ids:) input = { name: name, teamIds: team_ids } data = post!(PROJECT_CREATE, { input: input }) node = data.dig("projectCreate", "project") symbolize(node) end |
#delete_comment(id) ⇒ Boolean
Delete a Linear comment.
325 326 327 328 |
# File 'app/services/collavre_linear/client.rb', line 325 def delete_comment(id) data = post!(COMMENT_DELETE, { id: id }) data.dig("commentDelete", "success") == true end |
#delete_webhook(id) ⇒ Boolean
Delete (deregister) a webhook from Linear.
143 144 145 146 |
# File 'app/services/collavre_linear/client.rb', line 143 def delete_webhook(id) data = post!(WEBHOOK_DELETE, { id: id }) data.dig("webhookDelete", "success") == true end |
#fetch_issue_state(id) ⇒ Hash?
Fetch a single Linear issue's current workflow state.
239 240 241 242 243 244 245 |
# File 'app/services/collavre_linear/client.rb', line 239 def fetch_issue_state(id) data = post!(ISSUE_QUERY, { id: id }) state = data.dig("issue", "state") return nil if state.nil? state.slice("id", "name", "type") end |
#list_projects ⇒ Array<Hash>
List projects for the link picker, each with the ids of its owning teams so the UI can scope projects to the selected team.
354 355 356 357 358 359 360 361 362 363 364 |
# File 'app/services/collavre_linear/client.rb', line 354 def list_projects data = post!(PROJECTS, {}) nodes = data.dig("projects", "nodes") || [] nodes.map do |n| { id: n["id"], name: n["name"], team_ids: (n.dig("teams", "nodes") || []).map { |t| t["id"] } } end end |
#list_teams ⇒ Array<Hash>
List workspace teams for the link picker.
345 346 347 348 349 |
# File 'app/services/collavre_linear/client.rb', line 345 def list_teams data = post!(TEAMS, {}) nodes = data.dig("teams", "nodes") || [] nodes.map { |n| symbolize(n) } end |
#list_workflow_states ⇒ Array<Hash>
List workflow states for the "done state" picker, each with its owning team id and category type so the UI can scope states to the selected team and default to the completed one.
370 371 372 373 374 375 376 377 378 379 380 381 382 |
# File 'app/services/collavre_linear/client.rb', line 370 def list_workflow_states data = post!(WORKFLOW_STATES, {}) nodes = data.dig("workflowStates", "nodes") || [] nodes.map do |n| { id: n["id"], name: n["name"], type: n["type"], position: n["position"], team_id: n.dig("team", "id") } end end |
#register_webhook(url:, secret:, team_id:, resource_types:) ⇒ Hash
Register a webhook with Linear. WebhookCreateInput fields: url, secret, teamId, resourceTypes
387 388 389 390 391 392 |
# File 'app/services/collavre_linear/client.rb', line 387 def register_webhook(url:, secret:, team_id:, resource_types:) input = { url: url, secret: secret, teamId: team_id, resourceTypes: resource_types } data = post!(WEBHOOK_CREATE, { input: input }) node = data.dig("webhookCreate", "webhook") symbolize(node) end |
#update_comment(id:, body:) ⇒ Hash
Edit an existing Linear comment's body.
317 318 319 320 321 |
# File 'app/services/collavre_linear/client.rb', line 317 def update_comment(id:, body:) data = post!(COMMENT_UPDATE, { id: id, input: { body: body } }) node = data.dig("commentUpdate", "comment") symbolize(node) end |
#update_issue(id, **fields) ⇒ Hash
Update a Linear issue by id.
271 272 273 274 275 276 |
# File 'app/services/collavre_linear/client.rb', line 271 def update_issue(id, **fields) input = camelize_keys(fields) data = post!(ISSUE_UPDATE, { id: id, input: input }) node = data.dig("issueUpdate", "issue") symbolize(node) end |
#update_project(id, **fields) ⇒ Hash
Update a Linear project by id.
291 292 293 294 295 296 |
# File 'app/services/collavre_linear/client.rb', line 291 def update_project(id, **fields) input = camelize_keys(fields) data = post!(PROJECT_UPDATE, { id: id, input: input }) node = data.dig("projectUpdate", "project") symbolize(node) end |
#viewer_and_app_actor ⇒ Hash
Fetch the authenticated viewer identity. app_actor_id is intentionally nil (no Linear query exposes it for the current token); EchoGuard degrades to a no-op — see VIEWER above.
334 335 336 337 338 339 340 341 |
# File 'app/services/collavre_linear/client.rb', line 334 def viewer_and_app_actor data = post!(VIEWER, {}) { user_id: data.dig("viewer", "id"), organization_id: data.dig("viewer", "organization", "id"), app_actor_id: nil } end |