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 affed66b7 feat(q_dev):delete user metrics (#8493)
affed66b7 is described below
commit affed66b7a02a206e65d9ef2a36e96505194bdb1
Author: Warren Chen <[email protected]>
AuthorDate: Wed Jul 9 17:47:51 2025 +0800
feat(q_dev):delete user metrics (#8493)
* feat: delete user metrics
* feat: modify dashboard for qdev
---
backend/plugins/q_dev/impl/impl.go | 2 -
backend/plugins/q_dev/impl/impl_test.go | 18 +-
.../20250709_delete_user_metrics.go | 46 ++++
backend/plugins/q_dev/models/user_metrics.go | 68 ------
backend/plugins/q_dev/models/user_metrics_test.go | 155 -------------
.../plugins/q_dev/tasks/user_metrics_converter.go | 240 ---------------------
.../q_dev/tasks/user_metrics_converter_test.go | 212 ------------------
7 files changed, 55 insertions(+), 686 deletions(-)
diff --git a/backend/plugins/q_dev/impl/impl.go
b/backend/plugins/q_dev/impl/impl.go
index 3a93a11ef..5cf8bf393 100644
--- a/backend/plugins/q_dev/impl/impl.go
+++ b/backend/plugins/q_dev/impl/impl.go
@@ -52,7 +52,6 @@ func (p QDev) GetTablesInfo() []dal.Tabler {
return []dal.Tabler{
&models.QDevConnection{},
&models.QDevUserData{},
- &models.QDevUserMetrics{},
&models.QDevS3FileMeta{},
}
}
@@ -81,7 +80,6 @@ func (p QDev) SubTaskMetas() []plugin.SubTaskMeta {
return []plugin.SubTaskMeta{
tasks.CollectQDevS3FilesMeta,
tasks.ExtractQDevS3DataMeta,
- tasks.ConvertQDevUserMetricsMeta,
}
}
diff --git a/backend/plugins/q_dev/impl/impl_test.go
b/backend/plugins/q_dev/impl/impl_test.go
index 568c5af26..7c02ef488 100644
--- a/backend/plugins/q_dev/impl/impl_test.go
+++ b/backend/plugins/q_dev/impl/impl_test.go
@@ -27,19 +27,19 @@ import (
func TestQDev_BasicPluginMethods(t *testing.T) {
plugin := &QDev{}
-
+
assert.Equal(t, "q_dev", plugin.Name())
assert.Equal(t, "To collect and enrich data from AWS Q Developer usage
metrics", plugin.Description())
assert.Equal(t, "github.com/apache/incubator-devlake/plugins/q_dev",
plugin.RootPkgPath())
-
+
// Test table info
tables := plugin.GetTablesInfo()
- assert.Len(t, tables, 4)
-
+ assert.Len(t, tables, 3)
+
// Test subtask metas
subtasks := plugin.SubTaskMetas()
- assert.Len(t, subtasks, 3)
-
+ assert.Len(t, subtasks, 2)
+
// Test API resources
apiResources := plugin.ApiResources()
assert.NotEmpty(t, apiResources)
@@ -62,11 +62,11 @@ func TestQDev_TaskDataStructure(t *testing.T) {
Region: "us-west-2",
},
}
-
+
assert.NotNil(t, taskData.Options)
assert.NotNil(t, taskData.S3Client)
assert.NotNil(t, taskData.IdentityClient)
-
+
assert.Equal(t, uint64(1), taskData.Options.ConnectionId)
assert.Equal(t, "test/", taskData.Options.S3Prefix)
assert.Equal(t, "test-bucket", taskData.S3Client.Bucket)
@@ -85,7 +85,7 @@ func TestQDev_TaskDataWithoutIdentityClient(t *testing.T) {
},
IdentityClient: nil, // No identity client
}
-
+
assert.NotNil(t, taskData.Options)
assert.NotNil(t, taskData.S3Client)
assert.Nil(t, taskData.IdentityClient)
diff --git
a/backend/plugins/q_dev/models/migrationscripts/20250709_delete_user_metrics.go
b/backend/plugins/q_dev/models/migrationscripts/20250709_delete_user_metrics.go
new file mode 100644
index 000000000..4908559e3
--- /dev/null
+++
b/backend/plugins/q_dev/models/migrationscripts/20250709_delete_user_metrics.go
@@ -0,0 +1,46 @@
+/*
+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 = (*deleteUserMetrics)(nil)
+
+type deleteUserMetrics struct{}
+
+func (*deleteUserMetrics) Up(basicRes context.BasicRes) errors.Error {
+ db := basicRes.GetDal()
+
+ // Drop the QDevUserMetrics table
+ // Ignore error if table doesn't exist
+ _ = db.Exec("DROP TABLE IF EXISTS _tool_q_dev_user_metrics")
+
+ return nil
+}
+
+func (*deleteUserMetrics) Version() uint64 {
+ return 20250709000001
+}
+
+func (*deleteUserMetrics) Name() string {
+ return "delete QDevUserMetrics table"
+}
diff --git a/backend/plugins/q_dev/models/user_metrics.go
b/backend/plugins/q_dev/models/user_metrics.go
deleted file mode 100644
index 71987e92e..000000000
--- a/backend/plugins/q_dev/models/user_metrics.go
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-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 models
-
-import (
- "time"
-
- "github.com/apache/incubator-devlake/core/models/common"
-)
-
-// QDevUserMetrics 存储按用户聚合的指标数据
-type QDevUserMetrics struct {
- common.NoPKModel
- ConnectionId uint64 `gorm:"primaryKey"`
- UserId string `gorm:"primaryKey"`
- DisplayName string `gorm:"type:varchar(255)" json:"displayName"` //
New field for user display name
- FirstDate time.Time
- LastDate time.Time
- TotalDays int
-
- // 聚合指标
- TotalCodeReview_FindingsCount int
- TotalCodeReview_SucceededEventCount int
- TotalInlineChat_AcceptanceEventCount int
- TotalInlineChat_AcceptedLineAdditions int
- TotalInlineChat_AcceptedLineDeletions int
- TotalInlineChat_DismissalEventCount int
- TotalInlineChat_DismissedLineAdditions int
- TotalInlineChat_DismissedLineDeletions int
- TotalInlineChat_RejectedLineAdditions int
- TotalInlineChat_RejectedLineDeletions int
- TotalInlineChat_RejectionEventCount int
- TotalInlineChat_TotalEventCount int
- TotalInline_AICodeLines int
- TotalInline_AcceptanceCount int
- TotalInline_SuggestionsCount int
-
- // 平均指标
- AvgCodeReview_FindingsCount float64
- AvgCodeReview_SucceededEventCount float64
- AvgInlineChat_AcceptanceEventCount float64
- AvgInlineChat_TotalEventCount float64
- AvgInline_AICodeLines float64
- AvgInline_AcceptanceCount float64
- AvgInline_SuggestionsCount float64
-
- // 接受率指标
- AcceptanceRate float64
-}
-
-func (QDevUserMetrics) TableName() string {
- return "_tool_q_dev_user_metrics"
-}
diff --git a/backend/plugins/q_dev/models/user_metrics_test.go
b/backend/plugins/q_dev/models/user_metrics_test.go
deleted file mode 100644
index 0a85d3263..000000000
--- a/backend/plugins/q_dev/models/user_metrics_test.go
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
-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 models
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestQDevUserMetrics_WithDisplayName(t *testing.T) {
- userMetrics := QDevUserMetrics{
- ConnectionId: 1,
- UserId: "uuid-123",
- DisplayName: "John Doe",
- FirstDate: time.Now().AddDate(0, 0, -30),
- LastDate: time.Now(),
- TotalDays: 30,
- TotalCodeReview_FindingsCount: 50,
- AcceptanceRate: 0.85,
- }
-
- assert.Equal(t, "John Doe", userMetrics.DisplayName)
- assert.Equal(t, "uuid-123", userMetrics.UserId)
- assert.Equal(t, uint64(1), userMetrics.ConnectionId)
- assert.Equal(t, 50, userMetrics.TotalCodeReview_FindingsCount)
- assert.Equal(t, 0.85, userMetrics.AcceptanceRate)
-}
-
-func TestQDevUserMetrics_WithFallbackDisplayName(t *testing.T) {
- userMetrics := QDevUserMetrics{
- ConnectionId: 1,
- UserId: "uuid-456",
- DisplayName: "uuid-456", // Fallback case when display name
resolution fails
- TotalDays: 15,
- }
-
- assert.Equal(t, "uuid-456", userMetrics.DisplayName)
- assert.Equal(t, userMetrics.UserId, userMetrics.DisplayName) // Should
match when fallback
- assert.Equal(t, 15, userMetrics.TotalDays)
-}
-
-func TestQDevUserMetrics_EmptyDisplayName(t *testing.T) {
- userMetrics := QDevUserMetrics{
- ConnectionId: 1,
- UserId: "uuid-789",
- DisplayName: "", // Empty display name
- TotalDays: 5,
- }
-
- assert.Equal(t, "", userMetrics.DisplayName)
- assert.Equal(t, "uuid-789", userMetrics.UserId)
- assert.NotEqual(t, userMetrics.UserId, userMetrics.DisplayName)
-}
-
-func TestQDevUserMetrics_TableName(t *testing.T) {
- userMetrics := QDevUserMetrics{}
- assert.Equal(t, "_tool_q_dev_user_metrics", userMetrics.TableName())
-}
-
-func TestQDevUserMetrics_AllFields(t *testing.T) {
- firstDate := time.Now().AddDate(0, 0, -30)
- lastDate := time.Now()
-
- userMetrics := QDevUserMetrics{
- ConnectionId: 1,
- UserId: "test-user",
- DisplayName: "Test User",
- FirstDate: firstDate,
- LastDate: lastDate,
- TotalDays: 30,
-
- // 聚合指标
- TotalCodeReview_FindingsCount: 100,
- TotalCodeReview_SucceededEventCount: 90,
- TotalInlineChat_AcceptanceEventCount: 80,
- TotalInlineChat_AcceptedLineAdditions: 70,
- TotalInlineChat_AcceptedLineDeletions: 60,
- TotalInlineChat_DismissalEventCount: 50,
- TotalInlineChat_DismissedLineAdditions: 40,
- TotalInlineChat_DismissedLineDeletions: 30,
- TotalInlineChat_RejectedLineAdditions: 20,
- TotalInlineChat_RejectedLineDeletions: 10,
- TotalInlineChat_RejectionEventCount: 5,
- TotalInlineChat_TotalEventCount: 200,
- TotalInline_AICodeLines: 1000,
- TotalInline_AcceptanceCount: 150,
- TotalInline_SuggestionsCount: 180,
-
- // 平均指标
- AvgCodeReview_FindingsCount: 3.33,
- AvgCodeReview_SucceededEventCount: 3.0,
- AvgInlineChat_AcceptanceEventCount: 2.67,
- AvgInlineChat_TotalEventCount: 6.67,
- AvgInline_AICodeLines: 33.33,
- AvgInline_AcceptanceCount: 5.0,
- AvgInline_SuggestionsCount: 6.0,
-
- // 接受率指标
- AcceptanceRate: 0.83,
- }
-
- // Verify all fields are properly set
- assert.Equal(t, uint64(1), userMetrics.ConnectionId)
- assert.Equal(t, "test-user", userMetrics.UserId)
- assert.Equal(t, "Test User", userMetrics.DisplayName)
- assert.Equal(t, firstDate, userMetrics.FirstDate)
- assert.Equal(t, lastDate, userMetrics.LastDate)
- assert.Equal(t, 30, userMetrics.TotalDays)
-
- // Test aggregated metrics
- assert.Equal(t, 100, userMetrics.TotalCodeReview_FindingsCount)
- assert.Equal(t, 90, userMetrics.TotalCodeReview_SucceededEventCount)
- assert.Equal(t, 80, userMetrics.TotalInlineChat_AcceptanceEventCount)
- assert.Equal(t, 70, userMetrics.TotalInlineChat_AcceptedLineAdditions)
- assert.Equal(t, 60, userMetrics.TotalInlineChat_AcceptedLineDeletions)
- assert.Equal(t, 50, userMetrics.TotalInlineChat_DismissalEventCount)
- assert.Equal(t, 40, userMetrics.TotalInlineChat_DismissedLineAdditions)
- assert.Equal(t, 30, userMetrics.TotalInlineChat_DismissedLineDeletions)
- assert.Equal(t, 20, userMetrics.TotalInlineChat_RejectedLineAdditions)
- assert.Equal(t, 10, userMetrics.TotalInlineChat_RejectedLineDeletions)
- assert.Equal(t, 5, userMetrics.TotalInlineChat_RejectionEventCount)
- assert.Equal(t, 200, userMetrics.TotalInlineChat_TotalEventCount)
- assert.Equal(t, 1000, userMetrics.TotalInline_AICodeLines)
- assert.Equal(t, 150, userMetrics.TotalInline_AcceptanceCount)
- assert.Equal(t, 180, userMetrics.TotalInline_SuggestionsCount)
-
- // Test average metrics
- assert.Equal(t, 3.33, userMetrics.AvgCodeReview_FindingsCount)
- assert.Equal(t, 3.0, userMetrics.AvgCodeReview_SucceededEventCount)
- assert.Equal(t, 2.67, userMetrics.AvgInlineChat_AcceptanceEventCount)
- assert.Equal(t, 6.67, userMetrics.AvgInlineChat_TotalEventCount)
- assert.Equal(t, 33.33, userMetrics.AvgInline_AICodeLines)
- assert.Equal(t, 5.0, userMetrics.AvgInline_AcceptanceCount)
- assert.Equal(t, 6.0, userMetrics.AvgInline_SuggestionsCount)
-
- // Test acceptance rate
- assert.Equal(t, 0.83, userMetrics.AcceptanceRate)
-}
diff --git a/backend/plugins/q_dev/tasks/user_metrics_converter.go
b/backend/plugins/q_dev/tasks/user_metrics_converter.go
deleted file mode 100644
index c639da3e3..000000000
--- a/backend/plugins/q_dev/tasks/user_metrics_converter.go
+++ /dev/null
@@ -1,240 +0,0 @@
-/*
-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 tasks
-
-import (
- "fmt"
- "github.com/apache/incubator-devlake/core/dal"
- "github.com/apache/incubator-devlake/core/errors"
- "github.com/apache/incubator-devlake/core/plugin"
- "github.com/apache/incubator-devlake/plugins/q_dev/models"
- "math"
- "time"
-)
-
-var _ plugin.SubTaskEntryPoint = ConvertQDevUserMetrics
-
-// ConvertQDevUserMetrics 按用户聚合指标 (enhanced with display name support)
-func ConvertQDevUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
- data := taskCtx.GetData().(*QDevTaskData)
- db := taskCtx.GetDal()
-
- // 清空之前聚合的数据
- clauses := []dal.Clause{
- dal.Where("connection_id = ?", data.Options.ConnectionId),
- }
- err := db.Delete(&models.QDevUserMetrics{}, clauses...)
- if err != nil {
- return errors.Default.Wrap(err, "failed to delete previous user
metrics")
- }
-
- // 聚合数据 (updated to include display name)
- userDataMap := make(map[string]*UserMetricsAggregationWithDisplayName)
-
- cursor, err := db.Cursor(
- dal.From(&models.QDevUserData{}),
- dal.Where("connection_id = ?", data.Options.ConnectionId),
- )
- if err != nil {
- return errors.Default.Wrap(err, "failed to get user data
cursor")
- }
- defer cursor.Close()
-
- taskCtx.SetProgress(0, -1)
-
- // 汇总每个用户的数据
- for cursor.Next() {
- userData := &models.QDevUserData{}
- err = db.Fetch(cursor, userData)
- if err != nil {
- return errors.Default.Wrap(err, "failed to fetch user
data")
- }
-
- // 获取或创建用户聚合
- aggregation, ok := userDataMap[userData.UserId]
- if !ok {
- // Resolve display name for new user (new functionality)
- displayName :=
resolveDisplayNameForAggregation(userData.UserId, data.IdentityClient)
- // If user data already has display name, use it;
otherwise use resolved name
- if userData.DisplayName != "" {
- displayName = userData.DisplayName
- }
-
- aggregation = &UserMetricsAggregationWithDisplayName{
- ConnectionId: userData.ConnectionId,
- UserId: userData.UserId,
- DisplayName: displayName, // New field
- FirstDate: userData.Date,
- LastDate: userData.Date,
- DataCount: 0,
- }
- userDataMap[userData.UserId] = aggregation
- }
-
- // 更新日期范围
- if userData.Date.Before(aggregation.FirstDate) {
- aggregation.FirstDate = userData.Date
- }
- if userData.Date.After(aggregation.LastDate) {
- aggregation.LastDate = userData.Date
- }
-
- // 累加指标
- aggregation.DataCount++
- aggregation.TotalCodeReview_FindingsCount +=
userData.CodeReview_FindingsCount
- aggregation.TotalCodeReview_SucceededEventCount +=
userData.CodeReview_SucceededEventCount
- aggregation.TotalInlineChat_AcceptanceEventCount +=
userData.InlineChat_AcceptanceEventCount
- aggregation.TotalInlineChat_AcceptedLineAdditions +=
userData.InlineChat_AcceptedLineAdditions
- aggregation.TotalInlineChat_AcceptedLineDeletions +=
userData.InlineChat_AcceptedLineDeletions
- aggregation.TotalInlineChat_DismissalEventCount +=
userData.InlineChat_DismissalEventCount
- aggregation.TotalInlineChat_DismissedLineAdditions +=
userData.InlineChat_DismissedLineAdditions
- aggregation.TotalInlineChat_DismissedLineDeletions +=
userData.InlineChat_DismissedLineDeletions
- aggregation.TotalInlineChat_RejectedLineAdditions +=
userData.InlineChat_RejectedLineAdditions
- aggregation.TotalInlineChat_RejectedLineDeletions +=
userData.InlineChat_RejectedLineDeletions
- aggregation.TotalInlineChat_RejectionEventCount +=
userData.InlineChat_RejectionEventCount
- aggregation.TotalInlineChat_TotalEventCount +=
userData.InlineChat_TotalEventCount
- aggregation.TotalInline_AICodeLines +=
userData.Inline_AICodeLines
- aggregation.TotalInline_AcceptanceCount +=
userData.Inline_AcceptanceCount
- aggregation.TotalInline_SuggestionsCount +=
userData.Inline_SuggestionsCount
- }
-
- // 计算每个用户的平均指标和总天数
- for _, aggregation := range userDataMap {
- // 创建指标记录 (updated to use new method)
- metrics := aggregation.ToUserMetrics()
-
- // 存储聚合指标
- err = db.Create(metrics)
- if err != nil {
- return errors.Default.Wrap(err, "failed to create user
metrics")
- }
-
- taskCtx.IncProgress(1)
- }
-
- return nil
-}
-
-// UserMetricsAggregationWithDisplayName 聚合过程中用于保存用户指标的结构 (enhanced with
display name)
-type UserMetricsAggregationWithDisplayName struct {
- ConnectionId uint64
- UserId string
- DisplayName string // New field for display
name
- FirstDate time.Time
- LastDate time.Time
- DataCount int
- TotalCodeReview_FindingsCount int
- TotalCodeReview_SucceededEventCount int
- TotalInlineChat_AcceptanceEventCount int
- TotalInlineChat_AcceptedLineAdditions int
- TotalInlineChat_AcceptedLineDeletions int
- TotalInlineChat_DismissalEventCount int
- TotalInlineChat_DismissedLineAdditions int
- TotalInlineChat_DismissedLineDeletions int
- TotalInlineChat_RejectedLineAdditions int
- TotalInlineChat_RejectedLineDeletions int
- TotalInlineChat_RejectionEventCount int
- TotalInlineChat_TotalEventCount int
- TotalInline_AICodeLines int
- TotalInline_AcceptanceCount int
- TotalInline_SuggestionsCount int
-}
-
-// ToUserMetrics converts aggregation data to QDevUserMetrics model
-func (aggregation *UserMetricsAggregationWithDisplayName) ToUserMetrics()
*models.QDevUserMetrics {
- metrics := &models.QDevUserMetrics{
- ConnectionId: aggregation.ConnectionId,
- UserId: aggregation.UserId,
- DisplayName: aggregation.DisplayName, // New field
- FirstDate: aggregation.FirstDate,
- LastDate: aggregation.LastDate,
- }
-
- // 计算总天数
- metrics.TotalDays =
int(math.Round(aggregation.LastDate.Sub(aggregation.FirstDate).Hours()/24)) + 1
-
- // 设置总计指标
- metrics.TotalCodeReview_FindingsCount =
aggregation.TotalCodeReview_FindingsCount
- metrics.TotalCodeReview_SucceededEventCount =
aggregation.TotalCodeReview_SucceededEventCount
- metrics.TotalInlineChat_AcceptanceEventCount =
aggregation.TotalInlineChat_AcceptanceEventCount
- metrics.TotalInlineChat_AcceptedLineAdditions =
aggregation.TotalInlineChat_AcceptedLineAdditions
- metrics.TotalInlineChat_AcceptedLineDeletions =
aggregation.TotalInlineChat_AcceptedLineDeletions
- metrics.TotalInlineChat_DismissalEventCount =
aggregation.TotalInlineChat_DismissalEventCount
- metrics.TotalInlineChat_DismissedLineAdditions =
aggregation.TotalInlineChat_DismissedLineAdditions
- metrics.TotalInlineChat_DismissedLineDeletions =
aggregation.TotalInlineChat_DismissedLineDeletions
- metrics.TotalInlineChat_RejectedLineAdditions =
aggregation.TotalInlineChat_RejectedLineAdditions
- metrics.TotalInlineChat_RejectedLineDeletions =
aggregation.TotalInlineChat_RejectedLineDeletions
- metrics.TotalInlineChat_RejectionEventCount =
aggregation.TotalInlineChat_RejectionEventCount
- metrics.TotalInlineChat_TotalEventCount =
aggregation.TotalInlineChat_TotalEventCount
- metrics.TotalInline_AICodeLines = aggregation.TotalInline_AICodeLines
- metrics.TotalInline_AcceptanceCount =
aggregation.TotalInline_AcceptanceCount
- metrics.TotalInline_SuggestionsCount =
aggregation.TotalInline_SuggestionsCount
-
- // 计算平均值指标
- if metrics.TotalDays > 0 {
- metrics.AvgCodeReview_FindingsCount =
float64(aggregation.TotalCodeReview_FindingsCount) / float64(metrics.TotalDays)
- metrics.AvgCodeReview_SucceededEventCount =
float64(aggregation.TotalCodeReview_SucceededEventCount) /
float64(metrics.TotalDays)
- metrics.AvgInlineChat_AcceptanceEventCount =
float64(aggregation.TotalInlineChat_AcceptanceEventCount) /
float64(metrics.TotalDays)
- metrics.AvgInlineChat_TotalEventCount =
float64(aggregation.TotalInlineChat_TotalEventCount) /
float64(metrics.TotalDays)
- metrics.AvgInline_AICodeLines =
float64(aggregation.TotalInline_AICodeLines) / float64(metrics.TotalDays)
- metrics.AvgInline_AcceptanceCount =
float64(aggregation.TotalInline_AcceptanceCount) / float64(metrics.TotalDays)
- metrics.AvgInline_SuggestionsCount =
float64(aggregation.TotalInline_SuggestionsCount) / float64(metrics.TotalDays)
- }
-
- // 计算接受率
- totalEvents := aggregation.TotalInlineChat_AcceptanceEventCount +
- aggregation.TotalInlineChat_DismissalEventCount +
- aggregation.TotalInlineChat_RejectionEventCount
-
- if totalEvents > 0 {
- metrics.AcceptanceRate =
float64(aggregation.TotalInlineChat_AcceptanceEventCount) / float64(totalEvents)
- }
-
- return metrics
-}
-
-// resolveDisplayNameForAggregation resolves display name for user metrics
aggregation
-func resolveDisplayNameForAggregation(userId string, identityClient
UserDisplayNameResolver) string {
- // If no identity client available, use userId as fallback
- if identityClient == nil {
- return userId
- }
-
- // Try to resolve display name
- displayName, err := identityClient.ResolveUserDisplayName(userId)
- if err != nil {
- // Log error but continue with userId as fallback
- fmt.Printf("Failed to resolve display name for user %s during
aggregation: %v\n", userId, err)
- return userId
- }
-
- // If display name is empty, use userId as fallback
- if displayName == "" {
- return userId
- }
-
- return displayName
-}
-
-var ConvertQDevUserMetricsMeta = plugin.SubTaskMeta{
- Name: "convertQDevUserMetrics",
- EntryPoint: ConvertQDevUserMetrics,
- EnabledByDefault: true,
- Description: "Convert user data to metrics by each user",
- DomainTypes: []string{plugin.DOMAIN_TYPE_CROSS},
-}
diff --git a/backend/plugins/q_dev/tasks/user_metrics_converter_test.go
b/backend/plugins/q_dev/tasks/user_metrics_converter_test.go
deleted file mode 100644
index 15df329f7..000000000
--- a/backend/plugins/q_dev/tasks/user_metrics_converter_test.go
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
-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 tasks
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestUserMetricsAggregationWithDisplayName_SingleUser(t *testing.T) {
- aggregation := &UserMetricsAggregationWithDisplayName{
- ConnectionId: 1,
- UserId: "user-123",
- DisplayName: "John Doe",
- FirstDate: time.Date(2025, 6, 20, 0, 0, 0, 0, time.UTC),
- LastDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- DataCount: 4,
- TotalCodeReview_FindingsCount: 20,
- TotalInlineChat_AcceptanceEventCount: 40,
- TotalInline_AcceptanceCount: 60,
- TotalInline_SuggestionsCount: 80,
- }
-
- metrics := aggregation.ToUserMetrics()
-
- assert.Equal(t, uint64(1), metrics.ConnectionId)
- assert.Equal(t, "user-123", metrics.UserId)
- assert.Equal(t, "John Doe", metrics.DisplayName)
- assert.Equal(t, 4, metrics.TotalDays) // 6/20 to 6/23 = 4 days
- assert.Equal(t, 20, metrics.TotalCodeReview_FindingsCount)
- assert.Equal(t, 40, metrics.TotalInlineChat_AcceptanceEventCount)
- assert.Equal(t, 60, metrics.TotalInline_AcceptanceCount)
- assert.Equal(t, 80, metrics.TotalInline_SuggestionsCount)
-
- // Test averages
- assert.Equal(t, 5.0, metrics.AvgCodeReview_FindingsCount) // 20/4
- assert.Equal(t, 10.0, metrics.AvgInlineChat_AcceptanceEventCount) //
40/4
- assert.Equal(t, 15.0, metrics.AvgInline_AcceptanceCount) // 60/4
- assert.Equal(t, 20.0, metrics.AvgInline_SuggestionsCount) // 80/4
-}
-
-func TestUserMetricsAggregationWithDisplayName_FallbackDisplayName(t
*testing.T) {
- aggregation := &UserMetricsAggregationWithDisplayName{
- ConnectionId: 1,
- UserId: "user-456",
- DisplayName: "user-456", // Fallback case
- FirstDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- LastDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- DataCount: 1,
- }
-
- metrics := aggregation.ToUserMetrics()
-
- assert.Equal(t, "user-456", metrics.UserId)
- assert.Equal(t, "user-456", metrics.DisplayName)
- assert.Equal(t, metrics.UserId, metrics.DisplayName) // Should match
when fallback
- assert.Equal(t, 1, metrics.TotalDays) // Same day = 1 day
-}
-
-func TestUserMetricsAggregationWithDisplayName_AcceptanceRateCalculation(t
*testing.T) {
- aggregation := &UserMetricsAggregationWithDisplayName{
- ConnectionId: 1,
- UserId: "user-789",
- DisplayName: "Jane Smith",
- FirstDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- LastDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- DataCount: 1,
- TotalInlineChat_AcceptanceEventCount: 80, // Accepted
- TotalInlineChat_DismissalEventCount: 15, // Dismissed
- TotalInlineChat_RejectionEventCount: 5, // Rejected
- // Total events = 80 + 15 + 5 = 100
- // Acceptance rate = 80/100 = 0.8
- }
-
- metrics := aggregation.ToUserMetrics()
-
- assert.Equal(t, "Jane Smith", metrics.DisplayName)
- assert.Equal(t, 80, metrics.TotalInlineChat_AcceptanceEventCount)
- assert.Equal(t, 15, metrics.TotalInlineChat_DismissalEventCount)
- assert.Equal(t, 5, metrics.TotalInlineChat_RejectionEventCount)
- assert.Equal(t, 0.8, metrics.AcceptanceRate)
-}
-
-func TestUserMetricsAggregationWithDisplayName_ZeroAcceptanceRate(t
*testing.T) {
- aggregation := &UserMetricsAggregationWithDisplayName{
- ConnectionId: 1,
- UserId: "user-zero",
- DisplayName: "Zero User",
- FirstDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- LastDate: time.Date(2025, 6, 23, 0, 0, 0, 0, time.UTC),
- DataCount: 1,
- // No events = acceptance rate should be 0
- }
-
- metrics := aggregation.ToUserMetrics()
-
- assert.Equal(t, "Zero User", metrics.DisplayName)
- assert.Equal(t, 0.0, metrics.AcceptanceRate)
-}
-
-func TestUserMetricsAggregationWithDisplayName_AllFields(t *testing.T) {
- firstDate := time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC)
- lastDate := time.Date(2025, 6, 30, 0, 0, 0, 0, time.UTC)
-
- aggregation := &UserMetricsAggregationWithDisplayName{
- ConnectionId: 123,
- UserId: "test-user",
- DisplayName: "Test User",
- FirstDate: firstDate,
- LastDate: lastDate,
- DataCount: 30,
-
- // Set all total fields
- TotalCodeReview_FindingsCount: 300,
- TotalCodeReview_SucceededEventCount: 270,
- TotalInlineChat_AcceptanceEventCount: 240,
- TotalInlineChat_AcceptedLineAdditions: 210,
- TotalInlineChat_AcceptedLineDeletions: 180,
- TotalInlineChat_DismissalEventCount: 150,
- TotalInlineChat_DismissedLineAdditions: 120,
- TotalInlineChat_DismissedLineDeletions: 90,
- TotalInlineChat_RejectedLineAdditions: 60,
- TotalInlineChat_RejectedLineDeletions: 30,
- TotalInlineChat_RejectionEventCount: 15,
- TotalInlineChat_TotalEventCount: 600,
- TotalInline_AICodeLines: 3000,
- TotalInline_AcceptanceCount: 450,
- TotalInline_SuggestionsCount: 540,
- }
-
- metrics := aggregation.ToUserMetrics()
-
- // Verify basic fields
- assert.Equal(t, uint64(123), metrics.ConnectionId)
- assert.Equal(t, "test-user", metrics.UserId)
- assert.Equal(t, "Test User", metrics.DisplayName)
- assert.Equal(t, firstDate, metrics.FirstDate)
- assert.Equal(t, lastDate, metrics.LastDate)
- assert.Equal(t, 30, metrics.TotalDays) // June 1-30 = 30 days
-
- // Verify all total fields
- assert.Equal(t, 300, metrics.TotalCodeReview_FindingsCount)
- assert.Equal(t, 270, metrics.TotalCodeReview_SucceededEventCount)
- assert.Equal(t, 240, metrics.TotalInlineChat_AcceptanceEventCount)
- assert.Equal(t, 210, metrics.TotalInlineChat_AcceptedLineAdditions)
- assert.Equal(t, 180, metrics.TotalInlineChat_AcceptedLineDeletions)
- assert.Equal(t, 150, metrics.TotalInlineChat_DismissalEventCount)
- assert.Equal(t, 120, metrics.TotalInlineChat_DismissedLineAdditions)
- assert.Equal(t, 90, metrics.TotalInlineChat_DismissedLineDeletions)
- assert.Equal(t, 60, metrics.TotalInlineChat_RejectedLineAdditions)
- assert.Equal(t, 30, metrics.TotalInlineChat_RejectedLineDeletions)
- assert.Equal(t, 15, metrics.TotalInlineChat_RejectionEventCount)
- assert.Equal(t, 600, metrics.TotalInlineChat_TotalEventCount)
- assert.Equal(t, 3000, metrics.TotalInline_AICodeLines)
- assert.Equal(t, 450, metrics.TotalInline_AcceptanceCount)
- assert.Equal(t, 540, metrics.TotalInline_SuggestionsCount)
-
- // Verify average fields (all divided by 30 days)
- assert.Equal(t, 10.0, metrics.AvgCodeReview_FindingsCount) //
300/30
- assert.Equal(t, 9.0, metrics.AvgCodeReview_SucceededEventCount) //
270/30
- assert.Equal(t, 8.0, metrics.AvgInlineChat_AcceptanceEventCount) //
240/30
- assert.Equal(t, 20.0, metrics.AvgInlineChat_TotalEventCount) //
600/30
- assert.Equal(t, 100.0, metrics.AvgInline_AICodeLines) //
3000/30
- assert.Equal(t, 15.0, metrics.AvgInline_AcceptanceCount) //
450/30
- assert.Equal(t, 18.0, metrics.AvgInline_SuggestionsCount) //
540/30
-
- // Verify acceptance rate: 240 / (240 + 150 + 15) = 240/405 ≈ 0.593
- expectedAcceptanceRate := 240.0 / (240.0 + 150.0 + 15.0)
- assert.InDelta(t, expectedAcceptanceRate, metrics.AcceptanceRate, 0.001)
-}
-
-func TestResolveDisplayNameForAggregation_Success(t *testing.T) {
- mockIdentityClient := &MockIdentityClient{}
- mockIdentityClient.On("ResolveUserDisplayName",
"user-123").Return("John Doe", nil)
-
- displayName := resolveDisplayNameForAggregation("user-123",
mockIdentityClient)
- assert.Equal(t, "John Doe", displayName)
-
- mockIdentityClient.AssertExpectations(t)
-}
-
-func TestResolveDisplayNameForAggregation_NoClient(t *testing.T) {
- displayName := resolveDisplayNameForAggregation("user-456", nil)
- assert.Equal(t, "user-456", displayName)
-}
-
-func TestResolveDisplayNameForAggregation_Error(t *testing.T) {
- mockIdentityClient := &MockIdentityClient{}
- mockIdentityClient.On("ResolveUserDisplayName",
"user-error").Return("user-error", assert.AnError)
-
- displayName := resolveDisplayNameForAggregation("user-error",
mockIdentityClient)
- assert.Equal(t, "user-error", displayName) // Should fallback to UUID
-
- mockIdentityClient.AssertExpectations(t)
-}