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