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/devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new b24379c46 feat(dora): exclude bot/automation accounts from PR Pickup 
Time calcu… (#9000)
b24379c46 is described below

commit b24379c46a9a4b6e2da589f3fd4a14705fafed8b
Author: Bramhanand Lingala <[email protected]>
AuthorDate: Fri Aug 14 05:50:29 2026 +0530

    feat(dora): exclude bot/automation accounts from PR Pickup Time calcu… 
(#9000)
    
    * feat(dora): exclude bot/automation accounts from PR Pickup Time 
calculation
    
    * test: add is_bot column to account e2e snapshot fixtures
---
 .../core/models/domainlayer/crossdomain/account.go |  4 ++-
 .../20260720_add_is_bot_to_accounts.go}            | 40 +++++++++++++++-------
 backend/core/models/migrationscripts/register.go   |  1 +
 .../bitbucket/e2e/snapshot_tables/accounts.csv     |  4 +--
 .../dora/tasks/change_lead_time_calculator.go      |  2 ++
 backend/plugins/github/tasks/account_convertor.go  |  4 +++
 .../linear/e2e/snapshot_tables/accounts.csv        |  8 ++---
 .../sonarqube/e2e/snapshot_tables/accounts.csv     |  6 ++--
 .../plugins/zentao/e2e/snapshot_tables/users.csv   | 26 +++++++-------
 9 files changed, 59 insertions(+), 36 deletions(-)

diff --git a/backend/core/models/domainlayer/crossdomain/account.go 
b/backend/core/models/domainlayer/crossdomain/account.go
index eba65fd63..e8ae63a1e 100644
--- a/backend/core/models/domainlayer/crossdomain/account.go
+++ b/backend/core/models/domainlayer/crossdomain/account.go
@@ -18,8 +18,9 @@ limitations under the License.
 package crossdomain
 
 import (
-       "github.com/apache/incubator-devlake/core/models/domainlayer"
        "time"
+
+       "github.com/apache/incubator-devlake/core/models/domainlayer"
 )
 
 type Account struct {
@@ -31,6 +32,7 @@ type Account struct {
        Organization string `gorm:"type:varchar(255)"`
        CreatedDate  *time.Time
        Status       int
+       IsBot        bool `gorm:"default:false"`
 }
 
 func (Account) TableName() string {
diff --git a/backend/core/models/domainlayer/crossdomain/account.go 
b/backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go
similarity index 50%
copy from backend/core/models/domainlayer/crossdomain/account.go
copy to backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go
index eba65fd63..6830e18a6 100644
--- a/backend/core/models/domainlayer/crossdomain/account.go
+++ b/backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go
@@ -15,24 +15,38 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package crossdomain
+package migrationscripts
 
 import (
-       "github.com/apache/incubator-devlake/core/models/domainlayer"
-       "time"
+       "github.com/apache/incubator-devlake/core/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/plugin"
 )
 
-type Account struct {
-       domainlayer.DomainEntity
-       Email        string `gorm:"type:varchar(255)"`
-       FullName     string `gorm:"type:varchar(255)"`
-       UserName     string `gorm:"type:varchar(255)"`
-       AvatarUrl    string `gorm:"type:varchar(255)"`
-       Organization string `gorm:"type:varchar(255)"`
-       CreatedDate  *time.Time
-       Status       int
+var _ plugin.MigrationScript = (*addIsBotToAccounts)(nil)
+
+type account20260720 struct {
+       IsBot bool `gorm:"default:false"`
 }
 
-func (Account) TableName() string {
+func (account20260720) TableName() string {
        return "accounts"
 }
+
+type addIsBotToAccounts struct{}
+
+func (*addIsBotToAccounts) Up(basicRes context.BasicRes) errors.Error {
+       db := basicRes.GetDal()
+       if err := db.AutoMigrate(&account20260720{}); err != nil {
+               return err
+       }
+       return nil
+}
+
+func (*addIsBotToAccounts) Version() uint64 {
+       return 20260720120000
+}
+
+func (*addIsBotToAccounts) Name() string {
+       return "add is_bot to accounts so bot/automation activity can be 
excluded from metrics, according to #8974"
+}
diff --git a/backend/core/models/migrationscripts/register.go 
b/backend/core/models/migrationscripts/register.go
index 9dd5e762b..69ab4d673 100644
--- a/backend/core/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/register.go
@@ -148,6 +148,7 @@ func All() []plugin.MigrationScript {
                new(changeIssueComponentToText),
                new(changeCqIssueCodeBlocksComponentToText),
                new(addCqProjectMetricsHistory),
+               new(addIsBotToAccounts),
                new(addSprintVelocityFields),
        }
 }
diff --git a/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv 
b/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv
index 8ff11005f..ae3de7a57 100644
--- a/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv
+++ b/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv
@@ -1,2 +1,2 @@
-id,email,full_name,user_name,avatar_url,organization,created_date,status
-bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,,teoiaoe,,https://secure.gravatar.com/avatar/d37e5c64cd007c5c20654af064c3f410?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FT-0.png,,,0
+id,email,full_name,user_name,avatar_url,organization,created_date,status,is_bot
+bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,,teoiaoe,,https://secure.gravatar.com/avatar/d37e5c64cd007c5c20654af064c3f410?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FT-0.png,,,0,0
diff --git a/backend/plugins/dora/tasks/change_lead_time_calculator.go 
b/backend/plugins/dora/tasks/change_lead_time_calculator.go
index 9861a5c7a..57e5b7309 100644
--- a/backend/plugins/dora/tasks/change_lead_time_calculator.go
+++ b/backend/plugins/dora/tasks/change_lead_time_calculator.go
@@ -248,7 +248,9 @@ func batchFetchFirstReviews(projectName string, db dal.Dal) 
(map[string]*code.Pu
                        SELECT prc2.pull_request_id, MIN(prc2.created_date) as 
min_date
                        FROM pull_request_comments prc2
                        INNER JOIN pull_requests pr2 ON pr2.id = 
prc2.pull_request_id
+                       LEFT JOIN accounts acc2 ON acc2.id = prc2.account_id
                        WHERE (pr2.author_id IS NULL OR pr2.author_id = '' OR 
prc2.account_id != pr2.author_id)
+                       AND COALESCE(acc2.is_bot, false) = false
                        GROUP BY prc2.pull_request_id
                ) first_reviews ON prc.pull_request_id = 
first_reviews.pull_request_id
                AND prc.created_date = first_reviews.min_date`),
diff --git a/backend/plugins/github/tasks/account_convertor.go 
b/backend/plugins/github/tasks/account_convertor.go
index 3d4db97d1..310780fc7 100644
--- a/backend/plugins/github/tasks/account_convertor.go
+++ b/backend/plugins/github/tasks/account_convertor.go
@@ -41,6 +41,7 @@ type repoAccountForConvert struct {
        Name      string
        Email     string
        AvatarUrl string
+       Type      string
        common.NoPKModel
 }
 
@@ -96,6 +97,7 @@ func ConvertAccounts(taskCtx plugin.SubTaskContext) 
errors.Error {
                                        COALESCE(ga.name, '') AS name,
                                        COALESCE(ga.email, '') AS email,
                                        COALESCE(ga.avatar_url, '') AS 
avatar_url,
+                                       COALESCE(ga.type, '') AS type,
                                        COALESCE(ga._raw_data_params, 
_tool_github_repo_accounts._raw_data_params) AS _raw_data_params,
                                        COALESCE(ga._raw_data_table, 
_tool_github_repo_accounts._raw_data_table) AS _raw_data_table,
                                        COALESCE(ga._raw_data_id, 
_tool_github_repo_accounts._raw_data_id) AS _raw_data_id,
@@ -145,6 +147,7 @@ func ConvertAccounts(taskCtx plugin.SubTaskContext) 
errors.Error {
                                        orgStr = orgStr[:255]
                                }
                        }
+                       isBot := githubUser.Type == "Bot" || 
strings.HasSuffix(githubUser.Login, "[bot]")
 
                        domainUser := &crossdomain.Account{
                                DomainEntity: domainlayer.DomainEntity{Id: 
accountIdGen.Generate(data.Options.ConnectionId, githubUser.Id)},
@@ -153,6 +156,7 @@ func ConvertAccounts(taskCtx plugin.SubTaskContext) 
errors.Error {
                                UserName:     githubUser.Login,
                                AvatarUrl:    githubUser.AvatarUrl,
                                Organization: orgStr,
+                               IsBot:        isBot,
                        }
                        return []interface{}{
                                domainUser,
diff --git a/backend/plugins/linear/e2e/snapshot_tables/accounts.csv 
b/backend/plugins/linear/e2e/snapshot_tables/accounts.csv
index 12c2066d1..5e4022915 100644
--- a/backend/plugins/linear/e2e/snapshot_tables/accounts.csv
+++ b/backend/plugins/linear/e2e/snapshot_tables/accounts.csv
@@ -1,4 +1,4 @@
-id,email,full_name,user_name,avatar_url,organization,created_date,status
-linear:LinearAccount:1:user-1,[email protected],Alice 
Anderson,alice,https://linear.app/avatars/alice.png,,,1
-linear:LinearAccount:1:user-2,[email protected],Bob 
Brown,bob,https://linear.app/avatars/bob.png,,,1
-linear:LinearAccount:1:user-3,[email protected],Carol 
Clark,carol,https://linear.app/avatars/carol.png,,,0
+id,email,full_name,user_name,avatar_url,organization,created_date,status,is_bot
+linear:LinearAccount:1:user-1,[email protected],Alice 
Anderson,alice,https://linear.app/avatars/alice.png,,,1,0
+linear:LinearAccount:1:user-2,[email protected],Bob 
Brown,bob,https://linear.app/avatars/bob.png,,,1,0
+linear:LinearAccount:1:user-3,[email protected],Carol 
Clark,carol,https://linear.app/avatars/carol.png,,,0,0
diff --git a/backend/plugins/sonarqube/e2e/snapshot_tables/accounts.csv 
b/backend/plugins/sonarqube/e2e/snapshot_tables/accounts.csv
index f30153ddf..43c28055f 100644
--- a/backend/plugins/sonarqube/e2e/snapshot_tables/accounts.csv
+++ b/backend/plugins/sonarqube/e2e/snapshot_tables/accounts.csv
@@ -1,3 +1,3 @@
-id,email,full_name,user_name,avatar_url,organization,created_date,status
-sonarqube:SonarqubeAccount:2:admin2,[email protected],Administrator2,admin2,,,,0
-sonarqube:SonarqubeAccount:2:admin3,,Administrator3,admin3,,,,0
+id,email,full_name,user_name,avatar_url,organization,created_date,status,is_bot
+sonarqube:SonarqubeAccount:2:admin2,[email protected],Administrator2,admin2,,,,0,0
+sonarqube:SonarqubeAccount:2:admin3,,Administrator3,admin3,,,,0,0
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/users.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/users.csv
index 33e6f173d..1b8c8b6db 100644
--- a/backend/plugins/zentao/e2e/snapshot_tables/users.csv
+++ b/backend/plugins/zentao/e2e/snapshot_tables/users.csv
@@ -1,13 +1,13 @@
-id,email,full_name,user_name,avatar_url,organization,created_date,status
-zentao:ZentaoAccount:1:1,,devlake,devlake,,zentao:ZentaoDepartment:1:0,,0
-zentao:ZentaoAccount:1:10,,测试经理,testManager,,zentao:ZentaoDepartment:1:1,,0
-zentao:ZentaoAccount:1:11,,产品经理,productManager,,zentao:ZentaoDepartment:1:5,,0
-zentao:ZentaoAccount:1:12,,devlake,devlake,,zentao:ZentaoDepartment:1:0,,0
-zentao:ZentaoAccount:1:2,,产品经理,productManager,,zentao:ZentaoDepartment:1:5,,0
-zentao:ZentaoAccount:1:3,,项目经理,projectManager,,zentao:ZentaoDepartment:1:6,,0
-zentao:ZentaoAccount:1:4,,开发甲,dev1,,zentao:ZentaoDepartment:1:2,,0
-zentao:ZentaoAccount:1:5,,开发乙,dev2,,zentao:ZentaoDepartment:1:2,,0
-zentao:ZentaoAccount:1:6,,开发丙,dev3,,zentao:ZentaoDepartment:1:2,,0
-zentao:ZentaoAccount:1:7,,测试甲,tester1,,zentao:ZentaoDepartment:1:3,,0
-zentao:ZentaoAccount:1:8,,测试乙,tester2,,zentao:ZentaoDepartment:1:3,,0
-zentao:ZentaoAccount:1:9,,测试丙,tester3,,zentao:ZentaoDepartment:1:3,,0
+id,email,full_name,user_name,avatar_url,organization,created_date,status,is_bot
+zentao:ZentaoAccount:1:1,,devlake,devlake,,zentao:ZentaoDepartment:1:0,,0,0
+zentao:ZentaoAccount:1:10,,测试经理,testManager,,zentao:ZentaoDepartment:1:1,,0,0
+zentao:ZentaoAccount:1:11,,产品经理,productManager,,zentao:ZentaoDepartment:1:5,,0,0
+zentao:ZentaoAccount:1:12,,devlake,devlake,,zentao:ZentaoDepartment:1:0,,0,0
+zentao:ZentaoAccount:1:2,,产品经理,productManager,,zentao:ZentaoDepartment:1:5,,0,0
+zentao:ZentaoAccount:1:3,,项目经理,projectManager,,zentao:ZentaoDepartment:1:6,,0,0
+zentao:ZentaoAccount:1:4,,开发甲,dev1,,zentao:ZentaoDepartment:1:2,,0,0
+zentao:ZentaoAccount:1:5,,开发乙,dev2,,zentao:ZentaoDepartment:1:2,,0,0
+zentao:ZentaoAccount:1:6,,开发丙,dev3,,zentao:ZentaoDepartment:1:2,,0,0
+zentao:ZentaoAccount:1:7,,测试甲,tester1,,zentao:ZentaoDepartment:1:3,,0,0
+zentao:ZentaoAccount:1:8,,测试乙,tester2,,zentao:ZentaoDepartment:1:3,,0,0
+zentao:ZentaoAccount:1:9,,测试丙,tester3,,zentao:ZentaoDepartment:1:3,,0,0

Reply via email to