Linear model

Cycle progress

Scope and completion per cycle: the numbers behind Linear's cycle graph.

Grain
team x cycle
Built from
  1. 1
    Raw tables
  2. 1
    Staging
  3. 1
    Core
  4. 1
    Analytics
Reads from Linear
issues
Ships as
Weld and dbt SQL, open source on GitHub
core.linear.cycle_progress
1-- core.linear.cycle_progress
2-- Scope and completion per cycle: the numbers behind Linear's cycle graph.
3-- Grain: team x cycle.
4--
5-- Built from each issue's CURRENT cycle. Issues that were in a cycle and rolled
6-- over to the next one count in the cycle they are in now, so a finished cycle's
7-- scope here is what remained in it, not what was planned at the start. Linear's
8-- own cycle graph uses its history and will show a larger starting scope.
9-- Cycle start and end dates are not synced; the first and last activity dates
10-- below are a stand-in.
11
12SELECT
13    i.team_id,
14    ANY_VALUE(i.team_name)                                            AS team_name,
15    i.cycle_id,
16    ANY_VALUE(i.cycle_number)                                         AS cycle_number,
17    ANY_VALUE(i.cycle_name)                                           AS cycle_name,
18    COUNT(*)                                                          AS scope_issues,
19    COUNTIF(i.state_type = 'completed')                               AS completed_issues,
20    COUNTIF(i.state_type = 'canceled')                                AS canceled_issues,
21    COUNTIF(i.state_type = 'started')                                 AS in_progress_issues,
22    COUNTIF(i.state_type IN ('triage', 'backlog', 'unstarted'))       AS not_started_issues,
23    SUM(COALESCE(i.estimate, 0))                                      AS scope_points,
24    SUM(IF(i.state_type = 'completed', COALESCE(i.estimate, 0), 0))   AS completed_points,
25    SAFE_DIVIDE(COUNTIF(i.state_type = 'completed'),
26                COUNTIF(i.state_type != 'canceled'))                  AS pct_issues_completed,
27    MIN(DATE(i.added_to_cycle_at))                                    AS first_added_date,
28    MAX(DATE(i.completed_at))                                         AS last_completed_date
29FROM {{staging.linear.issue}} i
30WHERE i.cycle_id IS NOT NULL
31GROUP BY i.team_id, i.cycle_id

This model and the 2 it depends on. Deploy them together; refs like {{staging.…}} resolve to the matching folders in Weld, and the dbt view uses source() and ref().

Notes

How it counts. And where it stops.

Built from each issue's CURRENT cycle. Issues that were in a cycle and rolled over to the next one count in the cycle they are in now, so a finished cycle's scope here is what remained in it, not what was planned at the start. Linear's own cycle graph uses its history and will show a larger starting scope. Cycle start and end dates are not synced; the first and last activity dates below are a stand-in.

Raw tables it reads

  • raw.linear.issues

Put Linear in front
of your agents.

Connect Linear, deploy the models, and ask questions in Claude, Cursor or ChatGPT. Free for 14 days, no credit card.