This is an automated email from the ASF dual-hosted git repository.
zhangliang2022 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 9ba4f85ac feat: add jira tool layer issue relationship (#5762)
9ba4f85ac is described below
commit 9ba4f85acacd437210e820d45638c7cdb5fcc8f8
Author: abeizn <[email protected]>
AuthorDate: Mon Jul 31 10:59:12 2023 +0800
feat: add jira tool layer issue relationship (#5762)
* feat: add jira tool layer issue relationship
* feat: add domain issue relationships and e2e test
---
.../models/domainlayer/domaininfo/domaininfo.go | 1 +
.../domainlayer/ticket/issue_relationship.go} | 38 +++-----
.../20230728_add_issue_relationship_table.go} | 43 ++++-----
.../archived/issue_relationship.go} | 36 ++-----
backend/core/models/migrationscripts/register.go | 1 +
.../plugins/jira/e2e/issue_relationship_test.go | 63 +++++++++++++
.../_raw_jira_api_issue_relationships.csv | 23 +++++
.../snapshot_tables/_tool_jira_board_issues.csv | 24 +++++
.../_tool_jira_issue_relationships.csv | 13 +++
.../e2e/snapshot_tables/issue_relationships.csv | 13 +++
backend/plugins/jira/impl/impl.go | 2 +
backend/plugins/jira/models/issue_relationship.go | 41 ++++++++
...go => 20230726_add_issue_relationship_table.go} | 42 ++++-----
.../archived/issue_relationship.go | 41 ++++++++
.../jira/models/migrationscripts/register.go | 1 +
backend/plugins/jira/tasks/apiv2models/issue.go | 56 ++++++++++-
backend/plugins/jira/tasks/issue_extractor.go | 17 ++++
.../jira/tasks/issue_relationship_convertor.go | 105 +++++++++++++++++++++
18 files changed, 461 insertions(+), 99 deletions(-)
diff --git a/backend/core/models/domainlayer/domaininfo/domaininfo.go
b/backend/core/models/domainlayer/domaininfo/domaininfo.go
index b811d492a..de139e173 100644
--- a/backend/core/models/domainlayer/domaininfo/domaininfo.go
+++ b/backend/core/models/domainlayer/domaininfo/domaininfo.go
@@ -85,5 +85,6 @@ func GetDomainTablesInfo() []dal.Tabler {
&ticket.Sprint{},
&ticket.SprintIssue{},
&ticket.IssueAssignee{},
+ &ticket.IssueRelationship{},
}
}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go
b/backend/core/models/domainlayer/ticket/issue_relationship.go
similarity index 50%
copy from backend/plugins/jira/models/migrationscripts/register.go
copy to backend/core/models/domainlayer/ticket/issue_relationship.go
index c0c9ed9a2..136dcf9f0 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++ b/backend/core/models/domainlayer/ticket/issue_relationship.go
@@ -15,32 +15,18 @@ See the License for the specific language governing
permissions and
limitations under the License.
*/
-package migrationscripts
+package ticket
-import (
- "github.com/apache/incubator-devlake/core/plugin"
-)
+import "github.com/apache/incubator-devlake/core/models/domainlayer"
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
- return []plugin.MigrationScript{
- new(addSourceTable20220407),
- new(renameSourceTable20220505),
- new(addInitTables20220716),
- new(addTransformationRule20221116),
- new(addProjectName20221215),
- new(addJiraMultiAuth20230129),
- new(removeIssueStdStoryPoint),
- new(addCommitRepoPattern),
- new(expandRemotelinkUrl),
- new(addConnectionIdToTransformationRule),
- new(addChangeTotal20230412),
- new(expandRemotelinkSelfUrl),
- new(addDescAndComments),
- new(renameTr2ScopeConfig),
- new(addRepoUrl),
- new(addApplicationType),
- new(clearRepoPattern),
- new(addRawParamTableForScope),
- }
+type IssueRelationship struct {
+ domainlayer.DomainEntity
+
+ SourceIssueId uint64 `gorm:"index"`
+ TargetIssueId uint64
+ OriginalType string `gorm:"type:varchar(255)"`
+}
+
+func (IssueRelationship) TableName() string {
+ return "issue_relationships"
}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go
b/backend/core/models/migrationscripts/20230728_add_issue_relationship_table.go
similarity index 52%
copy from backend/plugins/jira/models/migrationscripts/register.go
copy to
backend/core/models/migrationscripts/20230728_add_issue_relationship_table.go
index c0c9ed9a2..9cf262b96 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++
b/backend/core/models/migrationscripts/20230728_add_issue_relationship_table.go
@@ -18,29 +18,26 @@ limitations under the License.
package migrationscripts
import (
- "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
)
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
- return []plugin.MigrationScript{
- new(addSourceTable20220407),
- new(renameSourceTable20220505),
- new(addInitTables20220716),
- new(addTransformationRule20221116),
- new(addProjectName20221215),
- new(addJiraMultiAuth20230129),
- new(removeIssueStdStoryPoint),
- new(addCommitRepoPattern),
- new(expandRemotelinkUrl),
- new(addConnectionIdToTransformationRule),
- new(addChangeTotal20230412),
- new(expandRemotelinkSelfUrl),
- new(addDescAndComments),
- new(renameTr2ScopeConfig),
- new(addRepoUrl),
- new(addApplicationType),
- new(clearRepoPattern),
- new(addRawParamTableForScope),
- }
+type addIssueRelationship struct{}
+
+func (u *addIssueRelationship) Up(basicRes context.BasicRes) errors.Error {
+ return migrationhelper.AutoMigrateTables(
+ basicRes,
+ &archived.IssueRelationship{},
+ )
+
+}
+
+func (*addIssueRelationship) Version() uint64 {
+ return 20230728000001
+}
+
+func (*addIssueRelationship) Name() string {
+ return "add issue_relationships table"
}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go
b/backend/core/models/migrationscripts/archived/issue_relationship.go
similarity index 50%
copy from backend/plugins/jira/models/migrationscripts/register.go
copy to backend/core/models/migrationscripts/archived/issue_relationship.go
index c0c9ed9a2..4779b534f 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/archived/issue_relationship.go
@@ -15,32 +15,16 @@ See the License for the specific language governing
permissions and
limitations under the License.
*/
-package migrationscripts
+package archived
-import (
- "github.com/apache/incubator-devlake/core/plugin"
-)
+type IssueRelationship struct {
+ DomainEntity
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
- return []plugin.MigrationScript{
- new(addSourceTable20220407),
- new(renameSourceTable20220505),
- new(addInitTables20220716),
- new(addTransformationRule20221116),
- new(addProjectName20221215),
- new(addJiraMultiAuth20230129),
- new(removeIssueStdStoryPoint),
- new(addCommitRepoPattern),
- new(expandRemotelinkUrl),
- new(addConnectionIdToTransformationRule),
- new(addChangeTotal20230412),
- new(expandRemotelinkSelfUrl),
- new(addDescAndComments),
- new(renameTr2ScopeConfig),
- new(addRepoUrl),
- new(addApplicationType),
- new(clearRepoPattern),
- new(addRawParamTableForScope),
- }
+ SourceIssueId uint64 `gorm:"index"`
+ TargetIssueId uint64
+ OriginalType string `gorm:"type:varchar(255)"`
+}
+
+func (IssueRelationship) TableName() string {
+ return "issue_relationships"
}
diff --git a/backend/core/models/migrationscripts/register.go
b/backend/core/models/migrationscripts/register.go
index 585855835..3aad7517b 100644
--- a/backend/core/models/migrationscripts/register.go
+++ b/backend/core/models/migrationscripts/register.go
@@ -86,5 +86,6 @@ func All() []plugin.MigrationScript {
new(modifyPrLabelsAndComments),
new(renameFinishedCommitsDiffs),
new(addUpdatedDateToIssueComments),
+ new(addIssueRelationship),
}
}
diff --git a/backend/plugins/jira/e2e/issue_relationship_test.go
b/backend/plugins/jira/e2e/issue_relationship_test.go
new file mode 100644
index 000000000..f75df3d8a
--- /dev/null
+++ b/backend/plugins/jira/e2e/issue_relationship_test.go
@@ -0,0 +1,63 @@
+/*
+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 (
+ "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"
+ "github.com/apache/incubator-devlake/plugins/jira/models"
+ "github.com/apache/incubator-devlake/plugins/jira/tasks"
+)
+
+func TestIssueRelationshipDataFlow(t *testing.T) {
+ var plugin impl.Jira
+ dataflowTester := e2ehelper.NewDataFlowTester(t, "jira", plugin)
+
+ taskData := &tasks.JiraTaskData{
+ Options: &tasks.JiraOptions{
+ ConnectionId: 2,
+ BoardId: 8,
+ },
+ }
+
+ // import raw data table
+
dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_jira_api_issue_relationships.csv",
"_raw_jira_api_issue_relationships")
+
+ // verify issue extraction
+ dataflowTester.FlushTabler(&models.JiraIssueRelationship{})
+ dataflowTester.Subtask(tasks.ExtractIssuesMeta, taskData)
+
+ dataflowTester.VerifyTableWithOptions(&models.JiraIssueRelationship{},
e2ehelper.TableOptions{
+ CSVRelPath:
"./snapshot_tables/_tool_jira_issue_relationships.csv",
+ IgnoreTypes: []interface{}{common.NoPKModel{}},
+ })
+
+
dataflowTester.ImportCsvIntoTabler("./snapshot_tables/_tool_jira_board_issues.csv",
&models.JiraBoardIssue{})
+
+ // verify issue conversion
+ dataflowTester.FlushTabler(&ticket.IssueRelationship{})
+ dataflowTester.Subtask(tasks.ConvertIssueRelationshipsMeta, taskData)
+ dataflowTester.VerifyTableWithOptions(&ticket.IssueRelationship{},
e2ehelper.TableOptions{
+ CSVRelPath: "./snapshot_tables/issue_relationships.csv",
+ IgnoreTypes: []interface{}{common.NoPKModel{}},
+ })
+}
diff --git
a/backend/plugins/jira/e2e/raw_tables/_raw_jira_api_issue_relationships.csv
b/backend/plugins/jira/e2e/raw_tables/_raw_jira_api_issue_relationships.csv
new file mode 100644
index 000000000..8fb2c53dd
--- /dev/null
+++ b/backend/plugins/jira/e2e/raw_tables/_raw_jira_api_issue_relationships.csv
@@ -0,0 +1,23 @@
+"id","params","data","url","input","created_at"
+1,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10802"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10802"",""key"":""DZFNK0168-1"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+2,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10803"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10803"",""key"":""DZFNK0168-2"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+3,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10804"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10804"",""key"":""DZFNK0168-3"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+4,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10805"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10805"",""key"":""DZFNK0168-4"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+5,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10806"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10806"",""key"":""DZFNK0168-5"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+6,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10807"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10807"",""key"":""DZFNK0168-6"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+7,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10808"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10808"",""key"":""DZFNK0168-7"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+8,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10809"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10809"",""key"":""DZFNK0168-8"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+9,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10810"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10810"",""key"":""DZFNK0168-9"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsmall
[...]
+10,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10811"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10811"",""key"":""DZFNK0168-10"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+11,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10812"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10812"",""key"":""DZFNK0168-11"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+12,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10813"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10813"",""key"":""DZFNK0168-12"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+13,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10814"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10814"",""key"":""DZFNK0168-13"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+14,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10815"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10815"",""key"":""DZFNK0168-14"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+15,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10816"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10816"",""key"":""DZFNK0168-15"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+16,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10817"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10817"",""key"":""DZFNK0168-16"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+17,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10818"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10818"",""key"":""DZFNK0168-17"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+18,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10819"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10819"",""key"":""DZFNK0168-18"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+19,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10820"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10820"",""key"":""DZFNK0168-19"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+20,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10821"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10821"",""key"":""DZFNK0168-20"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+21,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10822"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10822"",""key"":""DZFNK0168-21"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
+22,"{""ConnectionId"":2,""BoardId"":8}","{""expand"":""operations,versionedRepresentations,editmeta,changelog,renderedFields"",""id"":""10823"",""self"":""http://3.81.151.94:8080/rest/agile/1.0/issue/10823"",""key"":""DZFNK0168-22"",""fields"":{""issuetype"":{""self"":""http://3.81.151.94:8080/rest/api/2/issuetype/10004"",""id"":""10004"",""description"":""A
problem which impairs or prevents the functions of the
product."",""iconUrl"":""http://3.81.151.94:8080/secure/viewavatar?size=xsma
[...]
diff --git
a/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues.csv
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues.csv
index 2c4f0cfe1..b1a34effe 100644
--- a/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues.csv
+++ b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_board_issues.csv
@@ -29,3 +29,27 @@
connection_id,board_id,issue_id,_raw_data_params,_raw_data_table,_raw_data_id,_r
2,8,10097,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12468,
2,8,10098,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12469,
2,8,10099,"{""ConnectionId"":2,""BoardId"":8}",_raw_jira_api_issues,12470,
+2,8,10802,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",1,""
+2,8,10803,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",2,""
+2,8,10804,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",3,""
+2,8,10805,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",4,""
+2,8,10806,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",5,""
+2,8,10807,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",6,""
+2,8,10808,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",7,""
+2,8,10809,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",8,""
+2,8,10810,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",9,""
+2,8,10811,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",10,""
+2,8,10812,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",11,""
+2,8,10813,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",12,""
+2,8,10814,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",13,""
+2,8,10815,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",14,""
+2,8,10816,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",15,""
+2,8,10817,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",16,""
+2,8,10818,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",17,""
+2,8,10819,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",18,""
+2,8,10820,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",19,""
+2,8,10821,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",20,""
+2,8,10822,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",21,""
+2,8,10823,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",22,""
+2,8,116800,"{""ConnectionId"":2,""BoardId"":8}","_raw_jira_api_issues",23,""
+
diff --git
a/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_relationships.csv
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_relationships.csv
new file mode 100644
index 000000000..9277294ed
--- /dev/null
+++
b/backend/plugins/jira/e2e/snapshot_tables/_tool_jira_issue_relationships.csv
@@ -0,0 +1,13 @@
+connection_id,issue_id,issue_key,type_id,type_name,inward,outward,inward_issue_id,inward_issue_key,outward_issue_id,outward_issue_key
+2,10802,DZFNK0168-1,10000,Blocks,is blocked by,blocks,10806,DZFNK0168-5,0,
+2,10803,DZFNK0168-2,10000,Blocks,is blocked by,blocks,0,,10823,DZFNK0168-22
+2,10805,DZFNK0168-4,10001,Cloners,is cloned by,clones,0,,10812,DZFNK0168-11
+2,10806,DZFNK0168-5,10002,Duplicate,is duplicated
by,duplicates,10812,DZFNK0168-11,0,
+2,10812,DZFNK0168-11,10002,Duplicate,is duplicated
by,duplicates,116583,IP8CUD168-35305,0,
+2,10815,DZFNK0168-14,10002,Duplicate,is duplicated
by,duplicates,0,,116566,IP8CUD168-35288
+2,10817,DZFNK0168-16,10001,Cloners,is cloned
by,clones,111957,M2VRIQ168-12985,0,
+2,10818,DZFNK0168-17,10001,Cloners,is cloned
by,clones,0,,111900,M2VRIQ168-12954
+2,10820,DZFNK0168-19,10003,Relates,relates to,relates to,0,,10822,DZFNK0168-21
+2,10821,DZFNK0168-20,10000,Blocks,is blocked
by,blocks,116684,IP8CUD168-35406,0,
+2,10822,DZFNK0168-21,10003,Relates,relates to,relates to,10820,DZFNK0168-19,0,
+2,10823,DZFNK0168-22,10000,Blocks,is blocked by,blocks,10803,DZFNK0168-2,0,
diff --git a/backend/plugins/jira/e2e/snapshot_tables/issue_relationships.csv
b/backend/plugins/jira/e2e/snapshot_tables/issue_relationships.csv
new file mode 100644
index 000000000..515fc9089
--- /dev/null
+++ b/backend/plugins/jira/e2e/snapshot_tables/issue_relationships.csv
@@ -0,0 +1,13 @@
+id,source_issue_id,target_issue_id,original_type
+jira:JiraIssueRelationship:2:10802-isblockedby-10806,10802,10806,is blocked by
+jira:JiraIssueRelationship:2:10803-blocks-10823,10803,10823,blocks
+jira:JiraIssueRelationship:2:10805-clones-10812,10805,10812,clones
+jira:JiraIssueRelationship:2:10806-isduplicatedby-10812,10806,10812,is
duplicated by
+jira:JiraIssueRelationship:2:10812-isduplicatedby-116583,10812,116583,is
duplicated by
+jira:JiraIssueRelationship:2:10815-duplicates-116566,10815,116566,duplicates
+jira:JiraIssueRelationship:2:10817-isclonedby-111957,10817,111957,is cloned by
+jira:JiraIssueRelationship:2:10818-clones-111900,10818,111900,clones
+jira:JiraIssueRelationship:2:10820-relatesto-10822,10820,10822,relates to
+jira:JiraIssueRelationship:2:10821-isblockedby-116684,10821,116684,is blocked
by
+jira:JiraIssueRelationship:2:10822-relatesto-10820,10822,10820,relates to
+jira:JiraIssueRelationship:2:10823-isblockedby-10803,10823,10803,is blocked by
diff --git a/backend/plugins/jira/impl/impl.go
b/backend/plugins/jira/impl/impl.go
index 96928aa7e..b2a39bbd9 100644
--- a/backend/plugins/jira/impl/impl.go
+++ b/backend/plugins/jira/impl/impl.go
@@ -88,6 +88,7 @@ func (p Jira) GetTablesInfo() []dal.Tabler {
&models.JiraStatus{},
&models.JiraWorklog{},
&models.JiraIssueComment{},
+ &models.JiraIssueRelationship{},
&models.JiraScopeConfig{},
}
}
@@ -139,6 +140,7 @@ func (p Jira) SubTaskMetas() []plugin.SubTaskMeta {
tasks.ConvertIssueCommentsMeta,
tasks.ConvertWorklogsMeta,
tasks.ConvertIssueChangelogsMeta,
+ tasks.ConvertIssueRelationshipsMeta,
tasks.ConvertSprintsMeta,
tasks.ConvertSprintIssuesMeta,
diff --git a/backend/plugins/jira/models/issue_relationship.go
b/backend/plugins/jira/models/issue_relationship.go
new file mode 100644
index 000000000..f96e02a9e
--- /dev/null
+++ b/backend/plugins/jira/models/issue_relationship.go
@@ -0,0 +1,41 @@
+/*
+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 JiraIssueRelationship struct {
+ common.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ IssueId uint64 `gorm:"primarykey"`
+ IssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-1
+ TypeId uint64 // e.g. 10001
+ TypeName string `gorm:"type:varchar(255)"` // e.g. Blocks
+ Inward string `gorm:"type:varchar(255)"` // e.g. blocks
+ Outward string `gorm:"type:varchar(255)"` // e.g. is blocked by
+ InwardIssueId uint64 // e.g. 116566
+ InwardIssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-2
+ OutwardIssueId uint64 // e.g. 116567
+ OutwardIssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-3
+}
+
+func (JiraIssueRelationship) TableName() string {
+ return "_tool_jira_issue_relationships"
+}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go
b/backend/plugins/jira/models/migrationscripts/20230726_add_issue_relationship_table.go
similarity index 52%
copy from backend/plugins/jira/models/migrationscripts/register.go
copy to
backend/plugins/jira/models/migrationscripts/20230726_add_issue_relationship_table.go
index c0c9ed9a2..e04482f76 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++
b/backend/plugins/jira/models/migrationscripts/20230726_add_issue_relationship_table.go
@@ -18,29 +18,25 @@ limitations under the License.
package migrationscripts
import (
- "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/core/context"
+ "github.com/apache/incubator-devlake/core/errors"
+ "github.com/apache/incubator-devlake/helpers/migrationhelper"
+
"github.com/apache/incubator-devlake/plugins/jira/models/migrationscripts/archived"
)
-// All return all the migration scripts
-func All() []plugin.MigrationScript {
- return []plugin.MigrationScript{
- new(addSourceTable20220407),
- new(renameSourceTable20220505),
- new(addInitTables20220716),
- new(addTransformationRule20221116),
- new(addProjectName20221215),
- new(addJiraMultiAuth20230129),
- new(removeIssueStdStoryPoint),
- new(addCommitRepoPattern),
- new(expandRemotelinkUrl),
- new(addConnectionIdToTransformationRule),
- new(addChangeTotal20230412),
- new(expandRemotelinkSelfUrl),
- new(addDescAndComments),
- new(renameTr2ScopeConfig),
- new(addRepoUrl),
- new(addApplicationType),
- new(clearRepoPattern),
- new(addRawParamTableForScope),
- }
+type addIssueRelationship struct{}
+
+func (script *addIssueRelationship) Up(basicRes context.BasicRes) errors.Error
{
+ return migrationhelper.AutoMigrateTables(
+ basicRes,
+ &archived.JiraIssueRelationship{},
+ )
+}
+
+func (*addIssueRelationship) Version() uint64 {
+ return 20230727122534
+}
+
+func (*addIssueRelationship) Name() string {
+ return "add table _tool_jira_issue_relationships table"
}
diff --git
a/backend/plugins/jira/models/migrationscripts/archived/issue_relationship.go
b/backend/plugins/jira/models/migrationscripts/archived/issue_relationship.go
new file mode 100644
index 000000000..29f8b1a13
--- /dev/null
+++
b/backend/plugins/jira/models/migrationscripts/archived/issue_relationship.go
@@ -0,0 +1,41 @@
+/*
+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 archived
+
+import (
+
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+)
+
+type JiraIssueRelationship struct {
+ archived.NoPKModel
+ ConnectionId uint64 `gorm:"primaryKey"`
+ IssueId uint64 `gorm:"primarykey"`
+ IssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-1
+ TypeId uint64 // e.g. 10001
+ TypeName string `gorm:"type:varchar(255)"` // e.g. Blocks
+ Inward string `gorm:"type:varchar(255)"` // e.g. blocks
+ Outward string `gorm:"type:varchar(255)"` // e.g. is blocked by
+ InwardIssueId uint64 // e.g. 116566
+ InwardIssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-2
+ OutwardIssueId uint64 // e.g. 116567
+ OutwardIssueKey string `gorm:"type:varchar(255)"` // e.g. DEV-3
+}
+
+func (JiraIssueRelationship) TableName() string {
+ return "_tool_jira_issue_relationships"
+}
diff --git a/backend/plugins/jira/models/migrationscripts/register.go
b/backend/plugins/jira/models/migrationscripts/register.go
index c0c9ed9a2..37a65394b 100644
--- a/backend/plugins/jira/models/migrationscripts/register.go
+++ b/backend/plugins/jira/models/migrationscripts/register.go
@@ -42,5 +42,6 @@ func All() []plugin.MigrationScript {
new(addApplicationType),
new(clearRepoPattern),
new(addRawParamTableForScope),
+ new(addIssueRelationship),
}
}
diff --git a/backend/plugins/jira/tasks/apiv2models/issue.go
b/backend/plugins/jira/tasks/apiv2models/issue.go
index 8a0651acd..bfef2f171 100644
--- a/backend/plugins/jira/tasks/apiv2models/issue.go
+++ b/backend/plugins/jira/tasks/apiv2models/issue.go
@@ -97,7 +97,7 @@ type Issue struct {
Timeestimate interface{}
`json:"timeestimate"`
Aggregatetimeoriginalestimate interface{}
`json:"aggregatetimeoriginalestimate"`
Versions []interface{}
`json:"versions"`
- Issuelinks []interface{}
`json:"issuelinks"`
+ Issuelinks []IssueLink
`json:"issuelinks"`
Assignee *Account
`json:"assignee"`
Updated helper.Iso8601Time
`json:"updated"`
Status struct {
@@ -160,6 +160,60 @@ type Issue struct {
} `json:"changelog"`
}
+type IssueLinkType struct {
+ ID uint64 `json:"id,string"`
+ Name string `json:"name"`
+ Inward string `json:"inward"`
+ Outward string `json:"outward"`
+ Self string `json:"self"`
+}
+
+type InOutwardIssue struct {
+ ID uint64 `json:"id,string"`
+ Key string `json:"key"`
+ Self string `json:"self"`
+ Fields struct {
+ Summary string `json:"summary"`
+ Status struct {
+ Self string `json:"self"`
+ Description string `json:"description"`
+ IconURL string `json:"iconUrl"`
+ Name string `json:"name"`
+ ID uint64 `json:"id,string"`
+ StatusCategory struct {
+ Self string `json:"self"`
+ ID int `json:"id"`
+ Key string `json:"key"`
+ ColorName string `json:"colorName"`
+ Name string `json:"name"`
+ } `json:"statusCategory"`
+ } `json:"status"`
+ Priority struct {
+ Self string `json:"self"`
+ IconURL string `json:"iconUrl"`
+ Name string `json:"name"`
+ ID uint64 `json:"id,string"`
+ } `json:"priority"`
+ Issuetype struct {
+ Self string `json:"self"`
+ ID uint64 `json:"id,string"`
+ Description string `json:"description"`
+ IconURL string `json:"iconUrl"`
+ Name string `json:"name"`
+ Subtask bool `json:"subtask"`
+ AvatarID int `json:"avatarId"`
+ } `json:"issuetype"`
+ } `json:"fields"`
+}
+
+type IssueLink struct {
+ ID uint64 `json:"id,string"`
+ Self string `json:"self"`
+ Type IssueLinkType `json:"type"`
+ InwardIssue InOutwardIssue `json:"inwardIssue"`
+ OutwardIssue InOutwardIssue `json:"outwardIssue"`
+}
+
func (i Issue) toToolLayer(connectionId uint64) *models.JiraIssue {
var workload float64
result := &models.JiraIssue{
diff --git a/backend/plugins/jira/tasks/issue_extractor.go
b/backend/plugins/jira/tasks/issue_extractor.go
index 7b98a842d..11e730f35 100644
--- a/backend/plugins/jira/tasks/issue_extractor.go
+++ b/backend/plugins/jira/tasks/issue_extractor.go
@@ -177,6 +177,23 @@ func extractIssues(data *JiraTaskData, mappings
*typeMappings, row *api.RawData)
}
results = append(results, issueLabel)
}
+ issuelinks := apiIssue.Fields.Issuelinks
+ for _, v := range issuelinks {
+ issueLink := &models.JiraIssueRelationship{
+ ConnectionId: data.Options.ConnectionId,
+ IssueId: issue.IssueId,
+ IssueKey: issue.IssueKey,
+ TypeId: v.Type.ID, // Extracting the
TypeId from the issuelink
+ TypeName: v.Type.Name, // Extracting the
TypeName from the issuelink
+ Inward: v.Type.Inward, // Extracting the
Inward from the issuelink
+ Outward: v.Type.Outward, // Extracting the
Outward from the issuelink
+ InwardIssueId: v.InwardIssue.ID, // Extracting the
InwardIssueId from the issuelink
+ InwardIssueKey: v.InwardIssue.Key, // Extracting the
InwardIssueKey from the issuelink
+ OutwardIssueId: v.OutwardIssue.ID, // Extracting the
OutwardIssueId from the issuelink
+ OutwardIssueKey: v.OutwardIssue.Key, // Extracting the
OutwardIssueKey from the issuelink
+ }
+ results = append(results, issueLink)
+ }
return results, nil
}
diff --git a/backend/plugins/jira/tasks/issue_relationship_convertor.go
b/backend/plugins/jira/tasks/issue_relationship_convertor.go
new file mode 100644
index 000000000..64fbb640b
--- /dev/null
+++ b/backend/plugins/jira/tasks/issue_relationship_convertor.go
@@ -0,0 +1,105 @@
+/*
+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 (
+ "fmt"
+ "reflect"
+ "strings"
+
+ "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/jira/models"
+)
+
+var ConvertIssueRelationshipsMeta = plugin.SubTaskMeta{
+ Name: "convertIssueRelationships",
+ EntryPoint: ConvertIssueRelationships,
+ EnabledByDefault: true,
+ Description: "Convert tool layer table jira_issue_relationships
into domain layer table issue_relationships",
+ DomainTypes: []string{plugin.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertIssueRelationships(taskCtx plugin.SubTaskContext) errors.Error {
+ db := taskCtx.GetDal()
+ data := taskCtx.GetData().(*JiraTaskData)
+
+ cursor, err := db.Cursor(
+ dal.Select("jir.*"),
+ dal.From("_tool_jira_issue_relationships jir"),
+ dal.Join(`LEFT JOIN _tool_jira_board_issues jbi
+ ON jir.connection_id = jbi.connection_id AND jir.issue_id =
jbi.issue_id`),
+ dal.Where("jir.connection_id = ? AND jbi.board_id = ?",
data.Options.ConnectionId, data.Options.BoardId),
+ dal.Orderby("issue_id ASC"),
+ )
+ if err != nil {
+ return err
+ }
+ defer cursor.Close()
+ issueIdGen := didgen.NewDomainIdGenerator(&JiraIssueRelationship{})
+
+ converter, err := helper.NewDataConverter(helper.DataConverterArgs{
+ RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+ Ctx: taskCtx,
+ Params: JiraApiParams{
+ ConnectionId: data.Options.ConnectionId,
+ BoardId: data.Options.BoardId,
+ },
+ Table: RAW_ISSUE_TABLE,
+ },
+ InputRowType: reflect.TypeOf(models.JiraIssueRelationship{}),
+ Input: cursor,
+ Convert: func(inputRow interface{}) ([]interface{},
errors.Error) {
+ issueRelationship :=
inputRow.(*models.JiraIssueRelationship)
+ domainIssueRelationship := &ticket.IssueRelationship{
+ SourceIssueId: issueRelationship.IssueId,
+ }
+ if issueRelationship.InwardIssueId != 0 {
+ domainIssueRelationship.TargetIssueId =
issueRelationship.InwardIssueId
+ domainIssueRelationship.OriginalType =
issueRelationship.Inward
+ } else {
+ domainIssueRelationship.TargetIssueId =
issueRelationship.OutwardIssueId
+ domainIssueRelationship.OriginalType =
issueRelationship.Outward
+ }
+
+ originalType :=
strings.Replace(domainIssueRelationship.OriginalType, " ", "", -1)
+ relationshipKey := fmt.Sprintf("%d-%s-%d",
domainIssueRelationship.SourceIssueId, originalType,
domainIssueRelationship.TargetIssueId)
+
+ domainIssueRelationship.DomainEntity.Id =
issueIdGen.Generate(data.Options.ConnectionId, relationshipKey)
+
+ return []interface{}{
+ domainIssueRelationship,
+ }, nil
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ return converter.Execute()
+}
+
+type JiraIssueRelationship struct {
+ ConnectionId uint64 `gorm:"primaryKey"`
+ RelationshipKey string `gorm:"primarykey"`
+}