Class: LinearApi::SyncedIssue

Inherits:
ApplicationRecord show all
Defined in:
app/models/linear_api/synced_issue.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.find_open_for_fingerprint(fingerprint) ⇒ Object

Find existing open issue for this fingerprint



62
63
64
# File 'app/models/linear_api/synced_issue.rb', line 62

def find_open_for_fingerprint(fingerprint)
  by_fingerprint(fingerprint).unresolved.order(created_at: :desc).first
end

.refresh_stale_issues!Object

Refresh all stale issues using batch API call (avoids N+1)



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
# File 'app/models/linear_api/synced_issue.rb', line 72

def refresh_stale_issues!
  stale_issues = stale.to_a
  return if stale_issues.empty?

  LinearApi.logger.info { "LinearApi: refreshing #{stale_issues.size} stale issues" }

  # Batch fetch from Linear API in groups of 50
  stale_issues.each_slice(50) do |batch|
    ids = batch.map(&:linear_id)
    result = LinearApi.client.batch_get_issues(ids: ids)

    unless result.success?
      LinearApi.logger.error { "LinearApi: batch refresh failed: #{result.error}" }
      # Fall back to individual refresh for this batch
      batch.each(&:refresh_from_linear!)
      next
    end

    # Index fetched issues by ID for O(1) lookup
    fetched = result.data.index_by(&:id)

    batch.each do |synced|
      linear_issue = fetched[synced.linear_id]
      if linear_issue
        synced.update!(
          state_name: linear_issue.state_name,
          title: linear_issue.title,
          priority: linear_issue.priority,
          synced_at: Time.current
        )
      else
        LinearApi.logger.warn { "LinearApi: issue #{synced.identifier} not found in Linear (may be deleted)" }
      end
    end
  end
end

.should_create_new?(fingerprint) ⇒ Boolean

Check if we should create a new issue or add to existing

Returns:

  • (Boolean)


67
68
69
# File 'app/models/linear_api/synced_issue.rb', line 67

def should_create_new?(fingerprint)
  find_open_for_fingerprint(fingerprint).nil?
end

Instance Method Details

#linear_urlObject

Build Linear URL using configurable workspace slug



46
47
48
49
# File 'app/models/linear_api/synced_issue.rb', line 46

def linear_url
  slug = LinearApi.workspace_slug || 'theownerstack'
  "https://linear.app/#{slug}/issue/#{identifier}"
end

#open?Boolean

Check if this issue is still open in Linear

Returns:

  • (Boolean)


23
24
25
# File 'app/models/linear_api/synced_issue.rb', line 23

def open?
  !%w[Done Canceled Duplicate].include?(state_name)
end

#parsed_metadataObject

Parse metadata JSON



52
53
54
55
56
57
58
# File 'app/models/linear_api/synced_issue.rb', line 52

def 
  return {} unless 

  .is_a?(String) ? JSON.parse() : 
rescue JSON::ParserError
  {}
end

#refresh_from_linear!Object

Sync state from Linear



32
33
34
35
36
37
38
39
40
41
42
43
# File 'app/models/linear_api/synced_issue.rb', line 32

def refresh_from_linear!
  result = LinearApi.client.get_issue_by_id(linear_id)
  return false unless result.success?

  update!(
    state_name: result.data.state_name,
    title: result.data.title,
    priority: result.data.priority,
    synced_at: Time.current
  )
  true
end

#resolved?Boolean

Returns:

  • (Boolean)


27
28
29
# File 'app/models/linear_api/synced_issue.rb', line 27

def resolved?
  state_name == 'Done'
end