Module: CollavreLinear::FieldMapper

Defined in:
app/services/collavre_linear/field_mapper.rb

Overview

Pure field translation between Collavre Creative attributes and Linear issue attributes. NO I/O — this class never reads from the DB or makes HTTP calls.

priority <-> sequence mapping (locked product decision)

Linear priority is a 5-value enum:

0 = No priority, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low

Collavre sequence is a dense, ZERO-BASED total order of siblings (closure_tree): core resequencing (CreateService#resequence, Reorderer#resequence!) assigns each_with_index, so the first sibling is sequence 0. The mapping must honor that base — otherwise the first (top-ranked) sibling would export as "None".

Inbound (Linear → Collavre): sequence = (priority == 0 ? 4 : priority - 1) Urgent(1) → 0 (first), Low(4) → 3, None(0) → 4 (after all ranked buckets).

Outbound (Collavre → Linear): sequence 0-3 maps to priority 1-4 (0→Urgent); sequence 4 (the "None" sentinel) and nil/unranked → priority 0 (None).

Lossy edge: Linear priority is a 5-bucket enum; Collavre sequence is a dense integer total order. Within-bucket ordering is NOT representable in Linear priority — only the bucket (1-4) is preserved. The Task 10 applier must write the computed sequence via closure_tree's reorder path, not a raw column update, because closure_tree maintains sibling order invariants.

Class Method Summary collapse

Class Method Details

.creative_to_issue_attrs(creative) ⇒ Hash

Outbound: Creative → Linear issue attributes.

Parameters:

  • creative (#title, #description, #sequence, #data)

    — a Creative (or compatible stub). Must NOT expose #progress; the mapper never reads it.

Returns:

  • (Hash)

    with keys: :title, :description, :priority, and optionally :state_id and :label_ids (omitted when not set).



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'app/services/collavre_linear/field_mapper.rb', line 37

def creative_to_issue_attrs(creative)
  attrs = {
    title:       creative.title,
    description: creative.description,
    priority:    sequence_to_priority(creative.sequence)
  }

  linear_data = (creative.data || {})["linear"] || {}

  if (state = linear_data["state"])
    attrs[:state_id] = state["id"]
  end

  if (labels = linear_data["labels"]) && labels.any?
    attrs[:label_ids] = labels.map { |l| l["id"] }
  end

  attrs
end

.issue_to_creative_attrs(issue_payload) ⇒ Hash

Inbound: Linear issue payload → Collavre creative attributes.

Parameters:

  • issue_payload (Hash)

    — a raw Linear issue hash (string keys), e.g. from a webhook or API response. Expected keys: "title", "description", "priority", "state", "labels" (=> [...]), "assignee".

Returns:

  • (Hash)

    with keys: :title, :description, :sequence, :data_linear (labels:, assignee:). Never contains :progress.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/services/collavre_linear/field_mapper.rb', line 65

def issue_to_creative_attrs(issue_payload)
  priority = issue_payload["priority"].to_i

  {
    title:       issue_payload["title"],
    description: issue_payload["description"],
    sequence:    priority_to_sequence(priority),
    data_linear: {
      state:    issue_payload["state"],
      labels:   normalize_labels(issue_payload["labels"]),
      assignee: issue_payload["assignee"]
    }
  }
end