Class: CollavreLinear::Client

Inherits:
Object
  • Object
show all
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

Instance Method Summary collapse

Constructor Details

#initialize(account) ⇒ Client

Returns a new instance of Client.



194
195
196
197
# File 'app/services/collavre_linear/client.rb', line 194

def initialize()
  @account = 
  @endpoint = resolve_endpoint
end

Instance Method Details

#archive_issue(id) ⇒ Boolean

Archive a Linear issue (soft-delete).

Parameters:

  • id (String)

    Linear issue UUID

Returns:

  • (Boolean)

    success



253
254
255
256
# File 'app/services/collavre_linear/client.rb', line 253

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.

Returns:

  • (Hash)

    with :id



260
261
262
263
264
265
# File 'app/services/collavre_linear/client.rb', line 260

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.

Returns:

  • (Hash)

    with :id and :identifier



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'app/services/collavre_linear/client.rb', line 201

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.

Returns:

  • (Hash)

    with :id



232
233
234
235
236
237
# File 'app/services/collavre_linear/client.rb', line 232

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.

Returns:

  • (Boolean)

    success



277
278
279
280
# File 'app/services/collavre_linear/client.rb', line 277

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.

Parameters:

  • id (String)

    Linear webhook UUID

Returns:

  • (Boolean)

    success



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

#list_projectsArray<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.

Returns:

  • (Array<Hash>)

    each with :id, :name, :team_ids



306
307
308
309
310
311
312
313
314
315
316
# File 'app/services/collavre_linear/client.rb', line 306

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_teamsArray<Hash>

List workspace teams for the link picker.

Returns:

  • (Array<Hash>)

    each with :id, :name, :key



297
298
299
300
301
# File 'app/services/collavre_linear/client.rb', line 297

def list_teams
  data  = post!(TEAMS, {})
  nodes = data.dig("teams", "nodes") || []
  nodes.map { |n| symbolize(n) }
end

#register_webhook(url:, secret:, team_id:, resource_types:) ⇒ Hash

Register a webhook with Linear. WebhookCreateInput fields: url, secret, teamId, resourceTypes

Returns:

  • (Hash)

    with :id



321
322
323
324
325
326
# File 'app/services/collavre_linear/client.rb', line 321

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.

Returns:

  • (Hash)

    with :id



269
270
271
272
273
# File 'app/services/collavre_linear/client.rb', line 269

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.

Parameters:

  • id (String)

    Linear issue UUID

  • fields (Hash)

    keyword args mapping to IssueUpdateInput fields

Returns:

  • (Hash)

    with :id and :identifier



223
224
225
226
227
228
# File 'app/services/collavre_linear/client.rb', line 223

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.

Parameters:

  • id (String)

    Linear project UUID

  • fields (Hash)

    keyword args mapping to ProjectUpdateInput fields

Returns:

  • (Hash)

    with :id



243
244
245
246
247
248
# File 'app/services/collavre_linear/client.rb', line 243

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_actorHash

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.

Returns:

  • (Hash)

    with :user_id, :app_actor_id, :organization_id



286
287
288
289
290
291
292
293
# File 'app/services/collavre_linear/client.rb', line 286

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