From 0c4deb43647b2e47a72062d3a9038293bb959de9 Mon Sep 17 00:00:00 2001 From: bramhanandlingala Date: Mon, 20 Jul 2026 16:43:58 +0530 Subject: [PATCH 1/2] feat(dora): exclude bot/automation accounts from PR Pickup Time calculation --- .../models/domainlayer/crossdomain/account.go | 4 ++- .../20260720_add_is_bot_to_accounts.go | 35 +++++++++++++++++++ .../core/models/migrationscripts/register.go | 1 + .../dora/tasks/change_lead_time_calculator.go | 2 ++ .../plugins/github/tasks/account_convertor.go | 4 +++ 5 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go diff --git a/backend/core/models/domainlayer/crossdomain/account.go b/backend/core/models/domainlayer/crossdomain/account.go index eba65fd6303..e8ae63a1ebd 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/migrationscripts/20260720_add_is_bot_to_accounts.go b/backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go new file mode 100644 index 00000000000..47b53660c36 --- /dev/null +++ b/backend/core/models/migrationscripts/20260720_add_is_bot_to_accounts.go @@ -0,0 +1,35 @@ +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 = (*addIsBotToAccounts)(nil) + +type account20260720 struct { + IsBot bool `gorm:"default:false"` +} + +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 e25363a1b40..9099b32a235 100644 --- a/backend/core/models/migrationscripts/register.go +++ b/backend/core/models/migrationscripts/register.go @@ -148,5 +148,6 @@ func All() []plugin.MigrationScript { new(changeIssueComponentToText), new(changeCqIssueCodeBlocksComponentToText), new(addCqProjectMetricsHistory), + new(addIsBotToAccounts), } } diff --git a/backend/plugins/dora/tasks/change_lead_time_calculator.go b/backend/plugins/dora/tasks/change_lead_time_calculator.go index 9861a5c7a9e..57e5b730968 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 3d4db97d155..310780fc7f6 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, From f783ffe6957a634296b3d1bee0c5c384a93da59a Mon Sep 17 00:00:00 2001 From: bramhanandlingala Date: Wed, 12 Aug 2026 15:19:50 +0530 Subject: [PATCH 2/2] test: add is_bot column to account e2e snapshot fixtures --- .../e2e/snapshot_tables/accounts.csv | 4 +-- .../linear/e2e/snapshot_tables/accounts.csv | 8 +++--- .../e2e/snapshot_tables/accounts.csv | 6 ++--- .../zentao/e2e/snapshot_tables/users.csv | 26 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv b/backend/plugins/bitbucket/e2e/snapshot_tables/accounts.csv index 8ff11005fec..ae3de7a5731 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/linear/e2e/snapshot_tables/accounts.csv b/backend/plugins/linear/e2e/snapshot_tables/accounts.csv index 12c2066d191..5e4022915ae 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,alice@example.com,Alice Anderson,alice,https://linear.app/avatars/alice.png,,,1 -linear:LinearAccount:1:user-2,bob@example.com,Bob Brown,bob,https://linear.app/avatars/bob.png,,,1 -linear:LinearAccount:1:user-3,carol@example.com,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,alice@example.com,Alice Anderson,alice,https://linear.app/avatars/alice.png,,,1,0 +linear:LinearAccount:1:user-2,bob@example.com,Bob Brown,bob,https://linear.app/avatars/bob.png,,,1,0 +linear:LinearAccount:1:user-3,carol@example.com,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 f30153ddf48..43c28055f65 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,aaa@email.com,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,aaa@email.com,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 33e6f173d74..1b8c8b6db68 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