Skip to content

Commit 634128b

Browse files
committed
feat(cursor): add daily usage line metrics
* Store accepted, total line, and apply counts from daily usage API with migration. * Add Grafana panels and fix spend reconciliation to include included spend. Signed-off-by: Joshua Smith <jbsmith7741@gmail.com>
1 parent 594d385 commit 634128b

7 files changed

Lines changed: 179 additions & 12 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
id,params,data,url,input,created_at
2-
1,"{""ConnectionId"":1,""ScopeId"":""team"",""Endpoint"":""https://api.cursor.com""}","{""day"":""2026-07-08"",""userId"":""user_example123"",""email"":""user@example.com"",""isActive"":true,""completions"":15,""premiumRequests"":3,""agentRequests"":5,""chatRequests"":8,""composerRequests"":2,""totalTabsAccepted"":42,""totalTabsShown"":60,""usageBasedReqs"":7,""subscriptionIncludedReqs"":6,""mostUsedModel"":""composer-2.5-fast"",""clientVersion"":""0.50.3"",""linesAdded"":120,""linesDeleted"":30}",https://api.cursor.com/teams/daily-usage-data,null,2026-07-09 17:14:19.000
2+
1,"{""ConnectionId"":1,""ScopeId"":""team"",""Endpoint"":""https://api.cursor.com""}","{""day"":""2026-07-08"",""userId"":""user_example123"",""email"":""user@example.com"",""isActive"":true,""completions"":15,""premiumRequests"":3,""agentRequests"":5,""chatRequests"":8,""composerRequests"":2,""totalTabsAccepted"":42,""totalTabsShown"":60,""usageBasedReqs"":7,""subscriptionIncludedReqs"":6,""mostUsedModel"":""composer-2.5-fast"",""clientVersion"":""0.50.3"",""totalLinesAdded"":150,""totalLinesDeleted"":35,""acceptedLinesAdded"":120,""acceptedLinesDeleted"":30,""totalApplies"":10,""totalAccepts"":8,""totalRejects"":2}",https://api.cursor.com/teams/daily-usage-data,null,2026-07-09 17:14:19.000
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
connection_id,scope_id,user_id,usage_date,email,is_active,completions,premium_requests,agent_requests,chat_requests,composer_requests,tabs_accepted,tabs_shown,usage_based_reqs,subscription_included_reqs,most_used_model,client_version,lines_added,lines_deleted
2-
1,team,user_example123,2026-07-08T00:00:00.000+00:00,user@example.com,1,15,3,5,8,2,42,60,7,6,composer-2.5-fast,0.50.3,120,30
1+
connection_id,scope_id,user_id,usage_date,email,is_active,completions,premium_requests,agent_requests,chat_requests,composer_requests,tabs_accepted,tabs_shown,usage_based_reqs,subscription_included_reqs,most_used_model,client_version,accepted_lines_added,accepted_lines_deleted,total_lines_added,total_lines_deleted,total_applies,total_accepts,total_rejects,lines_added,lines_deleted
2+
1,team,user_example123,2026-07-08T00:00:00.000+00:00,user@example.com,1,15,3,5,8,2,42,60,7,6,composer-2.5-fast,0.50.3,120,30,150,35,10,8,2,120,30

backend/plugins/cursor/models/daily_usage.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
)
2525

2626
// CursorDailyUsage stores per-user per-day adoption metrics from POST /teams/daily-usage-data.
27+
// Line fields map from acceptedLinesAdded/totalLinesAdded (etc.); LinesAdded/LinesDeleted
28+
// duplicate accepted_lines_added/accepted_lines_deleted for backward compatibility.
2729
type CursorDailyUsage struct {
2830
ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
2931
ScopeId string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
@@ -43,6 +45,13 @@ type CursorDailyUsage struct {
4345
SubscriptionIncludedReqs int `json:"subscriptionIncludedReqs"`
4446
MostUsedModel string `json:"mostUsedModel" gorm:"type:varchar(255)"`
4547
ClientVersion string `json:"clientVersion" gorm:"type:varchar(100)"`
48+
AcceptedLinesAdded int `json:"acceptedLinesAdded"`
49+
AcceptedLinesDeleted int `json:"acceptedLinesDeleted"`
50+
TotalLinesAdded int `json:"totalLinesAdded"`
51+
TotalLinesDeleted int `json:"totalLinesDeleted"`
52+
TotalApplies int `json:"totalApplies"`
53+
TotalAccepts int `json:"totalAccepts"`
54+
TotalRejects int `json:"totalRejects"`
4655
LinesAdded int `json:"linesAdded"`
4756
LinesDeleted int `json:"linesDeleted"`
4857

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one or more
3+
contributor license agreements. See the NOTICE file distributed with
4+
this work for additional information regarding copyright ownership.
5+
The ASF licenses this file to You under the Apache License, Version 2.0
6+
(the "License"); you may not use this file except in compliance with
7+
the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
*/
17+
18+
package migrationscripts
19+
20+
import (
21+
"time"
22+
23+
"github.com/apache/incubator-devlake/core/context"
24+
"github.com/apache/incubator-devlake/core/errors"
25+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
26+
"github.com/apache/incubator-devlake/helpers/migrationhelper"
27+
)
28+
29+
type addCursorDailyUsageLineFields struct{}
30+
31+
type cursorDailyUsage20260716 struct {
32+
ConnectionId uint64 `gorm:"primaryKey"`
33+
ScopeId string `gorm:"primaryKey;type:varchar(255)"`
34+
UserId string `gorm:"primaryKey;type:varchar(255)"`
35+
UsageDate time.Time `gorm:"primaryKey"`
36+
Email string `gorm:"type:varchar(255);index"`
37+
IsActive bool
38+
Completions int
39+
PremiumRequests int
40+
AgentRequests int
41+
ChatRequests int
42+
ComposerRequests int
43+
TabsAccepted int
44+
TabsShown int
45+
UsageBasedReqs int
46+
SubscriptionIncludedReqs int
47+
MostUsedModel string `gorm:"type:varchar(255)"`
48+
ClientVersion string `gorm:"type:varchar(100)"`
49+
AcceptedLinesAdded int
50+
AcceptedLinesDeleted int
51+
TotalLinesAdded int
52+
TotalLinesDeleted int
53+
TotalApplies int
54+
TotalAccepts int
55+
TotalRejects int
56+
LinesAdded int
57+
LinesDeleted int
58+
archived.NoPKModel
59+
}
60+
61+
func (cursorDailyUsage20260716) TableName() string { return "_tool_cursor_daily_usage" }
62+
63+
func (script *addCursorDailyUsageLineFields) Up(basicRes context.BasicRes) errors.Error {
64+
return migrationhelper.AutoMigrateTables(basicRes, &cursorDailyUsage20260716{})
65+
}
66+
67+
func (*addCursorDailyUsageLineFields) Version() uint64 { return 20260716120000 }
68+
69+
func (*addCursorDailyUsageLineFields) Name() string {
70+
return "cursor add daily usage line and apply fields"
71+
}

backend/plugins/cursor/models/migrationscripts/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ func All() []plugin.MigrationScript {
2525
new(addCursorInitialTables),
2626
new(changeCursorRequestsCostsToFloat),
2727
new(addCursorDailyUsage),
28+
new(addCursorDailyUsageLineFields),
2829
}
2930
}

backend/plugins/cursor/tasks/daily_usage_extractor.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@ type dailyUsageRecord struct {
4444
SubscriptionIncludedReqs int `json:"subscriptionIncludedReqs"`
4545
MostUsedModel string `json:"mostUsedModel"`
4646
ClientVersion string `json:"clientVersion"`
47-
LinesAdded int `json:"linesAdded"`
48-
LinesDeleted int `json:"linesDeleted"`
47+
TotalLinesAdded int `json:"totalLinesAdded"`
48+
TotalLinesDeleted int `json:"totalLinesDeleted"`
49+
AcceptedLinesAdded int `json:"acceptedLinesAdded"`
50+
AcceptedLinesDeleted int `json:"acceptedLinesDeleted"`
51+
TotalApplies int `json:"totalApplies"`
52+
TotalAccepts int `json:"totalAccepts"`
53+
TotalRejects int `json:"totalRejects"`
4954
}
5055

5156
// ExtractDailyUsage parses raw daily usage records into tool-layer tables.
@@ -98,8 +103,15 @@ func ExtractDailyUsage(taskCtx plugin.SubTaskContext) errors.Error {
98103
SubscriptionIncludedReqs: record.SubscriptionIncludedReqs,
99104
MostUsedModel: strings.TrimSpace(record.MostUsedModel),
100105
ClientVersion: strings.TrimSpace(record.ClientVersion),
101-
LinesAdded: record.LinesAdded,
102-
LinesDeleted: record.LinesDeleted,
106+
AcceptedLinesAdded: record.AcceptedLinesAdded,
107+
AcceptedLinesDeleted: record.AcceptedLinesDeleted,
108+
TotalLinesAdded: record.TotalLinesAdded,
109+
TotalLinesDeleted: record.TotalLinesDeleted,
110+
TotalApplies: record.TotalApplies,
111+
TotalAccepts: record.TotalAccepts,
112+
TotalRejects: record.TotalRejects,
113+
LinesAdded: record.AcceptedLinesAdded,
114+
LinesDeleted: record.AcceptedLinesDeleted,
103115
}
104116
return []interface{}{usage}, nil
105117
},

grafana/dashboards/mysql/cursor-usage.json

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@
233233
},
234234
{
235235
"datasource": "mysql",
236-
"description": "Difference between /teams/spend total and sum of chargedCents for events since billing_cycle_start. Positive means spend exceeds collected events (backfill may be incomplete).",
236+
"description": "Difference between total cycle spend (on-demand + included) from /teams/spend and sum of chargedCents for events since billing_cycle_start. Positive means spend exceeds collected events (backfill may be incomplete).",
237237
"fieldConfig": {
238238
"defaults": {
239239
"decimals": 2,
@@ -259,7 +259,7 @@
259259
"datasource": "mysql",
260260
"format": "table",
261261
"rawQuery": true,
262-
"rawSql": "SELECT (\n COALESCE((SELECT SUM(spend_cents) FROM _tool_cursor_user_spend\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'), 0)\n - COALESCE((SELECT SUM(charged_cents) FROM _tool_cursor_usage_events\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'\n AND event_time >= (SELECT MIN(billing_cycle_start) FROM _tool_cursor_user_spend\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}')), 0)\n) / 100 AS \"Reconciliation Delta\"",
262+
"rawSql": "SELECT (\n COALESCE((SELECT SUM(spend_cents + included_spend_cents) FROM _tool_cursor_user_spend\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'), 0)\n - COALESCE((SELECT SUM(charged_cents) FROM _tool_cursor_usage_events\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}'\n AND event_time >= (SELECT MIN(billing_cycle_start) FROM _tool_cursor_user_spend\n WHERE connection_id = ${connection_id} AND scope_id = '${scope_id}')), 0)\n) / 100 AS \"Reconciliation Delta\"",
263263
"refId": "A"
264264
}
265265
],
@@ -564,7 +564,7 @@
564564
},
565565
{
566566
"datasource": "mysql",
567-
"description": "Tab completion acceptance rate: accepted / shown",
567+
"description": "Tab inline-completion acceptance rate only (tabs_accepted / tabs_shown); see AI Line Acceptance Rate for all accepted lines",
568568
"fieldConfig": {
569569
"defaults": {
570570
"color": { "fixedColor": "blue", "mode": "fixed" },
@@ -636,9 +636,83 @@
636636
"title": "Feature Adoption (Weekly)",
637637
"type": "timeseries"
638638
},
639+
{
640+
"datasource": "mysql",
641+
"description": "Daily sum of AI-suggested lines accepted into the editor (acceptedLinesAdded from /teams/daily-usage-data)",
642+
"fieldConfig": {
643+
"defaults": {
644+
"color": { "fixedColor": "blue", "mode": "fixed" },
645+
"custom": {
646+
"drawStyle": "line",
647+
"fillOpacity": 20,
648+
"lineWidth": 2,
649+
"showPoints": "auto",
650+
"pointSize": 4
651+
},
652+
"unit": "locale",
653+
"decimals": 0
654+
},
655+
"overrides": []
656+
},
657+
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 36 },
658+
"id": 204,
659+
"options": {
660+
"legend": { "displayMode": "list", "placement": "bottom", "showLegend": true },
661+
"tooltip": { "mode": "single", "sort": "none" }
662+
},
663+
"pluginVersion": "11.0.0",
664+
"targets": [
665+
{
666+
"datasource": "mysql",
667+
"format": "table",
668+
"rawQuery": true,
669+
"rawSql": "SELECT usage_date AS time,\n SUM(accepted_lines_added) AS \"Accepted Lines Added\"\nFROM _tool_cursor_daily_usage\nWHERE $__timeFilter(usage_date)\n AND connection_id = ${connection_id}\n AND scope_id = '${scope_id}'\n AND is_active = 1\nGROUP BY usage_date\nORDER BY 1",
670+
"refId": "A"
671+
}
672+
],
673+
"title": "Accepted Lines Added",
674+
"type": "timeseries"
675+
},
676+
{
677+
"datasource": "mysql",
678+
"description": "Share of all lines added that came from accepted AI suggestions (accepted_lines_added / total_lines_added)",
679+
"fieldConfig": {
680+
"defaults": {
681+
"color": { "fixedColor": "blue", "mode": "fixed" },
682+
"custom": {
683+
"drawStyle": "line",
684+
"fillOpacity": 10,
685+
"lineWidth": 2,
686+
"showPoints": "auto",
687+
"pointSize": 4
688+
},
689+
"unit": "percent",
690+
"decimals": 1
691+
},
692+
"overrides": []
693+
},
694+
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 36 },
695+
"id": 205,
696+
"options": {
697+
"legend": { "displayMode": "list", "placement": "bottom", "showLegend": true },
698+
"tooltip": { "mode": "single", "sort": "none" }
699+
},
700+
"pluginVersion": "11.0.0",
701+
"targets": [
702+
{
703+
"datasource": "mysql",
704+
"format": "table",
705+
"rawQuery": true,
706+
"rawSql": "SELECT usage_date AS time,\n ROUND(SUM(accepted_lines_added) * 100.0 / NULLIF(SUM(total_lines_added), 0), 1) AS \"AI Line Acceptance Rate\"\nFROM _tool_cursor_daily_usage\nWHERE $__timeFilter(usage_date)\n AND connection_id = ${connection_id}\n AND scope_id = '${scope_id}'\n AND is_active = 1\nGROUP BY usage_date\nORDER BY 1",
707+
"refId": "A"
708+
}
709+
],
710+
"title": "AI Line Acceptance Rate",
711+
"type": "timeseries"
712+
},
639713
{
640714
"collapsed": true,
641-
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 36 },
715+
"gridPos": { "h": 1, "w": 24, "x": 0, "y": 44 },
642716
"id": 104,
643717
"title": "Diagnostics",
644718
"type": "row",
@@ -647,7 +721,7 @@
647721
"datasource": "mysql",
648722
"description": "Row counts and latest timestamps per _tool_cursor_* table for pipeline health monitoring",
649723
"fieldConfig": { "defaults": {}, "overrides": [] },
650-
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 45 },
724+
"gridPos": { "h": 6, "w": 12, "x": 0, "y": 53 },
651725
"id": 40,
652726
"options": {
653727
"cellHeight": "sm",

0 commit comments

Comments
 (0)