This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new d812d7d7f feat(gh-copilot): add support for organization daily user
metrics (#8747)
d812d7d7f is described below
commit d812d7d7fde3cd1c824ed51a4be73abe4f9075f8
Author: Reece Ward <[email protected]>
AuthorDate: Tue Mar 10 05:06:40 2026 +0000
feat(gh-copilot): add support for organization daily user metrics (#8747)
---
...20260303_add_organization_id_to_user_metrics.go | 49 ++++++++++++++++++++++
.../gh-copilot/models/migrationscripts/register.go | 1 +
backend/plugins/gh-copilot/models/user_metrics.go | 9 ++--
.../gh-copilot/tasks/user_metrics_collector.go | 26 +++++++-----
.../gh-copilot/tasks/user_metrics_extractor.go | 23 +++++-----
5 files changed, 80 insertions(+), 28 deletions(-)
diff --git
a/backend/plugins/gh-copilot/models/migrationscripts/20260303_add_organization_id_to_user_metrics.go
b/backend/plugins/gh-copilot/models/migrationscripts/20260303_add_organization_id_to_user_metrics.go
new file mode 100644
index 000000000..d868be690
--- /dev/null
+++
b/backend/plugins/gh-copilot/models/migrationscripts/20260303_add_organization_id_to_user_metrics.go
@@ -0,0 +1,49 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+ "github.com/apache/incubator-devlake/core/plugin"
+)
+
+var _ plugin.MigrationScript = (*addOrganizationIdToUserMetrics)(nil)
+
+type addOrganizationIdToUserMetrics struct{}
+
+func (script *addOrganizationIdToUserMetrics) Up(basicRes context.BasicRes)
errors.Error {
+ db := basicRes.GetDal()
+ return db.AutoMigrate(&userDailyMetrics20260303{})
+}
+
+func (*addOrganizationIdToUserMetrics) Version() uint64 {
+ return 20260303000000
+}
+
+func (*addOrganizationIdToUserMetrics) Name() string {
+ return "add organization_id to user daily metrics"
+}
+
+type userDailyMetrics20260303 struct {
+ OrganizationId string `gorm:"type:varchar(100)"`
+}
+
+func (userDailyMetrics20260303) TableName() string {
+ return "_tool_copilot_user_daily_metrics"
+}
diff --git a/backend/plugins/gh-copilot/models/migrationscripts/register.go
b/backend/plugins/gh-copilot/models/migrationscripts/register.go
index 5e275f943..a9c1a770b 100644
--- a/backend/plugins/gh-copilot/models/migrationscripts/register.go
+++ b/backend/plugins/gh-copilot/models/migrationscripts/register.go
@@ -29,5 +29,6 @@ func All() []plugin.MigrationScript {
new(addScopeConfig20260121),
new(migrateToUsageMetricsV2),
new(addPRFieldsToEnterpriseMetrics),
+ new(addOrganizationIdToUserMetrics),
}
}
diff --git a/backend/plugins/gh-copilot/models/user_metrics.go
b/backend/plugins/gh-copilot/models/user_metrics.go
index c53f767ff..1f17acad8 100644
--- a/backend/plugins/gh-copilot/models/user_metrics.go
+++ b/backend/plugins/gh-copilot/models/user_metrics.go
@@ -30,10 +30,11 @@ type GhCopilotUserDailyMetrics struct {
Day time.Time `gorm:"primaryKey;type:date" json:"day"`
UserId int64 `gorm:"primaryKey" json:"userId"`
- EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
- UserLogin string `json:"userLogin" gorm:"type:varchar(255);index"`
- UsedAgent bool `json:"usedAgent"`
- UsedChat bool `json:"usedChat"`
+ OrganizationId string `json:"organizationId" gorm:"type:varchar(100)"`
+ EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
+ UserLogin string `json:"userLogin" gorm:"type:varchar(255);index"`
+ UsedAgent bool `json:"usedAgent"`
+ UsedChat bool `json:"usedChat"`
CopilotActivityMetrics `mapstructure:",squash"`
common.NoPKModel
diff --git a/backend/plugins/gh-copilot/tasks/user_metrics_collector.go
b/backend/plugins/gh-copilot/tasks/user_metrics_collector.go
index 526c13bf3..f665a85e7 100644
--- a/backend/plugins/gh-copilot/tasks/user_metrics_collector.go
+++ b/backend/plugins/gh-copilot/tasks/user_metrics_collector.go
@@ -32,9 +32,9 @@ import (
const rawUserMetricsTable = "copilot_user_metrics"
-// CollectUserMetrics collects enterprise user-level daily Copilot usage
reports.
+// CollectUserMetrics collects user-level daily Copilot usage reports.
// These reports are in JSONL format (one JSON object per line per user).
-// Only available for enterprise-scoped connections.
+// Utilizes the enterprise or organization endpoints depending on connection
configuration
func CollectUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
data, ok := taskCtx.TaskContext().GetData().(*GhCopilotTaskData)
if !ok {
@@ -43,16 +43,21 @@ func CollectUserMetrics(taskCtx plugin.SubTaskContext)
errors.Error {
connection := data.Connection
connection.Normalize()
- if !connection.HasEnterprise() {
- taskCtx.GetLogger().Info("No enterprise configured, skipping
user metrics collection")
- return nil
- }
-
apiClient, err := CreateApiClient(taskCtx.TaskContext(), connection)
if err != nil {
return err
}
+ var urlTemplate string
+
+ if connection.HasEnterprise() {
+ urlTemplate =
fmt.Sprintf("enterprises/%s/copilot/metrics/reports/users-1-day",
connection.Enterprise)
+ } else if connection.Organization != "" {
+ urlTemplate =
fmt.Sprintf("orgs/%s/copilot/metrics/reports/users-1-day",
connection.Organization)
+ } else {
+ return nil
+ }
+
rawArgs := helper.RawDataSubTaskArgs{
Ctx: taskCtx,
Table: rawUserMetricsTable,
@@ -76,10 +81,9 @@ func CollectUserMetrics(taskCtx plugin.SubTaskContext)
errors.Error {
dayIter := newDayIterator(start, until)
err = collector.InitCollector(helper.ApiCollectorArgs{
- ApiClient: apiClient,
- Input: dayIter,
- UrlTemplate:
fmt.Sprintf("enterprises/%s/copilot/metrics/reports/users-1-day",
- connection.Enterprise),
+ ApiClient: apiClient,
+ Input: dayIter,
+ UrlTemplate: urlTemplate,
Query: func(reqData *helper.RequestData) (url.Values,
errors.Error) {
input := reqData.Input.(*dayInput)
q := url.Values{}
diff --git a/backend/plugins/gh-copilot/tasks/user_metrics_extractor.go
b/backend/plugins/gh-copilot/tasks/user_metrics_extractor.go
index 1eb73554b..96f5570f7 100644
--- a/backend/plugins/gh-copilot/tasks/user_metrics_extractor.go
+++ b/backend/plugins/gh-copilot/tasks/user_metrics_extractor.go
@@ -33,6 +33,7 @@ type userDailyReport struct {
ReportStartDay string
`json:"report_start_day"`
ReportEndDay string
`json:"report_end_day"`
Day string `json:"day"`
+ OrganizationId string
`json:"organization_id"`
EnterpriseId string
`json:"enterprise_id"`
UserId int64 `json:"user_id"`
UserLogin string `json:"user_login"`
@@ -78,11 +79,6 @@ func ExtractUserMetrics(taskCtx plugin.SubTaskContext)
errors.Error {
connection := data.Connection
connection.Normalize()
- if !connection.HasEnterprise() {
- taskCtx.GetLogger().Info("No enterprise configured, skipping
user metrics extraction")
- return nil
- }
-
params := copilotRawParams{
ConnectionId: data.Options.ConnectionId,
ScopeId: data.Options.ScopeId,
@@ -111,14 +107,15 @@ func ExtractUserMetrics(taskCtx plugin.SubTaskContext)
errors.Error {
// Main user daily metrics
results = append(results,
&models.GhCopilotUserDailyMetrics{
- ConnectionId: data.Options.ConnectionId,
- ScopeId: data.Options.ScopeId,
- Day: day,
- UserId: u.UserId,
- EnterpriseId: u.EnterpriseId,
- UserLogin: u.UserLogin,
- UsedAgent: u.UsedAgent,
- UsedChat: u.UsedChat,
+ ConnectionId: data.Options.ConnectionId,
+ ScopeId: data.Options.ScopeId,
+ Day: day,
+ UserId: u.UserId,
+ OrganizationId: u.OrganizationId,
+ EnterpriseId: u.EnterpriseId,
+ UserLogin: u.UserLogin,
+ UsedAgent: u.UsedAgent,
+ UsedChat: u.UsedChat,
CopilotActivityMetrics:
models.CopilotActivityMetrics{
UserInitiatedInteractionCount:
u.UserInitiatedInteractionCount,
CodeGenerationActivityCount:
u.CodeGenerationActivityCount,