Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 58 additions & 6 deletions backend/plugins/gh-copilot/models/enterprise_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ type CopilotCodeMetrics struct {

// CopilotCliMetrics contains CLI usage breakdown metrics.
type CopilotCliMetrics struct {
CliSessionCount int `json:"cliSessionCount" gorm:"comment:Number of CLI sessions"`
CliRequestCount int `json:"cliRequestCount" gorm:"comment:Number of CLI requests"`
CliPromptCount int `json:"cliPromptCount" gorm:"comment:Number of CLI prompts"`
CliOutputTokenSum int `json:"cliOutputTokenSum" gorm:"comment:Total output tokens from CLI"`
CliPromptTokenSum int `json:"cliPromptTokenSum" gorm:"comment:Total prompt tokens from CLI"`
CliSessionCount int `json:"cliSessionCount" gorm:"comment:Number of CLI sessions"`
CliRequestCount int `json:"cliRequestCount" gorm:"comment:Number of CLI requests"`
CliPromptCount int `json:"cliPromptCount" gorm:"comment:Number of CLI prompts"`
CliOutputTokenSum int `json:"cliOutputTokenSum" gorm:"comment:Total output tokens from CLI"`
CliPromptTokenSum int `json:"cliPromptTokenSum" gorm:"comment:Total prompt tokens from CLI"`
CliLastKnownVersion string `json:"cliLastKnownVersion" gorm:"type:varchar(50);comment:Last known Copilot CLI version"`
CliAvgTokensPerRequest float64 `json:"cliAvgTokensPerRequest" gorm:"comment:Average tokens consumed per CLI request"`
}

// GhCopilotEnterpriseDailyMetrics captures daily enterprise-level aggregate Copilot metrics.
Expand All @@ -59,7 +61,12 @@ type GhCopilotEnterpriseDailyMetrics struct {
ScopeId string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
Day time.Time `gorm:"primaryKey;type:date" json:"day"`

EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
EnterpriseId string `json:"enterpriseId" gorm:"type:varchar(100)"`
// OrganizationId identifies the owning org for org-level connections/rows.
// Previously always written as "" by ExtractOrgMetrics, making org-level
// rows in this shared table unidentifiable without a join back to the
// connection/scope config.
OrganizationId string `json:"organizationId" gorm:"type:varchar(100)"`
DailyActiveUsers int `json:"dailyActiveUsers"`
WeeklyActiveUsers int `json:"weeklyActiveUsers"`
MonthlyActiveUsers int `json:"monthlyActiveUsers"`
Expand All @@ -69,6 +76,11 @@ type GhCopilotEnterpriseDailyMetrics struct {
// CLI active users
DailyActiveCliUsers int `json:"dailyActiveCliUsers" gorm:"comment:Daily active CLI users"`

// Copilot cloud agent (coding agent) active user counts
DailyActiveCopilotCloudAgentUsers int `json:"dailyActiveCopilotCloudAgentUsers" gorm:"comment:Daily active Copilot cloud/coding agent users"`
WeeklyActiveCopilotCloudAgentUsers int `json:"weeklyActiveCopilotCloudAgentUsers" gorm:"comment:Weekly active Copilot cloud/coding agent users"`
MonthlyActiveCopilotCloudAgentUsers int `json:"monthlyActiveCopilotCloudAgentUsers" gorm:"comment:Monthly active Copilot cloud/coding agent users"`

// Code review user counts
DailyActiveCopilotCodeReviewUsers int `json:"dailyActiveCopilotCodeReviewUsers"`
DailyPassiveCopilotCodeReviewUsers int `json:"dailyPassiveCopilotCodeReviewUsers"`
Expand Down Expand Up @@ -100,6 +112,10 @@ type GhCopilotEnterpriseDailyMetrics struct {
PRMedianMinToMergeCopilotReviewed float64 `json:"prMedianMinToMergeCopilotReviewed" gorm:"comment:Median min to merge Copilot-reviewed PRs"`
PRTotalCopilotSuggestions int `json:"prTotalCopilotSuggestions" gorm:"comment:Total Copilot review suggestions"`
PRTotalCopilotAppliedSuggestions int `json:"prTotalCopilotAppliedSuggestions" gorm:"comment:Total Copilot applied suggestions"`
// PRCopilotSuggestionsByCommentType is a JSON-encoded array of
// {comment_type, total_suggestions, total_applied_suggestions}, e.g. the
// split between "suggestion" and "explanation" style review comments.
PRCopilotSuggestionsByCommentType string `json:"prCopilotSuggestionsByCommentType" gorm:"type:text;comment:JSON breakdown of Copilot PR suggestions by comment type"`

CopilotActivityMetrics `mapstructure:",squash"`
CopilotCliMetrics `mapstructure:",squash"`
Expand Down Expand Up @@ -187,3 +203,39 @@ type GhCopilotMetricsByModelFeature struct {
func (GhCopilotMetricsByModelFeature) TableName() string {
return "_tool_copilot_metrics_by_model_feature"
}

// GhCopilotMetricsByAiAdoptionPhase stores enterprise/org metrics broken down
// by AI adoption cohort (phase 0-3, see GitHub's totals_by_ai_adoption_phase).
// Unlike CopilotActivityMetrics elsewhere in this file, most of these fields
// are per-user averages within the phase rather than sums.
type GhCopilotMetricsByAiAdoptionPhase struct {
ConnectionId uint64 `gorm:"primaryKey" json:"connectionId"`
ScopeId string `gorm:"primaryKey;type:varchar(255)" json:"scopeId"`
Day time.Time `gorm:"primaryKey;type:date" json:"day"`
Phase int `gorm:"primaryKey" json:"phase"`

PhaseVersion int `json:"phaseVersion" gorm:"comment:Adoption-phase classification version, starts at 1"`
EngagedUsers int `json:"engagedUsers" gorm:"comment:Users engaged in this phase (2-day-in-28 window)"`

AvgUserInitiatedInteractionCount float64 `json:"avgUserInitiatedInteractionCount"`
AvgCodeGenerationActivityCount float64 `json:"avgCodeGenerationActivityCount"`
AvgCodeAcceptanceActivityCount float64 `json:"avgCodeAcceptanceActivityCount"`
AvgLocAddedSum float64 `json:"avgLocAddedSum"`
AvgLocDeletedSum float64 `json:"avgLocDeletedSum"`

AvgPullRequestsCreated float64 `json:"avgPullRequestsCreated"`
AvgPullRequestsMerged float64 `json:"avgPullRequestsMerged"`
AvgPullRequestsReviewed float64 `json:"avgPullRequestsReviewed"`
AvgMedianMinutesToMerge float64 `json:"avgMedianMinutesToMerge"`

AvgPullRequestsMinutesToReview float64 `json:"avgPullRequestsMinutesToReview" gorm:"comment:Added 2026-07-07"`
AvgPullRequestsReviewCycles float64 `json:"avgPullRequestsReviewCycles" gorm:"comment:Added 2026-07-07"`

TotalPullRequestsMerged int `json:"totalPullRequestsMerged" gorm:"comment:True sum (not average), added 2026-06-26"`

common.NoPKModel
}

func (GhCopilotMetricsByAiAdoptionPhase) TableName() string {
return "_tool_copilot_metrics_by_ai_adoption_phase"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
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 (
"time"

"github.com/apache/incubator-devlake/core/context"
"github.com/apache/incubator-devlake/core/errors"
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
"github.com/apache/incubator-devlake/helpers/migrationhelper"
)

type addCopilotMetricsGapsV2 struct{}

// --- Enterprise daily metrics: columns added in the latest Copilot API pass ---

type enterpriseDailyMetrics20260720 struct {
// totals_by_cli additions
CliLastKnownVersion string `gorm:"type:varchar(50)"`
CliAvgTokensPerRequest float64

// cloud/coding agent active user counts
DailyActiveCopilotCloudAgentUsers int
WeeklyActiveCopilotCloudAgentUsers int
MonthlyActiveCopilotCloudAgentUsers int

// pull_requests.copilot_suggestions_by_comment_type (JSON blob)
PRCopilotSuggestionsByCommentType string `gorm:"type:text"`
}

func (enterpriseDailyMetrics20260720) TableName() string {
return "_tool_copilot_enterprise_daily_metrics"
}

// --- User daily metrics: columns added in the latest Copilot API pass ---

type userDailyMetrics20260720 struct {
UsedCopilotCodingAgent bool
UsedCopilotCloudAgent bool
AiAdoptionPhase int
AiAdoptionPhaseVersion int

// totals_by_cli additions (CopilotCliMetrics is squashed into this table too)
CliLastKnownVersion string `gorm:"type:varchar(50)"`
CliAvgTokensPerRequest float64
}

func (userDailyMetrics20260720) TableName() string {
return "_tool_copilot_user_daily_metrics"
}

// --- New table: enterprise/org metrics broken down by AI adoption phase ---

type metricsByAiAdoptionPhase20260720 struct {
ConnectionId uint64 `gorm:"primaryKey"`
ScopeId string `gorm:"primaryKey;type:varchar(255)"`
Day time.Time `gorm:"primaryKey;type:date"`
Phase int `gorm:"primaryKey"`

PhaseVersion int
EngagedUsers int

AvgUserInitiatedInteractionCount float64
AvgCodeGenerationActivityCount float64
AvgCodeAcceptanceActivityCount float64
AvgLocAddedSum float64
AvgLocDeletedSum float64

AvgPullRequestsCreated float64
AvgPullRequestsMerged float64
AvgPullRequestsReviewed float64
AvgMedianMinutesToMerge float64

AvgPullRequestsMinutesToReview float64
AvgPullRequestsReviewCycles float64

TotalPullRequestsMerged int

archived.NoPKModel
}

func (metricsByAiAdoptionPhase20260720) TableName() string {
return "_tool_copilot_metrics_by_ai_adoption_phase"
}

func (script *addCopilotMetricsGapsV2) Up(basicRes context.BasicRes) errors.Error {
// Add the new columns to the existing daily-metrics tables
if err := migrationhelper.AutoMigrateTables(basicRes,
&enterpriseDailyMetrics20260720{},
&userDailyMetrics20260720{},
); err != nil {
return err
}

// Create the new AI-adoption-phase breakdown table
return migrationhelper.AutoMigrateTables(basicRes,
&metricsByAiAdoptionPhase20260720{},
)
}

func (*addCopilotMetricsGapsV2) Version() uint64 {
return 20260720000000
}

func (*addCopilotMetricsGapsV2) Name() string {
return "Add Copilot metrics gaps v2: CLI version/tokens, cloud-agent users, PR suggestion-type blob, AI adoption phase"
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func All() []plugin.MigrationScript {
new(addPRFieldsToEnterpriseMetrics),
new(addOrganizationIdToUserMetrics),
new(addCopilotMetricsGaps),
new(addCopilotMetricsGapsV2),
new(addAiCreditUsageMetrics),
new(fixAiCreditUsageBreakdownColumns),
}
Expand Down
2 changes: 1 addition & 1 deletion backend/plugins/gh-copilot/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func GetTablesInfo() []dal.Tabler {
&GhCopilotMetricsByLanguageFeature{},
&GhCopilotMetricsByLanguageModel{},
&GhCopilotMetricsByModelFeature{},
&GhCopilotMetricsByAiAdoptionPhase{},
// User-level metrics (from enterprise user reports)
&GhCopilotUserDailyMetrics{},
&GhCopilotUserMetricsByIde{},
Expand All @@ -47,7 +48,6 @@ func GetTablesInfo() []dal.Tabler {
&GhCopilotSeat{},
// User-team mappings
&GhCopilotUserTeam{},
// AI credit usage billing (enterprise, org, user levels)
&GhCopilotEnterpriseAiCreditUsage{},
&GhCopilotOrgAiCreditUsage{},
&GhCopilotUserAiCreditUsage{},
Expand Down
1 change: 1 addition & 0 deletions backend/plugins/gh-copilot/models/models_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func TestGetTablesInfo(t *testing.T) {
(&GhCopilotMetricsByLanguageFeature{}).TableName(): false,
(&GhCopilotMetricsByLanguageModel{}).TableName(): false,
(&GhCopilotMetricsByModelFeature{}).TableName(): false,
(&GhCopilotMetricsByAiAdoptionPhase{}).TableName(): false,
(&GhCopilotUserDailyMetrics{}).TableName(): false,
(&GhCopilotUserMetricsByIde{}).TableName(): false,
(&GhCopilotUserMetricsByFeature{}).TableName(): false,
Expand Down
6 changes: 6 additions & 0 deletions backend/plugins/gh-copilot/models/user_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,14 @@ type GhCopilotUserDailyMetrics struct {
UsedCli bool `json:"usedCli" gorm:"comment:Whether user used Copilot CLI"`
UsedCopilotCodeReviewActive bool `json:"usedCopilotCodeReviewActive" gorm:"comment:Whether user actively used code review"`
UsedCopilotCodeReviewPassive bool `json:"usedCopilotCodeReviewPassive" gorm:"comment:Whether user passively used code review"`
UsedCopilotCodingAgent bool `json:"usedCopilotCodingAgent" gorm:"comment:Whether user used the Copilot coding agent"`
UsedCopilotCloudAgent bool `json:"usedCopilotCloudAgent" gorm:"comment:Whether user used the Copilot cloud agent"`
AiCreditsUsed float64 `json:"aiCreditsUsed" gorm:"comment:AI credits consumed on this day"`

// AI adoption cohort classification (phase 0-3), added 2026-05-29.
AiAdoptionPhase int `json:"aiAdoptionPhase" gorm:"comment:0=no cohort,1=code-first,2=agent-first,3=multi-agent"`
AiAdoptionPhaseVersion int `json:"aiAdoptionPhaseVersion" gorm:"comment:Classification logic version, starts at 1"`

CopilotActivityMetrics `mapstructure:",squash"`
CopilotCliMetrics `mapstructure:",squash"`
common.NoPKModel
Expand Down
Loading