Class: LinearApi::IssueTracker

Inherits:
Object
  • Object
show all
Defined in:
lib/linear_api/issue_tracker.rb

Constant Summary collapse

DEFAULT_LABELS =
%i[bug sync].freeze
AUTO_TRACKED_PROJECT_NAME =
'Auto-Tracked Errors'
AUTO_TRACKED_PROJECT_KEY =
'auto_tracked_errors'
AUTO_TRACKED_LABEL_NAME =
'auto:tracked'
AUTO_TRACKED_LABEL_COLOR =

Gray

'#6b7280'

Class Method Summary collapse

Class Method Details

.setup!Object

Setup auto-tracking infrastructure (project + label)



111
112
113
114
115
116
117
118
119
120
# File 'lib/linear_api/issue_tracker.rb', line 111

def setup!
  # Clear in-memory cache to force fresh lookup
  @auto_tracked_project_id = nil
  @auto_tracked_label_id = nil

  {
    project_id: ensure_auto_tracked_project,
    label_id: ensure_auto_tracked_label
  }
end

.track_error(exception:, context: {}, source: nil, labels: DEFAULT_LABELS) ⇒ LinearApi::Result, LinearApi::SyncedIssue

Track an error and create/update a Linear issue

Parameters:

  • exception (Exception)

    The exception to track

  • context (Hash) (defaults to: {})

    Additional context (date, IDs, etc.)

  • source (Object) (defaults to: nil)

    The source object (AccountLinking, etc.)

  • labels (Array<Symbol, String>) (defaults to: DEFAULT_LABELS)

    Labels to apply (:bug, :sync, :critical, or IDs)

Returns:



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/linear_api/issue_tracker.rb', line 19

def track_error(exception:, context: {}, source: nil, labels: DEFAULT_LABELS)
  fingerprint = generate_fingerprint(exception, source)

  # Check for existing open issue
  existing = SyncedIssue.find_open_for_fingerprint(fingerprint)
  return add_occurrence_comment(existing, exception, context) if existing

  # Ensure auto-tracked project and label exist (uses in-memory cache after first call)
  project_id = ensure_auto_tracked_project
  auto_label_id = ensure_auto_tracked_label

  # Resolve label IDs from cache + add auto:tracked label
  label_ids = resolve_label_ids(labels)
  label_ids << auto_label_id if auto_label_id

  # Create new Linear issue
  result = LinearApi.client.create_issue(
    title: build_title(exception),
    description: build_description(exception, context, source),
    priority: determine_priority(exception, context),
    label_ids: label_ids.compact.uniq,
    project_id: project_id
  )

  return result unless result.success?

  # Track locally
  synced_issue = SyncedIssue.create!(
    linear_id: result.data.id,
    identifier: result.data.identifier,
    title: result.data.title,
    state_name: 'Backlog',
    priority: determine_priority(exception, context),
    fingerprint: fingerprint,
    source_type: source&.class&.name,
    source_id: source&.id&.to_s,
    metadata: context.to_json,
    synced_at: Time.current
  )

  Result.new(success: true, data: synced_issue)
rescue StandardError => e
  # Never let error tracking failures propagate - log and return failure
  LinearApi.logger.error { "LinearApi::IssueTracker: failed to track error: #{e.class} - #{e.message}" }
  Result.new(success: false, error: "Error tracking failed: #{e.message}")
end

.track_issue(title:, description:, context: {}, source: nil, labels: [], priority: 3) ⇒ Object

Track a custom issue (not from exception)



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/linear_api/issue_tracker.rb', line 67

def track_issue(title:, description:, context: {}, source: nil, labels: [], priority: 3)
  fingerprint = generate_custom_fingerprint(title, source)

  existing = SyncedIssue.find_open_for_fingerprint(fingerprint)
  return Result.new(success: true, data: existing) if existing

  # Ensure auto-tracked project and label exist
  project_id = ensure_auto_tracked_project
  auto_label_id = ensure_auto_tracked_label

  # Resolve label IDs + add auto:tracked label
  label_ids = resolve_label_ids(labels)
  label_ids << auto_label_id if auto_label_id

  result = LinearApi.client.create_issue(
    title: "[Auto] #{title}",
    description: description,
    priority: priority,
    label_ids: label_ids.compact.uniq,
    project_id: project_id
  )

  return result unless result.success?

  synced_issue = SyncedIssue.create!(
    linear_id: result.data.id,
    identifier: result.data.identifier,
    title: result.data.title,
    state_name: 'Backlog',
    priority: priority,
    fingerprint: fingerprint,
    source_type: source&.class&.name,
    source_id: source&.id&.to_s,
    metadata: context.to_json,
    synced_at: Time.current
  )

  Result.new(success: true, data: synced_issue)
rescue StandardError => e
  LinearApi.logger.error { "LinearApi::IssueTracker: failed to track issue: #{e.class} - #{e.message}" }
  Result.new(success: false, error: "Issue tracking failed: #{e.message}")
end