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,

Reply via email to