Skip to content

Commit d812d7d

Browse files
authored
feat(gh-copilot): add support for organization daily user metrics (#8747)
1 parent 628e358 commit d812d7d

5 files changed

Lines changed: 80 additions & 28 deletions

File tree

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
"github.com/apache/incubator-devlake/core/context"
22+
"github.com/apache/incubator-devlake/core/errors"
23+
"github.com/apache/incubator-devlake/core/plugin"
24+
)
25+
26+
var _ plugin.MigrationScript = (*addOrganizationIdToUserMetrics)(nil)
27+
28+
type addOrganizationIdToUserMetrics struct{}
29+
30+
func (script *addOrganizationIdToUserMetrics) Up(basicRes context.BasicRes) errors.Error {
31+
db := basicRes.GetDal()
32+
return db.AutoMigrate(&userDailyMetrics20260303{})
33+
}
34+
35+
func (*addOrganizationIdToUserMetrics) Version() uint64 {
36+
return 20260303000000
37+
}
38+
39+
func (*addOrganizationIdToUserMetrics) Name() string {
40+
return "add organization_id to user daily metrics"
41+
}
42+
43+
type userDailyMetrics20260303 struct {
44+
OrganizationId string `gorm:"type:varchar(100)"`
45+
}
46+
47+
func (userDailyMetrics20260303) TableName() string {
48+
return "_tool_copilot_user_daily_metrics"
49+
}

backend/plugins/gh-copilot/models/migrationscripts/register.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,6 @@ func All() []plugin.MigrationScript {
2929
new(addScopeConfig20260121),
3030
new(migrateToUsageMetricsV2),
3131
new(addPRFieldsToEnterpriseMetrics),
32+
new(addOrganizationIdToUserMetrics),
3233
}
3334
}

backend/plugins/gh-copilot/models/user_metrics.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ type GhCopilotUserDailyMetrics struct {
3030
Day time.Time `gorm:"primaryKey;type:date" json:"day"`
3131
UserId int64 `gorm:"primaryKey" json:"userId"`
3232

33-
EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
34-
UserLogin string `json:"userLogin" gorm:"type:varchar(255);index"`
35-
UsedAgent bool `json:"usedAgent"`
36-
UsedChat bool `json:"usedChat"`
33+
OrganizationId string `json:"organizationId" gorm:"type:varchar(100)"`
34+
EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
35+
UserLogin string `json:"userLogin" gorm:"type:varchar(255);index"`
36+
UsedAgent bool `json:"usedAgent"`
37+
UsedChat bool `json:"usedChat"`
3738

3839
CopilotActivityMetrics `mapstructure:",squash"`
3940
common.NoPKModel

backend/plugins/gh-copilot/tasks/user_metrics_collector.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ import (
3232

3333
const rawUserMetricsTable = "copilot_user_metrics"
3434

35-
// CollectUserMetrics collects enterprise user-level daily Copilot usage reports.
35+
// CollectUserMetrics collects user-level daily Copilot usage reports.
3636
// These reports are in JSONL format (one JSON object per line per user).
37-
// Only available for enterprise-scoped connections.
37+
// Utilizes the enterprise or organization endpoints depending on connection configuration
3838
func CollectUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
3939
data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
4040
if !ok {
@@ -43,16 +43,21 @@ func CollectUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
4343
connection := data.Connection
4444
connection.Normalize()
4545

46-
if !connection.HasEnterprise() {
47-
taskCtx.GetLogger().Info("No enterprise configured, skipping user metrics collection")
48-
return nil
49-
}
50-
5146
apiClient, err := CreateApiClient(taskCtx.TaskContext(), connection)
5247
if err != nil {
5348
return err
5449
}
5550

51+
var urlTemplate string
52+
53+
if connection.HasEnterprise() {
54+
urlTemplate = fmt.Sprintf("enterprises/%s/copilot/metrics/reports/users-1-day", connection.Enterprise)
55+
} else if connection.Organization != "" {
56+
urlTemplate = fmt.Sprintf("orgs/%s/copilot/metrics/reports/users-1-day", connection.Organization)
57+
} else {
58+
return nil
59+
}
60+
5661
rawArgs := helper.RawDataSubTaskArgs{
5762
Ctx: taskCtx,
5863
Table: rawUserMetricsTable,
@@ -76,10 +81,9 @@ func CollectUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
7681
dayIter := newDayIterator(start, until)
7782

7883
err = collector.InitCollector(helper.ApiCollectorArgs{
79-
ApiClient: apiClient,
80-
Input: dayIter,
81-
UrlTemplate: fmt.Sprintf("enterprises/%s/copilot/metrics/reports/users-1-day",
82-
connection.Enterprise),
84+
ApiClient: apiClient,
85+
Input: dayIter,
86+
UrlTemplate: urlTemplate,
8387
Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
8488
input := reqData.Input.(*dayInput)
8589
q := url.Values{}

backend/plugins/gh-copilot/tasks/user_metrics_extractor.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type userDailyReport struct {
3333
ReportStartDay string `json:"report_start_day"`
3434
ReportEndDay string `json:"report_end_day"`
3535
Day string `json:"day"`
36+
OrganizationId string `json:"organization_id"`
3637
EnterpriseId string `json:"enterprise_id"`
3738
UserId int64 `json:"user_id"`
3839
UserLogin string `json:"user_login"`
@@ -78,11 +79,6 @@ func ExtractUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
7879
connection := data.Connection
7980
connection.Normalize()
8081

81-
if !connection.HasEnterprise() {
82-
taskCtx.GetLogger().Info("No enterprise configured, skipping user metrics extraction")
83-
return nil
84-
}
85-
8682
params := copilotRawParams{
8783
ConnectionId: data.Options.ConnectionId,
8884
ScopeId: data.Options.ScopeId,
@@ -111,14 +107,15 @@ func ExtractUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
111107

112108
// Main user daily metrics
113109
results = append(results, &models.GhCopilotUserDailyMetrics{
114-
ConnectionId: data.Options.ConnectionId,
115-
ScopeId: data.Options.ScopeId,
116-
Day: day,
117-
UserId: u.UserId,
118-
EnterpriseId: u.EnterpriseId,
119-
UserLogin: u.UserLogin,
120-
UsedAgent: u.UsedAgent,
121-
UsedChat: u.UsedChat,
110+
ConnectionId: data.Options.ConnectionId,
111+
ScopeId: data.Options.ScopeId,
112+
Day: day,
113+
UserId: u.UserId,
114+
OrganizationId: u.OrganizationId,
115+
EnterpriseId: u.EnterpriseId,
116+
UserLogin: u.UserLogin,
117+
UsedAgent: u.UsedAgent,
118+
UsedChat: u.UsedChat,
122119
CopilotActivityMetrics: models.CopilotActivityMetrics{
123120
UserInitiatedInteractionCount: u.UserInitiatedInteractionCount,
124121
CodeGenerationActivityCount: u.CodeGenerationActivityCount,

0 commit comments

Comments
 (0)