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 b7f851df0 feat: add ConvertQDevUserMetrics (#8460)
b7f851df0 is described below

commit b7f851df082972975ce1cf24e07d921a8726432f
Author: Warren Chen <[email protected]>
AuthorDate: Tue Jun 3 22:00:59 2025 +0800

    feat: add ConvertQDevUserMetrics (#8460)
---
 backend/plugins/q_dev/impl/impl.go                 |   1 +
 .../plugins/q_dev/tasks/user_metrics_converter.go  | 199 +++++++++++++++++++++
 2 files changed, 200 insertions(+)

diff --git a/backend/plugins/q_dev/impl/impl.go 
b/backend/plugins/q_dev/impl/impl.go
index c5dc8e91d..baa2afa2b 100644
--- a/backend/plugins/q_dev/impl/impl.go
+++ b/backend/plugins/q_dev/impl/impl.go
@@ -81,6 +81,7 @@ func (p QDev) SubTaskMetas() []plugin.SubTaskMeta {
        return []plugin.SubTaskMeta{
                tasks.CollectQDevS3FilesMeta,
                tasks.ExtractQDevS3DataMeta,
+               tasks.ConvertQDevUserMetricsMeta,
        }
 }
 
diff --git a/backend/plugins/q_dev/tasks/user_metrics_converter.go 
b/backend/plugins/q_dev/tasks/user_metrics_converter.go
new file mode 100644
index 000000000..85b102e7c
--- /dev/null
+++ b/backend/plugins/q_dev/tasks/user_metrics_converter.go
@@ -0,0 +1,199 @@
+/*
+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 (
+       "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 按用户聚合指标
+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")
+       }
+
+       // 聚合数据
+       userDataMap := make(map[string]*UserMetricsAggregation)
+
+       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 {
+                       aggregation = &UserMetricsAggregation{
+                               ConnectionId: userData.ConnectionId,
+                               UserId:       userData.UserId,
+                               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 {
+               // 创建指标记录
+               metrics := &models.QDevUserMetrics{
+                       ConnectionId: aggregation.ConnectionId,
+                       UserId:       aggregation.UserId,
+                       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)
+               }
+
+               // 存储聚合指标
+               err = db.Create(metrics)
+               if err != nil {
+                       return errors.Default.Wrap(err, "failed to create user 
metrics")
+               }
+
+               taskCtx.IncProgress(1)
+       }
+
+       return nil
+}
+
+// UserMetricsAggregation 聚合过程中用于保存用户指标的结构
+type UserMetricsAggregation struct {
+       ConnectionId                           uint64
+       UserId                                 string
+       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
+}
+
+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},
+}

Reply via email to