This is an automated email from the ASF dual-hosted git repository.
abeizn 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 6941c77b9 Add a new plugin, linker. (#7509)
6941c77b9 is described below
commit 6941c77b99b8dedd2196aee3cd6b2dc0a22f0bdb
Author: Lynwee <[email protected]>
AuthorDate: Mon May 27 10:23:03 2024 +0800
Add a new plugin, linker. (#7509)
* fix(apikey): abort request if api key doesn't match
* feat(webhook): update deployment commit id
* fix(plugins): update path parameter, adapt to latest plugin helper
* feat(plugins): add new plugin linker
* feat(linker): fix compile errors
* feat(linker): remove `since` in options
* feat(linker): add e2e test, fix dora errors
* fix(test): fix test errors
* docs(linker): remove some comments
---
backend/core/plugin/hub.go | 8 +-
backend/core/runner/loader.go | 20 +++-
backend/plugins/dora/impl/impl.go | 9 +-
backend/plugins/linker/README.md | 16 +++
.../plugins/linker/e2e/link_pr_and_issue_test.go | 70 ++++++++++++
.../plugins/linker/e2e/snapshot_tables/issues.csv | 2 +
.../linker/e2e/snapshot_tables/project_mapping.csv | 2 +
.../e2e/snapshot_tables/pull_request_issues.csv | 2 +
.../linker/e2e/snapshot_tables/pull_requests.csv | 2 +
backend/plugins/linker/impl/impl.go | 127 +++++++++++++++++++++
backend/plugins/linker/linker.go | 42 +++++++
.../linker/models/migrationscripts/register.go | 27 +++++
backend/plugins/linker/tasks/link_pr_and_issue.go | 102 +++++++++++++++++
backend/plugins/linker/tasks/task_data.go | 43 +++++++
backend/plugins/table_info_test.go | 2 +
15 files changed, 464 insertions(+), 10 deletions(-)
diff --git a/backend/core/plugin/hub.go b/backend/core/plugin/hub.go
index 6788c9057..3feca2219 100644
--- a/backend/core/plugin/hub.go
+++ b/backend/core/plugin/hub.go
@@ -20,6 +20,7 @@ package plugin
import (
"fmt"
"strings"
+ "sync"
"github.com/apache/incubator-devlake/core/context"
"github.com/apache/incubator-devlake/core/errors"
@@ -27,9 +28,14 @@ import (
// Allowing plugin to know each other
-var plugins map[string]PluginMeta
+var (
+ plugins map[string]PluginMeta
+ pluginMutex sync.RWMutex
+)
func RegisterPlugin(name string, plugin PluginMeta) errors.Error {
+ pluginMutex.Lock()
+ defer pluginMutex.Unlock()
if plugins == nil {
plugins = make(map[string]PluginMeta)
}
diff --git a/backend/core/runner/loader.go b/backend/core/runner/loader.go
index 6e435a172..c454aeceb 100644
--- a/backend/core/runner/loader.go
+++ b/backend/core/runner/loader.go
@@ -23,6 +23,7 @@ import (
"path/filepath"
goplugin "plugin"
"strings"
+ "sync"
"github.com/apache/incubator-devlake/core/context"
"github.com/apache/incubator-devlake/core/errors"
@@ -49,6 +50,7 @@ func LoadPlugins(basicRes context.BasicRes) errors.Error {
func LoadGoPlugins(basicRes context.BasicRes) errors.Error {
pluginsDir := basicRes.GetConfig("PLUGIN_DIR")
+ var wg sync.WaitGroup
walkErr := filepath.WalkDir(pluginsDir, func(path string, d
fs.DirEntry, err error) error {
if err != nil {
return err
@@ -68,15 +70,21 @@ func LoadGoPlugins(basicRes context.BasicRes) errors.Error {
if !ok {
return errors.Default.New(fmt.Sprintf("%s
PluginEntry must implement PluginMeta interface", pluginName))
}
- err = plugin.RegisterPlugin(pluginName, pluginMeta)
- if err != nil {
- return err
- }
-
- basicRes.GetLogger().Info(`plugin loaded %s`,
pluginName)
+ wg.Add(1)
+ go func(pluginName string, pluginMeta
plugin.PluginMeta) {
+ defer func() {
+ wg.Done()
+ }()
+ err = plugin.RegisterPlugin(pluginName,
pluginMeta)
+ if err != nil {
+ panic(err)
+ }
+ basicRes.GetLogger().Info(`plugin loaded %s`,
pluginName)
+ }(pluginName, pluginMeta)
}
return nil
})
+ wg.Wait()
return errors.Convert(walkErr)
}
diff --git a/backend/plugins/dora/impl/impl.go
b/backend/plugins/dora/impl/impl.go
index b20ce189d..f96a04b5b 100644
--- a/backend/plugins/dora/impl/impl.go
+++ b/backend/plugins/dora/impl/impl.go
@@ -119,10 +119,13 @@ func (p Dora) MigrationScripts() []plugin.MigrationScript
{
func (p Dora) MakeMetricPluginPipelinePlanV200(projectName string, options
json.RawMessage) (coreModels.PipelinePlan, errors.Error) {
op := &tasks.DoraOptions{}
- err := json.Unmarshal(options, op)
- if err != nil {
- return nil, errors.Default.WrapRaw(err)
+ if options != nil && string(options) != "\"\"" {
+ err := json.Unmarshal(options, op)
+ if err != nil {
+ return nil, errors.Default.WrapRaw(err)
+ }
}
+
plan := coreModels.PipelinePlan{
{
{
diff --git a/backend/plugins/linker/README.md b/backend/plugins/linker/README.md
new file mode 100644
index 000000000..e9090fc2e
--- /dev/null
+++ b/backend/plugins/linker/README.md
@@ -0,0 +1,16 @@
+/*
+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.
+*/
diff --git a/backend/plugins/linker/e2e/link_pr_and_issue_test.go
b/backend/plugins/linker/e2e/link_pr_and_issue_test.go
new file mode 100644
index 000000000..a199764d4
--- /dev/null
+++ b/backend/plugins/linker/e2e/link_pr_and_issue_test.go
@@ -0,0 +1,70 @@
+/*
+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 (
+ "regexp"
+ "testing"
+
+ "github.com/apache/incubator-devlake/core/models/domainlayer/code"
+
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+ "github.com/apache/incubator-devlake/helpers/e2ehelper"
+ "github.com/apache/incubator-devlake/plugins/linker/impl"
+ "github.com/apache/incubator-devlake/plugins/linker/tasks"
+)
+
+func TestLinkPrToIssue(t *testing.T) {
+ var plugin impl.Linker
+ dataflowTester := e2ehelper.NewDataFlowTester(t, "issue_linker", plugin)
+
+ regexpStr := "#(\\d+)"
+ re, err := regexp.Compile(regexpStr)
+ if err != nil {
+ panic(err)
+ }
+ taskData := &tasks.LinkerTaskData{
+ Options: &tasks.LinkerOptions{
+ PrToIssueRegexp: regexpStr,
+ ProjectName: "GitHub1",
+ },
+ PrToIssueRegexp: re,
+ }
+
+ dataflowTester.ImportCsvIntoTabler("./snapshot_tables/issues.csv",
&ticket.Issue{})
+
dataflowTester.ImportCsvIntoTabler("./snapshot_tables/pull_requests.csv",
&code.PullRequest{})
+
dataflowTester.ImportCsvIntoTabler("./snapshot_tables/project_mapping.csv",
&crossdomain.ProjectMapping{})
+
+ dataflowTester.FlushTabler(&crossdomain.PullRequestIssue{})
+ dataflowTester.Subtask(tasks.LinkPrToIssueMeta, taskData)
+ dataflowTester.VerifyTable(
+ crossdomain.PullRequestIssue{},
+ "./snapshot_tables/pull_request_issues.csv",
+ []string{
+ "pull_request_id",
+ "pull_request_key",
+ "issue_id",
+ "issue_key",
+ "_raw_data_params",
+ "_raw_data_table",
+ "_raw_data_id",
+ "_raw_data_remark",
+ },
+ )
+
+}
diff --git a/backend/plugins/linker/e2e/snapshot_tables/issues.csv
b/backend/plugins/linker/e2e/snapshot_tables/issues.csv
new file mode 100644
index 000000000..a7d1fd0f9
--- /dev/null
+++ b/backend/plugins/linker/e2e/snapshot_tables/issues.csv
@@ -0,0 +1,2 @@
+"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","url","icon_url","issue_key","title","description","epic_key","type","original_type","status","original_status","resolution_date","created_date","updated_date","lead_time_minutes","parent_issue_id","priority","story_point","original_estimate_minutes","time_spent_minutes","time_remaining_minutes","creator_id","creator_name","assignee_id","assignee_name","severity","component","original_pr
[...]
+"github:GithubIssue:1:1237324696","2024-05-14 10:42:37.529","2024-05-15
12:07:36.450","{""ConnectionId"":1,""Name"":""apache/incubator-devlake""}","_raw_github_graphql_issues",59,"","https://github.com/apache/incubator-devlake/issues/1884","","1884","Add
a plugin for
Ones","desc","","","type/feature-request,Stale,add-a-plugin","TODO","OPEN","2032-05-16
15:23:21.000","2022-05-16 15:23:21.000","2024-05-11
00:17:21.000",10,"","",11,1,12,11,"github:GithubAccount:1:14050754","Startrekzky","",
[...]
diff --git a/backend/plugins/linker/e2e/snapshot_tables/project_mapping.csv
b/backend/plugins/linker/e2e/snapshot_tables/project_mapping.csv
new file mode 100644
index 000000000..70e57627e
--- /dev/null
+++ b/backend/plugins/linker/e2e/snapshot_tables/project_mapping.csv
@@ -0,0 +1,2 @@
+"project_name","table","row_id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark"
+"GitHub1","cicd_scopes","github:GithubRepo:1:384111310","2024-05-15
12:02:13.590","2024-05-15 12:02:13.590","GitHub1","",0,""
\ No newline at end of file
diff --git a/backend/plugins/linker/e2e/snapshot_tables/pull_request_issues.csv
b/backend/plugins/linker/e2e/snapshot_tables/pull_request_issues.csv
new file mode 100644
index 000000000..4f952b6fd
--- /dev/null
+++ b/backend/plugins/linker/e2e/snapshot_tables/pull_request_issues.csv
@@ -0,0 +1,2 @@
+pull_request_id,issue_id,pull_request_key,issue_key,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
+github:GithubPullRequest:1:1819250573,github:GithubIssue:1:1237324696,7317,1884,,,0,"pull_requests,"
diff --git a/backend/plugins/linker/e2e/snapshot_tables/pull_requests.csv
b/backend/plugins/linker/e2e/snapshot_tables/pull_requests.csv
new file mode 100644
index 000000000..bf260bf0c
--- /dev/null
+++ b/backend/plugins/linker/e2e/snapshot_tables/pull_requests.csv
@@ -0,0 +1,2 @@
+"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","base_repo_id","base_ref","base_commit_sha","head_repo_id","head_ref","head_commit_sha","merge_commit_sha","status","original_status","type","component","title","description","url","author_name","author_id","parent_pr_id","pull_request_key","created_date","merged_date","closed_date"
+"github:GithubPullRequest:1:1819250573","2024-05-15 12:07:36.778","2024-05-15
12:07:36.778","{""ConnectionId"":1,""Name"":""apache/incubator-devlake""}","_raw_github_api_pull_requests",191,"","github:GithubRepo:1:384111310","main","64c52748f3529784cb6c8a372691aa0f638fa73d","github:GithubRepo:1:384111310","fix#7275","14fb6488f2208e6a65374a86efce12dd460987e0","91dbce48759da14a4a030124c3ef751f1c5d8389","CLOSED","closed","","","fix:
can't GET projects which have / in their name #1884","desc" [...]
\ No newline at end of file
diff --git a/backend/plugins/linker/impl/impl.go
b/backend/plugins/linker/impl/impl.go
new file mode 100644
index 000000000..917b22df9
--- /dev/null
+++ b/backend/plugins/linker/impl/impl.go
@@ -0,0 +1,127 @@
+/*
+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 impl
+
+import (
+ "encoding/json"
+ "regexp"
+
+ "github.com/apache/incubator-devlake/core/dal"
+ "github.com/apache/incubator-devlake/core/errors"
+ coreModels "github.com/apache/incubator-devlake/core/models"
+ "github.com/apache/incubator-devlake/core/plugin"
+
"github.com/apache/incubator-devlake/plugins/linker/models/migrationscripts"
+ "github.com/apache/incubator-devlake/plugins/linker/tasks"
+)
+
+// make sure interface is implemented
+var _ interface {
+ plugin.PluginMeta
+ plugin.PluginTask
+ plugin.PluginModel
+ plugin.PluginMetric
+ plugin.PluginMigration
+ plugin.MetricPluginBlueprintV200
+} = (*Linker)(nil)
+
+type Linker struct{}
+
+func (p Linker) Description() string {
+ return "link some cross table datas together"
+}
+
+// RequiredDataEntities hasn't been used so far
+func (p Linker) RequiredDataEntities() (data []map[string]interface{}, err
errors.Error) {
+ return []map[string]interface{}{}, nil
+}
+
+func (p Linker) GetTablesInfo() []dal.Tabler {
+ return []dal.Tabler{}
+}
+
+func (p Linker) Name() string {
+ return "linker"
+}
+
+func (p Linker) IsProjectMetric() bool {
+ return true
+}
+
+func (p Linker) RunAfter() ([]string, errors.Error) {
+ return []string{}, nil
+}
+
+func (p Linker) Settings() interface{} {
+ return nil
+}
+
+func (p Linker) SubTaskMetas() []plugin.SubTaskMeta {
+ return []plugin.SubTaskMeta{
+ tasks.LinkPrToIssueMeta,
+ }
+}
+
+func (p Linker) PrepareTaskData(taskCtx plugin.TaskContext, options
map[string]interface{}) (interface{}, errors.Error) {
+ op, err := tasks.DecodeAndValidateTaskOptions(options)
+ if err != nil {
+ return nil, err
+ }
+ taskData := &tasks.LinkerTaskData{
+ Options: op,
+ }
+ if op.PrToIssueRegexp != "" {
+ re, err := regexp.Compile(op.PrToIssueRegexp)
+ if err != nil {
+ return taskData, errors.Convert(err)
+ }
+ taskData.PrToIssueRegexp = re
+ }
+ return taskData, nil
+}
+
+// RootPkgPath information lost when compiled as plugin(.so)
+func (p Linker) RootPkgPath() string {
+ return "github.com/apache/incubator-devlake/plugins/linker"
+}
+
+func (p Linker) MigrationScripts() []plugin.MigrationScript {
+ return migrationscripts.All()
+}
+
+func (p Linker) MakeMetricPluginPipelinePlanV200(projectName string, options
json.RawMessage) (coreModels.PipelinePlan, errors.Error) {
+ op := &tasks.LinkerOptions{}
+ err := json.Unmarshal(options, op)
+ if err != nil {
+ return nil, errors.Default.WrapRaw(err)
+ }
+ plan := coreModels.PipelinePlan{
+ {
+ {
+ Plugin: "linker",
+ Options: map[string]interface{}{
+ "projectName": projectName,
+ "prToIssueRegexp": op.PrToIssueRegexp,
+ },
+ Subtasks: []string{
+ "LinkPrToIssue",
+ },
+ },
+ },
+ }
+ return plan, nil
+}
diff --git a/backend/plugins/linker/linker.go b/backend/plugins/linker/linker.go
new file mode 100644
index 000000000..0f1bc4565
--- /dev/null
+++ b/backend/plugins/linker/linker.go
@@ -0,0 +1,42 @@
+/*
+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 main
+
+import (
+ "github.com/apache/incubator-devlake/core/runner"
+ "github.com/apache/incubator-devlake/plugins/linker/impl"
+ "github.com/spf13/cobra"
+)
+
+// PluginEntry exports for Framework to search and load
+var PluginEntry impl.Linker //nolint
+
+// standalone mode for debugging
+func main() {
+ cmd := &cobra.Command{Use: "linker"}
+
+ projectName := cmd.Flags().StringP("projectName", "p", "", "project
name")
+ timeAfter := cmd.Flags().StringP("timeAfter", "a", "", "collect data
that are created after specified time, ie 2006-01-02T15:04:05Z")
+
+ cmd.Run = func(cmd *cobra.Command, args []string) {
+ runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+ "projectName": *projectName,
+ }, *timeAfter)
+ }
+ runner.RunCmd(cmd)
+}
diff --git a/backend/plugins/linker/models/migrationscripts/register.go
b/backend/plugins/linker/models/migrationscripts/register.go
new file mode 100644
index 000000000..0aaa5373b
--- /dev/null
+++ b/backend/plugins/linker/models/migrationscripts/register.go
@@ -0,0 +1,27 @@
+/*
+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/plugin"
+)
+
+// All return all the migration scripts
+func All() []plugin.MigrationScript {
+ return []plugin.MigrationScript{}
+}
diff --git a/backend/plugins/linker/tasks/link_pr_and_issue.go
b/backend/plugins/linker/tasks/link_pr_and_issue.go
new file mode 100644
index 000000000..4456f1947
--- /dev/null
+++ b/backend/plugins/linker/tasks/link_pr_and_issue.go
@@ -0,0 +1,102 @@
+/*
+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 (
+ "github.com/apache/incubator-devlake/core/models/domainlayer/code"
+
"github.com/apache/incubator-devlake/core/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/core/models/domainlayer/ticket"
+ "strings"
+
+ "github.com/apache/incubator-devlake/core/dal"
+ "github.com/apache/incubator-devlake/core/errors"
+ "github.com/apache/incubator-devlake/core/plugin"
+ "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+var LinkPrToIssueMeta = plugin.SubTaskMeta{
+ Name: "LinkPrToIssue",
+ EntryPoint: LinkPrToIssue,
+ EnabledByDefault: true,
+ Description: "Try to link pull requests to issues, according to
pull requests' title and description",
+ DependencyTables: []string{code.PullRequest{}.TableName(),
ticket.Issue{}.TableName()},
+ DomainTypes: []string{plugin.DOMAIN_TYPE_CODE,
plugin.DOMAIN_TYPE_TICKET, plugin.DOMAIN_TYPE_CROSS},
+ ProductTables: []string{crossdomain.PullRequestIssue{}.TableName()},
+}
+
+func normalizeIssueKey(issueKey string) string {
+ issueKey = strings.ReplaceAll(issueKey, "#", "")
+ issueKey = strings.TrimSpace(issueKey)
+ return issueKey
+}
+
+func LinkPrToIssue(taskCtx plugin.SubTaskContext) errors.Error {
+ db := taskCtx.GetDal()
+ data := taskCtx.GetData().(*LinkerTaskData)
+ var clauses = []dal.Clause{
+ dal.From(&code.PullRequest{}),
+ dal.Join("LEFT JOIN project_mapping pm ON (pm.table =
'cicd_scopes' AND pm.row_id = pull_requests.base_repo_id)"),
+ dal.Where("pm.project_name = ?", data.Options.ProjectName),
+ }
+ cursor, err := db.Cursor(clauses...)
+ if err != nil {
+ return err
+ }
+
+ defer cursor.Close()
+
+ // iterate all rows
+ enricher, err :=
api.NewDataEnricher(api.DataEnricherArgs[code.PullRequest]{
+ Ctx: taskCtx,
+ Name: code.PullRequest{}.TableName(),
+ Input: cursor,
+ Enrich: func(pullRequest *code.PullRequest) ([]interface{},
errors.Error) {
+
+ issueKey := ""
+ for _, text := range []string{pullRequest.Title,
pullRequest.Description} {
+ issueKey = data.PrToIssueRegexp.FindString(text)
+ if issueKey != "" {
+ break
+ }
+ }
+ issueKey = normalizeIssueKey(issueKey)
+ if issueKey == "" {
+ return nil, nil
+ }
+
+ issue := &ticket.Issue{}
+ if err := db.First(issue, dal.Where("issue_key = ?",
issueKey)); err != nil {
+ return nil, err
+ }
+
+ pullRequestIssue := &crossdomain.PullRequestIssue{
+ PullRequestId: pullRequest.Id,
+ IssueId: issue.Id,
+ PullRequestKey: pullRequest.PullRequestKey,
+ IssueKey: issueKey,
+ }
+
+ return []interface{}{pullRequestIssue}, nil
+ },
+ })
+ if err != nil {
+ return err
+ }
+
+ return enricher.Execute()
+}
diff --git a/backend/plugins/linker/tasks/task_data.go
b/backend/plugins/linker/tasks/task_data.go
new file mode 100644
index 000000000..37a37c358
--- /dev/null
+++ b/backend/plugins/linker/tasks/task_data.go
@@ -0,0 +1,43 @@
+/*
+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 (
+ "github.com/apache/incubator-devlake/core/errors"
+ helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+ "regexp"
+)
+
+type LinkerOptions struct {
+ PrToIssueRegexp string `json:"prToIssueRegexp"`
+ ProjectName string `json:"projectName"`
+}
+
+type LinkerTaskData struct {
+ Options *LinkerOptions
+ PrToIssueRegexp *regexp.Regexp
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{})
(*LinkerOptions, errors.Error) {
+ var op LinkerOptions
+ err := helper.Decode(options, &op, nil)
+ if err != nil {
+ return nil, errors.Default.Wrap(err, "error decoding linker
task options")
+ }
+ return &op, nil
+}
diff --git a/backend/plugins/table_info_test.go
b/backend/plugins/table_info_test.go
index 7360b978b..937ba14a8 100644
--- a/backend/plugins/table_info_test.go
+++ b/backend/plugins/table_info_test.go
@@ -39,6 +39,7 @@ import (
icla "github.com/apache/incubator-devlake/plugins/icla/impl"
jenkins "github.com/apache/incubator-devlake/plugins/jenkins/impl"
jira "github.com/apache/incubator-devlake/plugins/jira/impl"
+ linker "github.com/apache/incubator-devlake/plugins/linker/impl"
opsgenie "github.com/apache/incubator-devlake/plugins/opsgenie/impl"
org "github.com/apache/incubator-devlake/plugins/org/impl"
pagerduty "github.com/apache/incubator-devlake/plugins/pagerduty/impl"
@@ -88,6 +89,7 @@ func Test_GetPluginTablesInfo(t *testing.T) {
checker.FeedIn("zentao/models", zentao.Zentao{}.GetTablesInfo)
checker.FeedIn("circleci/models", circleci.Circleci{}.GetTablesInfo)
checker.FeedIn("opsgenie/models", opsgenie.Opsgenie{}.GetTablesInfo)
+ checker.FeedIn("linker/models", linker.Linker{}.GetTablesInfo)
err := checker.Verify()
if err != nil {
t.Error(err)