feat: support multiple assignees for issues (#5321)

* feat: support multiple assignees for issue

* fix: gitlab e2e
diff --git a/backend/plugins/bitbucket/e2e/issue_test.go b/backend/plugins/bitbucket/e2e/issue_test.go
index cae94e3..2013e62 100644
--- a/backend/plugins/bitbucket/e2e/issue_test.go
+++ b/backend/plugins/bitbucket/e2e/issue_test.go
@@ -20,6 +20,7 @@
 import (
 	"testing"
 
+	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
 	"github.com/apache/incubator-devlake/plugins/bitbucket/impl"
@@ -96,6 +97,7 @@
 	// verify issue conversion
 	dataflowTester.FlushTabler(&ticket.Issue{})
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertIssuesMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.Issue{},
@@ -135,5 +137,8 @@
 			"issue_id",
 		),
 	)
-
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/bitbucket/e2e/snapshot_tables/issue_assignees.csv b/backend/plugins/bitbucket/e2e/snapshot_tables/issue_assignees.csv
new file mode 100644
index 0000000..0bbdfa9
--- /dev/null
+++ b/backend/plugins/bitbucket/e2e/snapshot_tables/issue_assignees.csv
@@ -0,0 +1,13 @@
+issue_id,assignee_id,assignee_name
+bitbucket:BitbucketIssue:1:likyh/likyhphp:1,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:10,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:11,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:14,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:19,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:20,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:23,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:24,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:29,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:30,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:6,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
+bitbucket:BitbucketIssue:1:likyh/likyhphp:9,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe
diff --git a/backend/plugins/bitbucket/e2e/snapshot_tables/issues.csv b/backend/plugins/bitbucket/e2e/snapshot_tables/issues.csv
index 4ab5193..69e58da 100644
--- a/backend/plugins/bitbucket/e2e/snapshot_tables/issues.csv
+++ b/backend/plugins/bitbucket/e2e/snapshot_tables/issues.csv
@@ -2,18 +2,18 @@
 bitbucket:BitbucketIssue:1:likyh/likyhphp:1,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/1,,1,issue test,bitbucket issues test for devants,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,60,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:10,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/10,,10,issue test007,issue test007,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,52,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:11,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/11,,11,issue test008,issue test008,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,51,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:12,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/12,,12,issue test009,issue test009,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,50,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:13,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/13,,13,issue test010,issue test010,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,49,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:12,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/12,,12,issue test009,issue test009,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,50,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:13,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/13,,13,issue test010,issue test010,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,49,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:14,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/14,,14,issue test011,issue test011,,issue,TODO,new,0,,0,,blocker,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,48,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:15,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/15,,15,issue test012,issue test012,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,47,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:16,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/16,,16,issue test013,issue test013,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,46,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:17,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/17,,17,issue test014,issue test014,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,45,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:18,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/18,,18,issue test015,issue test015,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,44,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:15,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/15,,15,issue test012,issue test012,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,47,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:16,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/16,,16,issue test013,issue test013,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,46,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:17,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/17,,17,issue test014,issue test014,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,45,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:18,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/18,,18,issue test015,issue test015,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,44,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:19,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/19,,19,issue test016,issue test016,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,43,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:2,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/2,,2,add bitbucket issue,feafejo,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,59,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:2,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/2,,2,add bitbucket issue,feafejo,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,59,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:20,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/20,,20,issue test017,issue test017,,issue,TODO,new,0,,0,,blocker,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,42,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:21,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/21,,21,issue test018,issue test018,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,41,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:22,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/22,,22,issue test019,issue test019,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,40,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:21,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/21,,21,issue test018,issue test018,,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,41,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:22,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/22,,22,issue test019,issue test019,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,40,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:23,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/23,,23,issue test020,issue test020,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,39,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:24,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/24,,24,issue test021,"issue test021 ijeiawgoeive/faveevaeviaevfejaofejfioejaiofe_veavejiovajgiorejoifjrogiorejieafajejaojoejvgioriovioraivjairobnrnoivaiorjbiorjiojaeiorjvioejroivjaoijeriojiaojioeefjafioejfiojeiofawefwefoiwefiwoiefweefwoefuwhufirfrw._
 
@@ -38,18 +38,18 @@
 |  |  |  |
 |  |  |  |
 
-‌",,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,36,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:26,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/26,,26,issue test022,issue test022,,issue,TODO,new,0,,0,,blocker,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,35,
+‌",,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,36,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:26,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/26,,26,issue test022,issue test022,,issue,TODO,new,0,,0,,blocker,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,35,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:27,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/27,,27,issue test024,"issue test024v  aejnoafoeiogoiae
 
-qwofjeoiwjf",,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,31,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:28,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/28,,28,issue test025,issue test025,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,34,
+qwofjeoiwjf",,issue,TODO,new,0,,0,,trivial,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,31,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:28,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/28,,28,issue test025,issue test025,,issue,TODO,new,0,,0,,minor,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,34,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:29,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/29,,29,issue test026,issue test026,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,33,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:3,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/3,,3,bitbucket test,"efaegjeoaijefioaegrjoeior,af enfaoiee vioea.,,.wew",,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,58,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:3,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/3,,3,bitbucket test,"efaegjeoaijefioaegrjoeior,af enfaoiee vioea.,,.wew",,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,58,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:30,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/30,,30,issue test027,issue test027,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,32,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:4,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/4,,4,issue test001,Bitbucket issue test001,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,57,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:5,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/5,,5,issue test002,issue test002,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,56,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:4,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/4,,4,issue test001,Bitbucket issue test001,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,57,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:5,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/5,,5,issue test002,issue test002,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,56,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:6,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/6,,6,issue test003,issue test 003,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,37,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:7,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/7,,7,issue test004,issue test004,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,55,
-bitbucket:BitbucketIssue:1:likyh/likyhphp:8,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/8,,8,issue test005,issue test005,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,54,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:7,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/7,,7,issue test004,issue test004,,issue,TODO,new,0,,0,,major,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,55,
+bitbucket:BitbucketIssue:1:likyh/likyhphp:8,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/8,,8,issue test005,issue test005,,issue,TODO,new,0,,0,,critical,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,"",,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,54,
 bitbucket:BitbucketIssue:1:likyh/likyhphp:9,https://api.bitbucket.org/2.0/repositories/likyh/likyhphp/issues/9,,9,issue test006,issue test006,,issue,TODO,new,0,,0,,blocker,0,0,0,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,bitbucket:BitbucketAccount:1:62abf394192edb006fa0e8cf,teoiaoe,,,"{""ConnectionId"":1,""FullName"":""likyh/likyhphp""}",_raw_bitbucket_api_issues,53,
diff --git a/backend/plugins/bitbucket/tasks/issue_convertor.go b/backend/plugins/bitbucket/tasks/issue_convertor.go
index 4975c82..04c4f59 100644
--- a/backend/plugins/bitbucket/tasks/issue_convertor.go
+++ b/backend/plugins/bitbucket/tasks/issue_convertor.go
@@ -80,22 +80,28 @@
 				Severity:        issue.Severity,
 				Component:       issue.Component,
 			}
-			if issue.AssigneeName != "" {
+			var result []interface{}
+			if issue.AssigneeId != "" {
 				domainIssue.AssigneeName = issue.AssigneeName
 				domainIssue.AssigneeId = accountIdGen.Generate(data.Options.ConnectionId, issue.AssigneeId)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainIssue.Id,
+					AssigneeId:   domainIssue.AssigneeId,
+					AssigneeName: domainIssue.AssigneeName,
+				}
+				result = append(result, issueAssignee)
 			}
-			if issue.AuthorName != "" {
+			if issue.AuthorId != "" {
 				domainIssue.CreatorName = issue.AuthorName
 				domainIssue.CreatorId = accountIdGen.Generate(data.Options.ConnectionId, issue.AuthorId)
 			}
+			result = append(result, domainIssue)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: boardIdGen.Generate(data.Options.ConnectionId, repoId),
 				IssueId: domainIssue.Id,
 			}
-			return []interface{}{
-				domainIssue,
-				boardIssue,
-			}, nil
+			result = append(result, boardIssue)
+			return result, nil
 		},
 	})
 	if err != nil {
diff --git a/backend/plugins/gitee/tasks/issue_convertor.go b/backend/plugins/gitee/tasks/issue_convertor.go
index 8c3890f..8cf0f86 100644
--- a/backend/plugins/gitee/tasks/issue_convertor.go
+++ b/backend/plugins/gitee/tasks/issue_convertor.go
@@ -18,6 +18,8 @@
 package tasks
 
 import (
+	"reflect"
+
 	"github.com/apache/incubator-devlake/core/dal"
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
@@ -26,7 +28,6 @@
 	"github.com/apache/incubator-devlake/core/plugin"
 	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	"github.com/apache/incubator-devlake/plugins/gitee/models"
-	"reflect"
 )
 
 var ConvertIssuesMeta = plugin.SubTaskMeta{
@@ -70,9 +71,7 @@
 				Description:     issue.Body,
 				Priority:        issue.Priority,
 				Type:            issue.Type,
-				AssigneeId:      accountIdGen.Generate(data.Options.ConnectionId, issue.AssigneeId),
 				AssigneeName:    issue.AssigneeName,
-				CreatorId:       accountIdGen.Generate(data.Options.ConnectionId, issue.AuthorId),
 				CreatorName:     issue.AuthorName,
 				LeadTimeMinutes: int64(issue.LeadTimeMinutes),
 				Url:             issue.Url,
@@ -82,19 +81,31 @@
 				Severity:        issue.Severity,
 				Component:       issue.Component,
 			}
+			var result []interface{}
+			if issue.AssigneeId != 0 {
+				domainIssue.AssigneeId = accountIdGen.Generate(data.Options.ConnectionId, issue.AssigneeId)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainIssue.Id,
+					AssigneeId:   domainIssue.AssigneeId,
+					AssigneeName: domainIssue.AssigneeName,
+				}
+				result = append(result, issueAssignee)
+			}
+			if issue.AuthorId != 0 {
+				domainIssue.CreatorId = accountIdGen.Generate(data.Options.ConnectionId, issue.AuthorId)
+			}
 			if issue.State == "closed" {
 				domainIssue.Status = ticket.DONE
 			} else {
 				domainIssue.Status = ticket.TODO
 			}
+			result = append(result, domainIssue)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: boardIdGen.Generate(data.Options.ConnectionId, repoId),
 				IssueId: domainIssue.Id,
 			}
-			return []interface{}{
-				domainIssue,
-				boardIssue,
-			}, nil
+			result = append(result, boardIssue)
+			return result, nil
 		},
 	})
 	if err != nil {
diff --git a/backend/plugins/github/e2e/issue_assignee_test.go b/backend/plugins/github/e2e/issue_assignee_test.go
new file mode 100644
index 0000000..423d6a1
--- /dev/null
+++ b/backend/plugins/github/e2e/issue_assignee_test.go
@@ -0,0 +1,49 @@
+/*
+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 e2e
+
+import (
+	"github.com/apache/incubator-devlake/plugins/github/models"
+	"testing"
+
+	"github.com/apache/incubator-devlake/core/models/common"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/github/impl"
+	"github.com/apache/incubator-devlake/plugins/github/tasks"
+)
+
+func TestIssueAssigneeDataFlow(t *testing.T) {
+	var plugin impl.Github
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "github", plugin)
+
+	taskData := &tasks.GithubTaskData{
+		Options: &tasks.GithubOptions{
+			ConnectionId: 1,
+			GithubId:     1,
+		},
+	}
+
+	dataflowTester.ImportCsvIntoTabler("./snapshot_tables/_tool_github_issue_assignees.csv", &models.GithubIssueAssignee{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
+	dataflowTester.Subtask(tasks.ConvertIssueAssigneeMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/backend/plugins/github/e2e/issue_test.go b/backend/plugins/github/e2e/issue_test.go
index d3d838c..7c001c9 100644
--- a/backend/plugins/github/e2e/issue_test.go
+++ b/backend/plugins/github/e2e/issue_test.go
@@ -57,6 +57,7 @@
 	dataflowTester.FlushTabler(&models.GithubIssue{})
 	dataflowTester.FlushTabler(&models.GithubIssueLabel{})
 	dataflowTester.FlushTabler(&models.GithubRepoAccount{})
+	dataflowTester.FlushTabler(&models.GithubIssueAssignee{})
 	dataflowTester.Subtask(tasks.ExtractApiIssuesMeta, taskData)
 	dataflowTester.VerifyTable(
 		models.GithubIssue{},
diff --git a/backend/plugins/github/e2e/snapshot_tables/_tool_github_issue_assignees.csv b/backend/plugins/github/e2e/snapshot_tables/_tool_github_issue_assignees.csv
new file mode 100644
index 0000000..00df5a6
--- /dev/null
+++ b/backend/plugins/github/e2e/snapshot_tables/_tool_github_issue_assignees.csv
@@ -0,0 +1,10 @@
+connection_id,issue_id,repo_id,assignee_id,assignee_name
+1,1,1,1,name1
+1,1,1,2,name2
+1,1,1,3,name3
+1,1,1,4,name4
+1,1,1,5,name5
+1,1,1,6,name6
+1,1,1,7,name7
+1,1,1,8,name8
+1,1,1,9,name9
\ No newline at end of file
diff --git a/backend/plugins/github/e2e/snapshot_tables/issue_assignees.csv b/backend/plugins/github/e2e/snapshot_tables/issue_assignees.csv
new file mode 100644
index 0000000..208c157
--- /dev/null
+++ b/backend/plugins/github/e2e/snapshot_tables/issue_assignees.csv
@@ -0,0 +1,10 @@
+issue_id,assignee_id,assignee_name
+github:GithubIssue:1:1,github:GithubAccount:1:1,name1
+github:GithubIssue:1:1,github:GithubAccount:1:2,name2
+github:GithubIssue:1:1,github:GithubAccount:1:3,name3
+github:GithubIssue:1:1,github:GithubAccount:1:4,name4
+github:GithubIssue:1:1,github:GithubAccount:1:5,name5
+github:GithubIssue:1:1,github:GithubAccount:1:6,name6
+github:GithubIssue:1:1,github:GithubAccount:1:7,name7
+github:GithubIssue:1:1,github:GithubAccount:1:8,name8
+github:GithubIssue:1:1,github:GithubAccount:1:9,name9
diff --git a/backend/plugins/github/impl/impl.go b/backend/plugins/github/impl/impl.go
index 606ffc8..224c3a5 100644
--- a/backend/plugins/github/impl/impl.go
+++ b/backend/plugins/github/impl/impl.go
@@ -128,6 +128,7 @@
 		tasks.EnrichPullRequestIssuesMeta,
 		tasks.ConvertRepoMeta,
 		tasks.ConvertIssuesMeta,
+		tasks.ConvertIssueAssigneeMeta,
 		tasks.ConvertCommitsMeta,
 		tasks.ConvertIssueLabelsMeta,
 		tasks.ConvertPullRequestCommitsMeta,
diff --git a/backend/plugins/github/models/issue_assignee.go b/backend/plugins/github/models/issue_assignee.go
new file mode 100644
index 0000000..4998198
--- /dev/null
+++ b/backend/plugins/github/models/issue_assignee.go
@@ -0,0 +1,33 @@
+/*
+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 models
+
+import "github.com/apache/incubator-devlake/core/models/common"
+
+type GithubIssueAssignee struct {
+	common.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey"`
+	IssueId      int    `gorm:"primaryKey"`
+	RepoId       int    `gorm:"primaryKey"`
+	AssigneeId   int    `gorm:"primaryKey"`
+	AssigneeName string `gorm:"type:varchar(255)"`
+}
+
+func (GithubIssueAssignee) TableName() string {
+	return "_tool_github_issue_assignees"
+}
diff --git a/backend/plugins/github/models/migrationscripts/20230530_add_issue_assignee.go b/backend/plugins/github/models/migrationscripts/20230530_add_issue_assignee.go
new file mode 100644
index 0000000..1a460b2
--- /dev/null
+++ b/backend/plugins/github/models/migrationscripts/20230530_add_issue_assignee.go
@@ -0,0 +1,52 @@
+/*
+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 migrationscripts
+
+import (
+	"github.com/apache/incubator-devlake/core/context"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+)
+
+type githubIssueAssignee20230530 struct {
+	archived.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey"`
+	IssueId      int    `gorm:"primaryKey"`
+	RepoId       int    `gorm:"primaryKey"`
+	AssigneeId   int    `gorm:"primaryKey"`
+	AssigneeName string `gorm:"type:varchar(255)"`
+}
+
+func (githubIssueAssignee20230530) TableName() string {
+	return "_tool_github_issue_assignees"
+}
+
+type addGithubIssueAssignee struct{}
+
+func (*addGithubIssueAssignee) Up(res context.BasicRes) errors.Error {
+	db := res.GetDal()
+	return db.AutoMigrate(&githubIssueAssignee20230530{})
+}
+
+func (*addGithubIssueAssignee) Version() uint64 {
+	return 20230530161510
+}
+
+func (*addGithubIssueAssignee) Name() string {
+	return "add _tool_github_issue_assignees table"
+}
diff --git a/backend/plugins/github/models/migrationscripts/register.go b/backend/plugins/github/models/migrationscripts/register.go
index bf800e4..518f347 100644
--- a/backend/plugins/github/models/migrationscripts/register.go
+++ b/backend/plugins/github/models/migrationscripts/register.go
@@ -41,5 +41,6 @@
 		new(fixRunNameToText),
 		new(addGithubMultiAuth),
 		new(renameTr2ScopeConfig),
+		new(addGithubIssueAssignee),
 	}
 }
diff --git a/backend/plugins/github/tasks/issue_assignee_convertor.go b/backend/plugins/github/tasks/issue_assignee_convertor.go
new file mode 100644
index 0000000..373bec2
--- /dev/null
+++ b/backend/plugins/github/tasks/issue_assignee_convertor.go
@@ -0,0 +1,83 @@
+/*
+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 (
+	"reflect"
+
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/core/plugin"
+	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/github/models"
+)
+
+var ConvertIssueAssigneeMeta = plugin.SubTaskMeta{
+	Name:             "convertIssueAssignee",
+	EntryPoint:       ConvertIssueAssignee,
+	EnabledByDefault: true,
+	Description:      "Convert tool layer table _tool_github_issue_assignees into  domain layer table issue_assignees",
+	DomainTypes:      []string{plugin.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertIssueAssignee(taskCtx plugin.SubTaskContext) errors.Error {
+	db := taskCtx.GetDal()
+	data := taskCtx.GetData().(*GithubTaskData)
+	repoId := data.Options.GithubId
+
+	cursor, err := db.Cursor(
+		dal.From(&models.GithubIssueAssignee{}),
+		dal.Where("repo_id = ? and connection_id=?", repoId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+
+	issueIdGen := didgen.NewDomainIdGenerator(&models.GithubIssue{})
+	accountIdGen := didgen.NewDomainIdGenerator(&models.GithubAccount{})
+
+	converter, err := api.NewDataConverter(api.DataConverterArgs{
+		RawDataSubTaskArgs: api.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: GithubApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				Name:         data.Options.Name,
+			},
+			Table: RAW_ISSUE_TABLE,
+		},
+		InputRowType: reflect.TypeOf(models.GithubIssueAssignee{}),
+		Input:        cursor,
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			githubIssueAssignee := inputRow.(*models.GithubIssueAssignee)
+			issueAssignee := &ticket.IssueAssignee{
+				IssueId:      issueIdGen.Generate(data.Options.ConnectionId, githubIssueAssignee.IssueId),
+				AssigneeId:   accountIdGen.Generate(data.Options.ConnectionId, githubIssueAssignee.AssigneeId),
+				AssigneeName: githubIssueAssignee.AssigneeName,
+			}
+			return []interface{}{issueAssignee}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return converter.Execute()
+}
diff --git a/backend/plugins/github/tasks/issue_extractor.go b/backend/plugins/github/tasks/issue_extractor.go
index 7bc2a6f..29f6c43 100644
--- a/backend/plugins/github/tasks/issue_extractor.go
+++ b/backend/plugins/github/tasks/issue_extractor.go
@@ -51,7 +51,8 @@
 	Labels []struct {
 		Name string `json:"name"`
 	} `json:"labels"`
-	Assignee  *GithubAccountResponse
+	Assignee  *GithubAccountResponse  `json:"assignee"`
+	Assignees []GithubAccountResponse `json:"assignees"`
 	User      *GithubAccountResponse
 	Milestone *struct {
 		Id int
@@ -129,6 +130,16 @@
 				}
 				results = append(results, relatedUser)
 			}
+			for _, assignee := range body.Assignees {
+				issueAssignee := &models.GithubIssueAssignee{
+					ConnectionId: githubIssue.ConnectionId,
+					IssueId:      githubIssue.GithubId,
+					RepoId:       githubIssue.RepoId,
+					AssigneeId:   assignee.Id,
+					AssigneeName: assignee.Login,
+				}
+				results = append(results, issueAssignee)
+			}
 			if body.User != nil {
 				githubIssue.AuthorId = body.User.Id
 				githubIssue.AuthorName = body.User.Login
diff --git a/backend/plugins/github_graphql/tasks/issue_collector.go b/backend/plugins/github_graphql/tasks/issue_collector.go
index 535dc01..3379d86 100644
--- a/backend/plugins/github_graphql/tasks/issue_collector.go
+++ b/backend/plugins/github_graphql/tasks/issue_collector.go
@@ -171,6 +171,16 @@
 					}
 					results = append(results, relatedUser)
 				}
+				for _, assignee := range issue.AssigneeList.Assignees {
+					issueAssignee := &models.GithubIssueAssignee{
+						ConnectionId: githubIssue.ConnectionId,
+						IssueId:      githubIssue.GithubId,
+						RepoId:       githubIssue.RepoId,
+						AssigneeId:   assignee.Id,
+						AssigneeName: assignee.Login,
+					}
+					results = append(results, issueAssignee)
+				}
 			}
 			if isFinish {
 				return results, helper.ErrFinishCollect
diff --git a/backend/plugins/gitlab/e2e/issues_test.go b/backend/plugins/gitlab/e2e/issues_test.go
index 4f10674..0553af3 100644
--- a/backend/plugins/gitlab/e2e/issues_test.go
+++ b/backend/plugins/gitlab/e2e/issues_test.go
@@ -47,6 +47,7 @@
 	dataflowTester.FlushTabler(&models.GitlabIssue{})
 	dataflowTester.FlushTabler(&models.GitlabAccount{})
 	dataflowTester.FlushTabler(&models.GitlabIssueLabel{})
+	dataflowTester.FlushTabler(&models.GitlabIssueAssignee{})
 	dataflowTester.Subtask(tasks.ExtractApiIssuesMeta, taskData)
 	dataflowTester.VerifyTable(
 		models.GitlabIssue{},
diff --git a/backend/plugins/gitlab/e2e/snapshot_tables/_tool_gitlab_issue_assignees.csv b/backend/plugins/gitlab/e2e/snapshot_tables/_tool_gitlab_issue_assignees.csv
new file mode 100644
index 0000000..924b48a
--- /dev/null
+++ b/backend/plugins/gitlab/e2e/snapshot_tables/_tool_gitlab_issue_assignees.csv
@@ -0,0 +1,6 @@
+connection_id,gitlab_id,project_id,assignee_id,assignee_name
+1,22097949,12345678,2295562,emilie
+1,24172103,12345678,5212782,m_walker
+1,32460839,12345678,2295562,emilie
+1,33004486,12345678,4189780,mpeychet_
+1,108497826,12345678,9386100,chrissharp
diff --git a/backend/plugins/gitlab/e2e/snapshot_tables/issue_assignees.csv b/backend/plugins/gitlab/e2e/snapshot_tables/issue_assignees.csv
new file mode 100644
index 0000000..66826f9
--- /dev/null
+++ b/backend/plugins/gitlab/e2e/snapshot_tables/issue_assignees.csv
@@ -0,0 +1,6 @@
+issue_id,assignee_id,assignee_name
+gitlab:GitlabIssue:1:108497826,gitlab:GitlabAccount:1:9386100,chrissharp
+gitlab:GitlabIssue:1:22097949,gitlab:GitlabAccount:1:2295562,emilie
+gitlab:GitlabIssue:1:24172103,gitlab:GitlabAccount:1:5212782,m_walker
+gitlab:GitlabIssue:1:32460839,gitlab:GitlabAccount:1:2295562,emilie
+gitlab:GitlabIssue:1:33004486,gitlab:GitlabAccount:1:4189780,mpeychet_
diff --git a/backend/plugins/gitlab/impl/impl.go b/backend/plugins/gitlab/impl/impl.go
index 4343b7a..36fca3d 100644
--- a/backend/plugins/gitlab/impl/impl.go
+++ b/backend/plugins/gitlab/impl/impl.go
@@ -120,6 +120,7 @@
 		tasks.ConvertMrCommentMeta,
 		tasks.ConvertApiMrCommitsMeta,
 		tasks.ConvertIssuesMeta,
+		tasks.ConvertIssueAssigneeMeta,
 		tasks.ConvertIssueLabelsMeta,
 		tasks.ConvertMrLabelsMeta,
 		tasks.ConvertCommitsMeta,
diff --git a/backend/plugins/gitlab/models/issue_assignee.go b/backend/plugins/gitlab/models/issue_assignee.go
new file mode 100644
index 0000000..d00c3cc
--- /dev/null
+++ b/backend/plugins/gitlab/models/issue_assignee.go
@@ -0,0 +1,33 @@
+/*
+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 models
+
+import "github.com/apache/incubator-devlake/core/models/common"
+
+type GitlabIssueAssignee struct {
+	common.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey"`
+	GitlabId     int    `gorm:"primaryKey"`
+	ProjectId    int    `gorm:"primaryKey"`
+	AssigneeId   int    `gorm:"primaryKey"`
+	AssigneeName string `gorm:"type:varchar(255)"`
+}
+
+func (GitlabIssueAssignee) TableName() string {
+	return "_tool_gitlab_issue_assignees"
+}
diff --git a/backend/plugins/gitlab/tasks/issue_assignee_convertor.go b/backend/plugins/gitlab/tasks/issue_assignee_convertor.go
new file mode 100644
index 0000000..9e7366e
--- /dev/null
+++ b/backend/plugins/gitlab/tasks/issue_assignee_convertor.go
@@ -0,0 +1,75 @@
+/*
+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 (
+	"reflect"
+
+	"github.com/apache/incubator-devlake/core/dal"
+	"github.com/apache/incubator-devlake/core/errors"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/core/plugin"
+	helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+	"github.com/apache/incubator-devlake/plugins/gitlab/models"
+)
+
+var ConvertIssueAssigneeMeta = plugin.SubTaskMeta{
+	Name:             "convertIssueAssignee",
+	EntryPoint:       ConvertIssueAssignee,
+	EnabledByDefault: true,
+	Description:      "Convert tool layer table _tool_gitlab_issue_assignees into  domain layer table issue_assignees",
+	DomainTypes:      []string{plugin.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertIssueAssignee(taskCtx plugin.SubTaskContext) errors.Error {
+	db := taskCtx.GetDal()
+	rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, RAW_ISSUE_TABLE)
+
+	cursor, err := db.Cursor(
+		dal.From(&models.GitlabIssueAssignee{}),
+		dal.Where("project_id = ? and connection_id=?", data.Options.ProjectId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+
+	issueIdGen := didgen.NewDomainIdGenerator(&models.GitlabIssue{})
+	accountIdGen := didgen.NewDomainIdGenerator(&models.GitlabAccount{})
+
+	converter, err := helper.NewDataConverter(helper.DataConverterArgs{
+		RawDataSubTaskArgs: *rawDataSubTaskArgs,
+		InputRowType:       reflect.TypeOf(models.GitlabIssueAssignee{}),
+		Input:              cursor,
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			input := inputRow.(*models.GitlabIssueAssignee)
+			domainIssueAssignee := &ticket.IssueAssignee{
+				IssueId:      issueIdGen.Generate(data.Options.ConnectionId, input.GitlabId),
+				AssigneeId:   accountIdGen.Generate(data.Options.ConnectionId, input.AssigneeId),
+				AssigneeName: input.AssigneeName,
+			}
+			return []interface{}{domainIssueAssignee}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return converter.Execute()
+}
diff --git a/backend/plugins/gitlab/tasks/issue_extractor.go b/backend/plugins/gitlab/tasks/issue_extractor.go
index 3152000..793c8ed 100644
--- a/backend/plugins/gitlab/tasks/issue_extractor.go
+++ b/backend/plugins/gitlab/tasks/issue_extractor.go
@@ -256,7 +256,7 @@
 			results = append(results, gitlabIssue)
 
 			for _, v := range body.Assignees {
-				GitlabAssignee := &models.GitlabAccount{
+				assignee := &models.GitlabAccount{
 					ConnectionId: data.Options.ConnectionId,
 					Username:     v.Username,
 					Name:         v.Name,
@@ -264,7 +264,14 @@
 					AvatarUrl:    v.AvatarUrl,
 					WebUrl:       v.WebUrl,
 				}
-				results = append(results, GitlabAssignee)
+				issueAssignee := &models.GitlabIssueAssignee{
+					ConnectionId: data.Options.ConnectionId,
+					GitlabId:     gitlabIssue.GitlabId,
+					ProjectId:    gitlabIssue.ProjectId,
+					AssigneeId:   v.Id,
+					AssigneeName: v.Username,
+				}
+				results = append(results, assignee, issueAssignee)
 			}
 
 			return results, nil
diff --git a/backend/plugins/jira/e2e/issue_test.go b/backend/plugins/jira/e2e/issue_test.go
index dec15ee..6dfd854 100644
--- a/backend/plugins/jira/e2e/issue_test.go
+++ b/backend/plugins/jira/e2e/issue_test.go
@@ -20,6 +20,7 @@
 import (
 	"testing"
 
+	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
 	"github.com/apache/incubator-devlake/plugins/jira/impl"
@@ -200,6 +201,7 @@
 	// verify issue conversion
 	dataflowTester.FlushTabler(&ticket.Issue{})
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertIssuesMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.Issue{},
@@ -240,4 +242,8 @@
 		"./snapshot_tables/board_issues.csv",
 		[]string{"board_id", "issue_id"},
 	)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/jira/e2e/snapshot_tables/issue_assignees.csv b/backend/plugins/jira/e2e/snapshot_tables/issue_assignees.csv
new file mode 100644
index 0000000..44a39c8
--- /dev/null
+++ b/backend/plugins/jira/e2e/snapshot_tables/issue_assignees.csv
@@ -0,0 +1,31 @@
+issue_id,assignee_id,assignee_name
+jira:JiraIssue:2:10063,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10064,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10065,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10066,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10067,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10068,jira:JiraAccount:2:5ecfbd0c2490cf0c09e2e598,
+jira:JiraIssue:2:10070,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10071,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10072,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10076,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10077,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10078,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10079,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10081,jira:JiraAccount:2:5ecfbd0aaa47a00c1997ea8e,
+jira:JiraIssue:2:10082,jira:JiraAccount:2:5ecfbd0984083c0c12e5af8f,
+jira:JiraIssue:2:10085,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10086,jira:JiraAccount:2:5ecfbd0aaa47a00c1997ea8e,
+jira:JiraIssue:2:10087,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10088,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10089,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10090,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10091,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10092,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10093,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10094,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10095,jira:JiraAccount:2:5ecfbd0984083c0c12e5af8f,
+jira:JiraIssue:2:10096,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
+jira:JiraIssue:2:10097,jira:JiraAccount:2:5ecfbd0a47d31e0c2a15fd87,
+jira:JiraIssue:2:10098,jira:JiraAccount:2:5ecfbd0ba04d9c0c220c18d8,
+jira:JiraIssue:2:10099,jira:JiraAccount:2:5ecfbd0c730ec90c1999cadf,
diff --git a/backend/plugins/jira/tasks/issue_convertor.go b/backend/plugins/jira/tasks/issue_convertor.go
index ffdb0d0..fa7e122 100644
--- a/backend/plugins/jira/tasks/issue_convertor.go
+++ b/backend/plugins/jira/tasks/issue_convertor.go
@@ -113,8 +113,15 @@
 			if jiraIssue.CreatorDisplayName != "" {
 				issue.CreatorName = jiraIssue.CreatorDisplayName
 			}
+			var result []interface{}
 			if jiraIssue.AssigneeAccountId != "" {
 				issue.AssigneeId = accountIdGen.Generate(data.Options.ConnectionId, jiraIssue.AssigneeAccountId)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      issue.Id,
+					AssigneeId:   issue.AssigneeId,
+					AssigneeName: issue.AssigneeName,
+				}
+				result = append(result, issueAssignee)
 			}
 			if jiraIssue.AssigneeDisplayName != "" {
 				issue.AssigneeName = jiraIssue.AssigneeDisplayName
@@ -122,14 +129,13 @@
 			if jiraIssue.ParentId != 0 {
 				issue.ParentIssueId = issueIdGen.Generate(data.Options.ConnectionId, jiraIssue.ParentId)
 			}
+			result = append(result, issue)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: boardId,
 				IssueId: issue.Id,
 			}
-			return []interface{}{
-				issue,
-				boardIssue,
-			}, nil
+			result = append(result, boardIssue)
+			return result, nil
 		},
 	})
 	if err != nil {
diff --git a/backend/plugins/pagerduty/e2e/incident_test.go b/backend/plugins/pagerduty/e2e/incident_test.go
index 6076b8a..fd1536d 100644
--- a/backend/plugins/pagerduty/e2e/incident_test.go
+++ b/backend/plugins/pagerduty/e2e/incident_test.go
@@ -91,6 +91,7 @@
 		},
 	)
 	dataflowTester.FlushTabler(&ticket.Issue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertIncidentsMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(
 		ticket.Issue{},
@@ -100,4 +101,8 @@
 			IgnoreFields: []string{"original_project"},
 		},
 	)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/pagerduty/e2e/snapshot_tables/issue_assignees.csv b/backend/plugins/pagerduty/e2e/snapshot_tables/issue_assignees.csv
new file mode 100644
index 0000000..c2763d1
--- /dev/null
+++ b/backend/plugins/pagerduty/e2e/snapshot_tables/issue_assignees.csv
@@ -0,0 +1,4 @@
+issue_id,assignee_id,assignee_name
+pagerduty:Incident:1:4,P25K520,Kian Amini
+pagerduty:Incident:1:4,PQYACO3,Keon Amini
+pagerduty:Incident:1:5,PQYACO3,Keon Amini
diff --git a/backend/plugins/pagerduty/tasks/incidents_converter.go b/backend/plugins/pagerduty/tasks/incidents_converter.go
index 7547bf5..e8448af 100644
--- a/backend/plugins/pagerduty/tasks/incidents_converter.go
+++ b/backend/plugins/pagerduty/tasks/incidents_converter.go
@@ -103,19 +103,25 @@
 				LeadTimeMinutes: leadTime,
 				Priority:        string(incident.Urgency),
 			}
+			var result []interface{}
 			if combined.User != nil {
 				domainIssue.AssigneeId = combined.User.Id
 				domainIssue.AssigneeName = combined.User.Name
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainIssue.Id,
+					AssigneeId:   domainIssue.AssigneeId,
+					AssigneeName: domainIssue.AssigneeName,
+				}
+				result = append(result, issueAssignee)
 			}
+			result = append(result, domainIssue)
 			seenIncidents[incident.Number] = combined
 			boardIssue := &ticket.BoardIssue{
 				BoardId: serviceIdGen.Generate(data.Options.ConnectionId, data.Options.ServiceId),
 				IssueId: domainIssue.Id,
 			}
-			return []interface{}{
-				boardIssue,
-				domainIssue,
-			}, nil
+			result = append(result, boardIssue)
+			return result, nil
 		},
 	})
 	if err != nil {
diff --git a/backend/plugins/tapd/e2e/bugs_test.go b/backend/plugins/tapd/e2e/bugs_test.go
index 7bdbf43..c88fcc0 100644
--- a/backend/plugins/tapd/e2e/bugs_test.go
+++ b/backend/plugins/tapd/e2e/bugs_test.go
@@ -100,6 +100,7 @@
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
 	dataflowTester.FlushTabler(&ticket.SprintIssue{})
 	dataflowTester.FlushTabler(&ticket.IssueLabel{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertBugMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.Issue{},
@@ -149,6 +150,10 @@
 			"sprint_id",
 		),
 	)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/bug_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 	dataflowTester.Subtask(tasks.ConvertBugLabelsMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.IssueLabel{},
diff --git a/backend/plugins/tapd/e2e/snapshot_tables/bug_issue_assignees.csv b/backend/plugins/tapd/e2e/snapshot_tables/bug_issue_assignees.csv
new file mode 100644
index 0000000..642ab68
--- /dev/null
+++ b/backend/plugins/tapd/e2e/snapshot_tables/bug_issue_assignees.csv
@@ -0,0 +1,10 @@
+issue_id,assignee_id,assignee_name
+tapd:TapdBug:1:11991001001417,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001418,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001422,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001423,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001426,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001448,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001710,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001711,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdBug:1:11991001001740,tapd:TapdAccount:1:郝骁宵,郝骁宵
diff --git a/backend/plugins/tapd/e2e/snapshot_tables/story_issue_assignees.csv b/backend/plugins/tapd/e2e/snapshot_tables/story_issue_assignees.csv
new file mode 100644
index 0000000..27a96ee
--- /dev/null
+++ b/backend/plugins/tapd/e2e/snapshot_tables/story_issue_assignees.csv
@@ -0,0 +1,21 @@
+issue_id,assignee_id,assignee_name
+tapd:TapdStory:1:11991001037563,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdStory:1:11991001037696,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001037697,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001038322,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001038323,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001038697,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001038911,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001038912,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001039664,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001039673,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001040086,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001040088,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001041163,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001041164,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdStory:1:11991001041165,tapd:TapdAccount:1:testUnicode9f50testUnicode9e9f,testUnicode9f50testUnicode9e9f
+tapd:TapdStory:1:11991001041166,tapd:TapdAccount:1:testUnicode9f50testUnicode9e9f,testUnicode9f50testUnicode9e9f
+tapd:TapdStory:1:11991001041788,tapd:TapdAccount:1:testUnicode6768testUnicode4e39,testUnicode6768testUnicode4e39
+tapd:TapdStory:1:11991001041789,tapd:TapdAccount:1:testUnicode6768testUnicode4e39,testUnicode6768testUnicode4e39
+tapd:TapdStory:1:11991001041899,tapd:TapdAccount:1:testUnicode5218testUnicode5b87testUnicode6615,testUnicode5218testUnicode5b87testUnicode6615
+tapd:TapdStory:1:11991001041900,tapd:TapdAccount:1:testUnicode5218testUnicode5b87testUnicode6615,testUnicode5218testUnicode5b87testUnicode6615
diff --git a/backend/plugins/tapd/e2e/snapshot_tables/task_issue_assignees.csv b/backend/plugins/tapd/e2e/snapshot_tables/task_issue_assignees.csv
new file mode 100644
index 0000000..f86a8ce
--- /dev/null
+++ b/backend/plugins/tapd/e2e/snapshot_tables/task_issue_assignees.csv
@@ -0,0 +1,16 @@
+issue_id,assignee_id,assignee_name
+tapd:TapdTask:1:11991001015107,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015121,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015142,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdTask:1:11991001015184,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015203,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdTask:1:11991001015207,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015253,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015307,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015309,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdTask:1:11991001015340,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015361,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdTask:1:11991001015431,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
+tapd:TapdTask:1:11991001015441,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015452,tapd:TapdAccount:1:test-11test-11,test-11test-11
+tapd:TapdTask:1:11991001015583,tapd:TapdAccount:1:test-11test-11test-11,test-11test-11test-11
diff --git a/backend/plugins/tapd/e2e/stories_test.go b/backend/plugins/tapd/e2e/stories_test.go
index c72ad77..6895761 100644
--- a/backend/plugins/tapd/e2e/stories_test.go
+++ b/backend/plugins/tapd/e2e/stories_test.go
@@ -101,6 +101,7 @@
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
 	dataflowTester.FlushTabler(&ticket.SprintIssue{})
 	dataflowTester.FlushTabler(&ticket.IssueLabel{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertStoryMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
 		CSVRelPath:  "./snapshot_tables/issues_story.csv",
@@ -123,6 +124,11 @@
 			"sprint_id",
 		),
 	)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/story_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
 	dataflowTester.Subtask(tasks.ConvertStoryLabelsMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.IssueLabel{},
diff --git a/backend/plugins/tapd/e2e/tasks_test.go b/backend/plugins/tapd/e2e/tasks_test.go
index 0d9c856..0f23ddf 100644
--- a/backend/plugins/tapd/e2e/tasks_test.go
+++ b/backend/plugins/tapd/e2e/tasks_test.go
@@ -93,6 +93,7 @@
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
 	dataflowTester.FlushTabler(&ticket.SprintIssue{})
 	dataflowTester.FlushTabler(&ticket.IssueLabel{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertTaskMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
 		CSVRelPath:  "./snapshot_tables/issues_task.csv",
@@ -114,6 +115,11 @@
 			"sprint_id",
 		),
 	)
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/task_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
 	dataflowTester.Subtask(tasks.ConvertTaskLabelsMeta, taskData)
 	dataflowTester.VerifyTable(
 		ticket.IssueLabel{},
diff --git a/backend/plugins/tapd/tasks/bug_converter.go b/backend/plugins/tapd/tasks/bug_converter.go
index 7da20db..aa32acb 100644
--- a/backend/plugins/tapd/tasks/bug_converter.go
+++ b/backend/plugins/tapd/tasks/bug_converter.go
@@ -76,13 +76,19 @@
 				Component:      toolL.Feature, // todo not sure about this
 				OriginalStatus: toolL.Status,
 			}
+			var results []interface{}
 			if domainL.AssigneeName != "" {
 				domainL.AssigneeId = getAccountIdGen().Generate(data.Options.ConnectionId, toolL.CurrentOwner)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainL.Id,
+					AssigneeId:   domainL.AssigneeId,
+					AssigneeName: domainL.AssigneeName,
+				}
+				results = append(results, issueAssignee)
 			}
 			if domainL.ResolutionDate != nil && domainL.CreatedDate != nil {
 				domainL.LeadTimeMinutes = int64(domainL.ResolutionDate.Sub(*domainL.CreatedDate).Minutes())
 			}
-			results := make([]interface{}, 0, 2)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: getWorkspaceIdGen().Generate(toolL.ConnectionId, toolL.WorkspaceId),
 				IssueId: domainL.Id,
diff --git a/backend/plugins/tapd/tasks/story_converter.go b/backend/plugins/tapd/tasks/story_converter.go
index a4047c3..7fccbf7 100644
--- a/backend/plugins/tapd/tasks/story_converter.go
+++ b/backend/plugins/tapd/tasks/story_converter.go
@@ -79,13 +79,19 @@
 				Severity:             "",
 				Component:            toolL.Feature,
 			}
+			var results []interface{}
 			if domainL.AssigneeName != "" {
 				domainL.AssigneeId = getAccountIdGen().Generate(data.Options.ConnectionId, toolL.Owner)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainL.Id,
+					AssigneeId:   domainL.AssigneeId,
+					AssigneeName: domainL.AssigneeName,
+				}
+				results = append(results, issueAssignee)
 			}
 			if domainL.ResolutionDate != nil && domainL.CreatedDate != nil {
 				domainL.LeadTimeMinutes = int64(domainL.ResolutionDate.Sub(*domainL.CreatedDate).Minutes())
 			}
-			results := make([]interface{}, 0, 2)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: getWorkspaceIdGen().Generate(toolL.ConnectionId, toolL.WorkspaceId),
 				IssueId: domainL.Id,
diff --git a/backend/plugins/tapd/tasks/task_converter.go b/backend/plugins/tapd/tasks/task_converter.go
index 2f71456..ee2ad9f 100644
--- a/backend/plugins/tapd/tasks/task_converter.go
+++ b/backend/plugins/tapd/tasks/task_converter.go
@@ -75,13 +75,19 @@
 				CreatorName:    toolL.Creator,
 				AssigneeName:   toolL.Owner,
 			}
+			var results []interface{}
 			if domainL.AssigneeName != "" {
 				domainL.AssigneeId = getAccountIdGen().Generate(data.Options.ConnectionId, toolL.Owner)
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainL.Id,
+					AssigneeId:   domainL.AssigneeId,
+					AssigneeName: domainL.AssigneeName,
+				}
+				results = append(results, issueAssignee)
 			}
 			if domainL.ResolutionDate != nil && domainL.CreatedDate != nil {
 				domainL.LeadTimeMinutes = int64(domainL.ResolutionDate.Sub(*domainL.CreatedDate).Minutes())
 			}
-			results := make([]interface{}, 0, 2)
 			boardIssue := &ticket.BoardIssue{
 				BoardId: getWorkspaceIdGen().Generate(toolL.ConnectionId, toolL.WorkspaceId),
 				IssueId: domainL.Id,
diff --git a/backend/plugins/zentao/e2e/bug_test.go b/backend/plugins/zentao/e2e/bug_test.go
index 4057d29..36ed757 100644
--- a/backend/plugins/zentao/e2e/bug_test.go
+++ b/backend/plugins/zentao/e2e/bug_test.go
@@ -18,13 +18,14 @@
 package e2e
 
 import (
+	"testing"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
 	"github.com/apache/incubator-devlake/plugins/zentao/impl"
 	"github.com/apache/incubator-devlake/plugins/zentao/models"
 	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
-	"testing"
 )
 
 func TestZentaoBugDataFlow(t *testing.T) {
@@ -55,6 +56,7 @@
 	// verify conversion
 	dataflowTester.FlushTabler(&ticket.Issue{})
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertBugMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
 		CSVRelPath:   "./snapshot_tables/issues_bug.csv",
@@ -65,4 +67,8 @@
 		CSVRelPath:  "./snapshot_tables/board_issues_bug.csv",
 		IgnoreTypes: []interface{}{common.NoPKModel{}},
 	})
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/bug_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv b/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv
new file mode 100644
index 0000000..2f2e3ad
--- /dev/null
+++ b/backend/plugins/zentao/e2e/snapshot_tables/bug_issue_assignees.csv
@@ -0,0 +1,5 @@
+issue_id,assignee_id,assignee_name
+zentao:ZentaoBug:1:1,4,开发甲
+zentao:ZentaoBug:1:2,0,
+zentao:ZentaoBug:1:3,4,开发甲
+zentao:ZentaoBug:1:4,9,测试丙
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv b/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv
new file mode 100644
index 0000000..ba8ab51
--- /dev/null
+++ b/backend/plugins/zentao/e2e/snapshot_tables/story_issue_assignees.csv
@@ -0,0 +1,8 @@
+issue_id,assignee_id,assignee_name
+zentao:ZentaoStory:1:1,2,产品经理
+zentao:ZentaoStory:1:2,2,产品经理
+zentao:ZentaoStory:1:3,2,产品经理
+zentao:ZentaoStory:1:4,2,产品经理
+zentao:ZentaoStory:1:5,2,产品经理
+zentao:ZentaoStory:1:6,2,产品经理
+zentao:ZentaoStory:1:7,2,产品经理
diff --git a/backend/plugins/zentao/e2e/snapshot_tables/task_issue_assignees.csv b/backend/plugins/zentao/e2e/snapshot_tables/task_issue_assignees.csv
new file mode 100644
index 0000000..6b047e9
--- /dev/null
+++ b/backend/plugins/zentao/e2e/snapshot_tables/task_issue_assignees.csv
@@ -0,0 +1,4 @@
+issue_id,assignee_id,assignee_name
+zentao:ZentaoTask:1:1,5,开发乙
+zentao:ZentaoTask:1:2,5,开发乙
+zentao:ZentaoTask:1:3,5,开发乙
diff --git a/backend/plugins/zentao/e2e/story_test.go b/backend/plugins/zentao/e2e/story_test.go
index 14ca04a..873caf8 100644
--- a/backend/plugins/zentao/e2e/story_test.go
+++ b/backend/plugins/zentao/e2e/story_test.go
@@ -18,13 +18,14 @@
 package e2e
 
 import (
+	"testing"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
 	"github.com/apache/incubator-devlake/plugins/zentao/impl"
 	"github.com/apache/incubator-devlake/plugins/zentao/models"
 	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
-	"testing"
 )
 
 func TestZentaoStoryDataFlow(t *testing.T) {
@@ -55,6 +56,7 @@
 	// verify conversion
 	dataflowTester.FlushTabler(&ticket.Issue{})
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertStoryMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
 		CSVRelPath:   "./snapshot_tables/issues_story.csv",
@@ -65,4 +67,8 @@
 		CSVRelPath:  "./snapshot_tables/board_issues_story.csv",
 		IgnoreTypes: []interface{}{common.NoPKModel{}},
 	})
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/story_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/zentao/e2e/task_test.go b/backend/plugins/zentao/e2e/task_test.go
index a796757..0cf711d 100644
--- a/backend/plugins/zentao/e2e/task_test.go
+++ b/backend/plugins/zentao/e2e/task_test.go
@@ -18,13 +18,14 @@
 package e2e
 
 import (
+	"testing"
+
 	"github.com/apache/incubator-devlake/core/models/common"
 	"github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
 	"github.com/apache/incubator-devlake/plugins/zentao/impl"
 	"github.com/apache/incubator-devlake/plugins/zentao/models"
 	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
-	"testing"
 )
 
 func TestZentaoTaskDataFlow(t *testing.T) {
@@ -54,6 +55,7 @@
 
 	dataflowTester.FlushTabler(&ticket.Issue{})
 	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.FlushTabler(&ticket.IssueAssignee{})
 	dataflowTester.Subtask(tasks.ConvertTaskMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
 		CSVRelPath:   "./snapshot_tables/issues_task.csv",
@@ -64,4 +66,8 @@
 		CSVRelPath:  "./snapshot_tables/board_issues_task.csv",
 		IgnoreTypes: []interface{}{common.NoPKModel{}},
 	})
+	dataflowTester.VerifyTableWithOptions(ticket.IssueAssignee{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/task_issue_assignees.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
 }
diff --git a/backend/plugins/zentao/tasks/bug_convertor.go b/backend/plugins/zentao/tasks/bug_convertor.go
index 67f89a9..3ae0382 100644
--- a/backend/plugins/zentao/tasks/bug_convertor.go
+++ b/backend/plugins/zentao/tasks/bug_convertor.go
@@ -18,6 +18,9 @@
 package tasks
 
 import (
+	"reflect"
+	"strconv"
+
 	"github.com/apache/incubator-devlake/core/dal"
 	"github.com/apache/incubator-devlake/core/errors"
 	"github.com/apache/incubator-devlake/core/models/domainlayer"
@@ -26,8 +29,6 @@
 	"github.com/apache/incubator-devlake/core/plugin"
 	"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
 	"github.com/apache/incubator-devlake/plugins/zentao/models"
-	"reflect"
-	"strconv"
 )
 
 var _ plugin.SubTaskEntryPoint = ConvertBug
@@ -98,11 +99,19 @@
 			if toolEntity.ClosedDate != nil {
 				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
 			}
+			var results []interface{}
+			if domainEntity.AssigneeId != "" {
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainEntity.Id,
+					AssigneeId:   domainEntity.AssigneeId,
+					AssigneeName: domainEntity.AssigneeName,
+				}
+				results = append(results, issueAssignee)
+			}
 			domainBoardIssue := &ticket.BoardIssue{
 				BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ProductId),
 				IssueId: domainEntity.Id,
 			}
-			results := make([]interface{}, 0)
 			results = append(results, domainEntity, domainBoardIssue)
 			return results, nil
 		},
diff --git a/backend/plugins/zentao/tasks/story_convertor.go b/backend/plugins/zentao/tasks/story_convertor.go
index f80093f..bfa9d3a 100644
--- a/backend/plugins/zentao/tasks/story_convertor.go
+++ b/backend/plugins/zentao/tasks/story_convertor.go
@@ -96,6 +96,15 @@
 			default:
 				domainEntity.Status = ticket.IN_PROGRESS
 			}
+			var results []interface{}
+			if domainEntity.AssigneeId != "" {
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainEntity.Id,
+					AssigneeId:   domainEntity.AssigneeId,
+					AssigneeName: domainEntity.AssigneeName,
+				}
+				results = append(results, issueAssignee)
+			}
 			if toolEntity.ClosedDate != nil {
 				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
 			}
@@ -103,7 +112,6 @@
 				BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ProductId),
 				IssueId: domainEntity.Id,
 			}
-			results := make([]interface{}, 0)
 			results = append(results, domainEntity, domainBoardIssue)
 			return results, nil
 		},
diff --git a/backend/plugins/zentao/tasks/task_convertor.go b/backend/plugins/zentao/tasks/task_convertor.go
index c282711..22f44a9 100644
--- a/backend/plugins/zentao/tasks/task_convertor.go
+++ b/backend/plugins/zentao/tasks/task_convertor.go
@@ -100,11 +100,19 @@
 			if toolEntity.ClosedDate != nil {
 				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
 			}
+			var results []interface{}
+			if domainEntity.AssigneeId != "" {
+				issueAssignee := &ticket.IssueAssignee{
+					IssueId:      domainEntity.Id,
+					AssigneeId:   domainEntity.AssigneeId,
+					AssigneeName: domainEntity.AssigneeName,
+				}
+				results = append(results, issueAssignee)
+			}
 			domainBoardIssue := &ticket.BoardIssue{
 				BoardId: boardIdGen.Generate(data.Options.ConnectionId, toolEntity.Execution),
 				IssueId: domainEntity.Id,
 			}
-			results := make([]interface{}, 0)
 			results = append(results, domainEntity, domainBoardIssue)
 			return results, nil
 		},