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 d08864432 fix(org): match user and account emails case-insensitively
(#9051)
d08864432 is described below
commit d0886443234c1206bb46aa0d37fddcbc21b1b4b2
Author: vbhanuchander-lang <[email protected]>
AuthorDate: Sat Aug 15 02:01:17 2026 -0400
fix(org): match user and account emails case-insensitively (#9051)
ConnectUserAccountsExact keyed its lookup map on the raw email address, so
the comparison was case-sensitive. A user imported from users.csv as
[email protected] was not linked to an account recorded as
[email protected], and the account was left unlinked with no
warning: the subtask still reported success.
That matters because the two sides come from different systems. A git
config, a provider profile and a hand-maintained CSV routinely disagree on
the capitalisation of the same address, and email addresses are treated
case-insensitively everywhere else. Once an account is unlinked, everything
attributing through it stops counting towards that user, and the scan is
restricted to accounts not already in user_accounts, so a later run does
not revisit it.
Folds case on both sides of the email comparison. Name matching is left
exactly as it was, since names are not case-insensitive in the same way and
loosening them could create links that are simply wrong.
The org e2e fixtures gain account a11, whose email differs from user U006's
only in case, with a name and login that deliberately match nothing. It
fails before this change (10 rows against 11 expected) and passes after.
---
backend/plugins/org/e2e/raw_tables/accounts.csv | 1 +
backend/plugins/org/e2e/snapshot_tables/user_accounts.csv | 1 +
backend/plugins/org/tasks/user_account.go | 8 ++++++--
3 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/backend/plugins/org/e2e/raw_tables/accounts.csv
b/backend/plugins/org/e2e/raw_tables/accounts.csv
index 6bbdb2a8f..9b1f09eb8 100644
--- a/backend/plugins/org/e2e/raw_tables/accounts.csv
+++ b/backend/plugins/org/e2e/raw_tables/accounts.csv
@@ -1,6 +1,7 @@
"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","email","avatar_url","full_name","user_name","organization","created_date","status"
"a1","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","e1","","n1","n1","","2022-07-10 14:27:43.813",0
"a10","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","e15","","pq","n10","","2022-07-10 14:27:43.813",0
+"a11","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","E6","","zz","zz","","2022-07-10 14:27:43.813",0
"a2","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","e1","","n2","n2","","2022-07-10 14:27:43.813",0
"a3","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","e2","","xyz","n3","","2022-07-10 14:27:43.813",0
"a4","2022-07-10 14:27:43.813","2022-07-10
14:27:43.813","","",0,"","e4","","n4","n4","","2022-07-10 14:27:43.813",0
diff --git a/backend/plugins/org/e2e/snapshot_tables/user_accounts.csv
b/backend/plugins/org/e2e/snapshot_tables/user_accounts.csv
index 4e89a0811..1e66cceb9 100644
--- a/backend/plugins/org/e2e/snapshot_tables/user_accounts.csv
+++ b/backend/plugins/org/e2e/snapshot_tables/user_accounts.csv
@@ -1,6 +1,7 @@
account_id,user_id,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
a1,U111,,,,
a10,U010,,,0,
+a11,U006,,,0,
a2,U112,,,,
a3,U113,,,,
a4,U004,,,0,
diff --git a/backend/plugins/org/tasks/user_account.go
b/backend/plugins/org/tasks/user_account.go
index 45f187e3b..30cd37b76 100644
--- a/backend/plugins/org/tasks/user_account.go
+++ b/backend/plugins/org/tasks/user_account.go
@@ -24,6 +24,7 @@ import (
"github.com/apache/incubator-devlake/core/plugin"
"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
"reflect"
+ "strings"
)
var ConnectUserAccountsExactMeta = plugin.SubTaskMeta{
@@ -42,11 +43,14 @@ func ConnectUserAccountsExact(taskCtx
plugin.SubTaskContext) errors.Error {
if err != nil {
return err
}
+ // Email addresses are compared case-insensitively: a corporate git
config and a provider
+ // profile routinely record the same address with different
capitalisation, and matching them
+ // exactly silently drops links that should be made.
emails := make(map[string]string)
names := make(map[string]string)
for _, user := range users {
if user.Email != "" {
- emails[user.Email] = user.Id
+ emails[strings.ToLower(user.Email)] = user.Id
}
if user.Name != "" {
names[user.Name] = user.Id
@@ -76,7 +80,7 @@ func ConnectUserAccountsExact(taskCtx plugin.SubTaskContext)
errors.Error {
Convert: func(inputRow interface{}) ([]interface{},
errors.Error) {
account := inputRow.(*crossdomain.Account)
- if userId, ok := emails[account.Email]; account.Email
!= "" && ok {
+ if userId, ok :=
emails[strings.ToLower(account.Email)]; account.Email != "" && ok {
return []interface{}{
&crossdomain.UserAccount{
UserId: userId,