This is an automated email from the ASF dual-hosted git repository.

mappjzc 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 7fa174c0 feat(github): add dora enrich (#3128)
7fa174c0 is described below

commit 7fa174c0ac1d8de23e60aa64af8371520dcc7087
Author: Warren Chen <[email protected]>
AuthorDate: Tue Sep 20 17:15:58 2022 +0800

    feat(github): add dora enrich (#3128)
    
    * feat(github): add dora enrich
    
    relate to #3127
    
    * feat(github): add unit test for makePipeline
    
    * fix(github): modify unit test
    
    Co-authored-by: mappjzc <[email protected]>
---
 Makefile                             |   1 +
 plugins/github/api/blueprint.go      | 131 +++++++++++++++++++++++++----------
 plugins/github/api/blueprint_test.go |  96 +++++++++++++++++++++++++
 services/blueprint.go                |  24 ++++++-
 4 files changed, 215 insertions(+), 37 deletions(-)

diff --git a/Makefile b/Makefile
index 6bd35581..b9b70ebf 100644
--- a/Makefile
+++ b/Makefile
@@ -94,6 +94,7 @@ mock:
        mockery --dir=./plugins/core --unroll-variadic=false --name='.*'
        mockery --dir=./plugins/core/dal --unroll-variadic=false --name='.*'
        mockery --dir=./plugins/helper --unroll-variadic=false --name='.*'
+       mockery --dir=./plugins/github/api --unroll-variadic=false --name='.*'
 
 test: unit-test e2e-test
 
diff --git a/plugins/github/api/blueprint.go b/plugins/github/api/blueprint.go
index a5e77129..55f23592 100644
--- a/plugins/github/api/blueprint.go
+++ b/plugins/github/api/blueprint.go
@@ -37,7 +37,17 @@ import (
 )
 
 func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, connectionId uint64, 
scope []*core.BlueprintScopeV100) (core.PipelinePlan, errors.Error) {
+       repoHelper := RepoHelper{}
+       plan, err := DoMakePipeline(subtaskMetas, connectionId, scope, 
repoHelper)
+       if err != nil {
+               return nil, err
+       }
+       return plan, nil
+}
+
+func DoMakePipeline(subtaskMetas []core.SubTaskMeta, connectionId uint64, 
scope []*core.BlueprintScopeV100, repoHelper BpHelper) (core.PipelinePlan, 
errors.Error) {
        var err errors.Error
+       apiRepo, _, _ := &tasks.GithubApiRepo{}, "", ""
        plan := make(core.PipelinePlan, len(scope))
        for i, scopeElem := range scope {
                // handle taskOptions and transformationRules, by dumping them 
to taskOptions
@@ -94,43 +104,11 @@ func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, 
connectionId uint64, scop
                // collect git data by gitextractor if CODE was requested
                if utils.StringsContains(scopeElem.Entities, 
core.DOMAIN_TYPE_CODE) {
                        // here is the tricky part, we have to obtain the repo 
id beforehand
-                       connection := new(models.GithubConnection)
-                       err = connectionHelper.FirstById(connection, 
connectionId)
-                       if err != nil {
-                               return nil, err
-                       }
-                       token := strings.Split(connection.Token, ",")[0]
-                       apiClient, err := helper.NewApiClient(
-                               context.TODO(),
-                               connection.Endpoint,
-                               map[string]string{
-                                       "Authorization": fmt.Sprintf("Bearer 
%s", token),
-                               },
-                               10*time.Second,
-                               connection.Proxy,
-                               basicRes,
-                       )
-                       if err != nil {
-                               return nil, err
-                       }
-                       res, err := apiClient.Get(fmt.Sprintf("repos/%s/%s", 
op.Owner, op.Repo), nil, nil)
+                       res, token, proxy, err := 
repoHelper.GetApiRepo(connectionId, op)
+                       apiRepo = res.(*tasks.GithubApiRepo)
                        if err != nil {
                                return nil, err
                        }
-                       defer res.Body.Close()
-                       if res.StatusCode != http.StatusOK {
-                               return nil, 
errors.HttpStatus(res.StatusCode).New(fmt.Sprintf(
-                                       "unexpected status code when requesting 
repo detail from %s", res.Request.URL.String()))
-                       }
-                       body, err := errors.Convert01(io.ReadAll(res.Body))
-                       if err != nil {
-                               return nil, err
-                       }
-                       apiRepo := new(tasks.GithubApiRepo)
-                       err = errors.Convert(json.Unmarshal(body, apiRepo))
-                       if err != nil {
-                               return nil, errors.Convert(err)
-                       }
                        cloneUrl, err := 
errors.Convert01(url.Parse(apiRepo.CloneUrl))
                        if err != nil {
                                return nil, err
@@ -141,11 +119,94 @@ func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, 
connectionId uint64, scop
                                Options: map[string]interface{}{
                                        "url":    cloneUrl.String(),
                                        "repoId": 
didgen.NewDomainIdGenerator(&models.GithubRepo{}).Generate(connectionId, 
apiRepo.GithubId),
-                                       "proxy":  connection.Proxy,
+                                       "proxy":  proxy,
                                },
                        })
                }
                plan[i] = stage
+               // dora
+               if doraRules, ok := transformationRules["dora"]; ok && 
doraRules != nil {
+                       j := i + 1
+                       // add a new task to next stage
+                       if plan[j] != nil {
+                               j++
+                       }
+                       if j == len(plan) {
+                               plan = append(plan, nil)
+                       }
+                       if err != nil {
+                               return nil, err
+                       }
+                       if apiRepo.GithubId == 0 {
+                               res, _, _, err := 
repoHelper.GetApiRepo(connectionId, op)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               apiRepo = res.(*tasks.GithubApiRepo)
+
+                       }
+                       doraOption := make(map[string]interface{})
+                       doraOption["repoId"] = 
didgen.NewDomainIdGenerator(&models.GithubRepo{}).Generate(connectionId, 
apiRepo.GithubId)
+                       doraOption["tasks"] = []string{"EnrichTaskEnv"}
+                       doraOption["transformation"] = doraRules
+                       plan[j] = core.PipelineStage{
+                               {
+                                       Plugin:  "dora",
+                                       Options: doraOption,
+                               },
+                       }
+                       // remove it from github transformationRules
+                       delete(transformationRules, "dora")
+               }
        }
        return plan, nil
 }
+
+type BpHelper interface {
+       GetApiRepo(connectionId uint64, op interface{}) (interface{}, string, 
string, errors.Error)
+}
+
+type RepoHelper struct{}
+
+func (c RepoHelper) GetApiRepo(connectionId uint64, originOp interface{}) 
(interface{}, string, string, errors.Error) {
+       op := originOp.(*tasks.GithubOptions)
+       // here is the tricky part, we have to obtain the repo id beforehand
+       connection := new(models.GithubConnection)
+       err := connectionHelper.FirstById(connection, connectionId)
+       if err != nil {
+               return nil, "", "", err
+       }
+       token := strings.Split(connection.Token, ",")[0]
+       apiClient, err := helper.NewApiClient(
+               context.TODO(),
+               connection.Endpoint,
+               map[string]string{
+                       "Authorization": fmt.Sprintf("Bearer %s", token),
+               },
+               10*time.Second,
+               connection.Proxy,
+               basicRes,
+       )
+       if err != nil {
+               return nil, "", "", err
+       }
+       res, err := apiClient.Get(fmt.Sprintf("repos/%s/%s", op.Owner, 
op.Repo), nil, nil)
+       if err != nil {
+               return nil, "", "", err
+       }
+       defer res.Body.Close()
+       if res.StatusCode != http.StatusOK {
+               return nil, "", "", 
errors.HttpStatus(res.StatusCode).New(fmt.Sprintf(
+                       "unexpected status code when requesting repo detail 
from %s", res.Request.URL.String()))
+       }
+       body, err := errors.Convert01(io.ReadAll(res.Body))
+       if err != nil {
+               return nil, "", "", err
+       }
+       apiRepo := new(tasks.GithubApiRepo)
+       err = errors.Convert(json.Unmarshal(body, apiRepo))
+       if err != nil {
+               return nil, "", "", errors.Convert(err)
+       }
+       return apiRepo, token, connection.Proxy, nil
+}
diff --git a/plugins/github/api/blueprint_test.go 
b/plugins/github/api/blueprint_test.go
new file mode 100644
index 00000000..4df95d31
--- /dev/null
+++ b/plugins/github/api/blueprint_test.go
@@ -0,0 +1,96 @@
+/*
+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 api
+
+import (
+       "encoding/json"
+       "fmt"
+       "github.com/apache/incubator-devlake/mocks"
+       "github.com/apache/incubator-devlake/plugins/core"
+       "github.com/apache/incubator-devlake/plugins/github/models"
+       "github.com/apache/incubator-devlake/plugins/github/tasks"
+       "github.com/spf13/cobra"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+func TestMakePipelinePlan(t *testing.T) {
+       cmd := &cobra.Command{Use: "github"}
+       option := &tasks.GithubOptions{
+               ConnectionId: 1,
+               Tasks:        nil,
+               Since:        "",
+               Owner:        "merico-dev",
+               Repo:         "lake",
+               TransformationRules: models.TransformationRules{
+                       PrType:               "hey,man,wasup",
+                       PrComponent:          "component/(.*)$",
+                       PrBodyClosePattern:   
"(?mi)(fix|close|resolve|fixes|closes|resolves|fixed|closed|resolved)[\\s]*.*(((and
 )?(#|https:\\/\\/github.com\\/%s\\/%s\\/issues\\/)\\d+[ ]*)+)",
+                       IssueSeverity:        "severity/(.*)$",
+                       IssuePriority:        "^(highest|high|medium|low)$",
+                       IssueComponent:       "component/(.*)$",
+                       IssueTypeBug:         "^(bug|failure|error)$",
+                       IssueTypeIncident:    "",
+                       IssueTypeRequirement: 
"^(feat|feature|proposal|requirement)$",
+                       DeployTagPattern:     "(?i)deploy",
+               },
+       }
+       mockConnHelper := mocks.NewBpHelper(t)
+       mockConnHelper.On("GetApiRepo", uint64(1), 
option).Return(&tasks.GithubApiRepo{
+               Name:     "test",
+               GithubId: 1,
+               CloneUrl: "CloneUrl",
+       }, "", "", nil)
+       mockMeta := mocks.NewPluginMeta(t)
+       
mockMeta.On("RootPkgPath").Return("github.com/apache/incubator-devlake/plugins/github")
+
+       err := core.RegisterPlugin(cmd.Use, mockMeta)
+       if err != nil {
+               panic(err)
+       }
+       bs := &core.BlueprintScopeV100{
+               Entities: []string{"CODE"},
+               Options: json.RawMessage(`{
+              "repo": "lake",
+              "owner": "merico-dev"
+            }`),
+               Transformation: json.RawMessage(`{
+              "prType": "hey,man,wasup",
+              "refdiff": {
+                "tagsPattern": "pattern",
+                "tagsLimit": 10,
+                "tagsOrder": "reverse semver"
+              },
+              "dora": {
+                "environment": "pattern",
+                "environmentRegex": "xxxx"
+              }
+            }`),
+       }
+       scopes := make([]*core.BlueprintScopeV100, 0)
+       scopes = append(scopes, bs)
+       plan, err := DoMakePipeline(nil, 1, scopes, mockConnHelper)
+
+       assert.Nil(t, err)
+       planJson, err1 := json.Marshal(plan)
+       assert.Nil(t, err1)
+       fmt.Println(string(planJson))
+       expectPlan := 
`[[{"plugin":"github","subtasks":[],"options":{"connectionId":1,"owner":"merico-dev","repo":"lake","transformationRules":{"prType":"hey,man,wasup"}}},{"plugin":"gitextractor","subtasks":null,"options":{"proxy":"","repoId":"github:GithubRepo:1:1","url":"//git:@CloneUrl"}}],[{"plugin":"refdiff","subtasks":null,"options":{"tagsLimit":10,"tagsOrder":"reverse
 
semver","tagsPattern":"pattern"}}],[{"plugin":"dora","subtasks":null,"options":{"repoId":"github:GithubRepo:1:1","tasks"
 [...]
+       assert.Equal(t, expectPlan, string(planJson))
+
+}
diff --git a/services/blueprint.go b/services/blueprint.go
index f9dccc4c..dfaf3d44 100644
--- a/services/blueprint.go
+++ b/services/blueprint.go
@@ -274,7 +274,8 @@ func GeneratePlanJsonV100(settings 
*models.BlueprintSettings) (core.PipelinePlan
        if err != nil {
                return nil, err
        }
-
+       hasDoraEnrich := false
+       doraRules := make(map[string]interface{})
        plans := make([]core.PipelinePlan, len(connections))
        for i, connection := range connections {
                if len(connection.Scope) == 0 {
@@ -292,9 +293,28 @@ func GeneratePlanJsonV100(settings 
*models.BlueprintSettings) (core.PipelinePlan
                } else {
                        return nil, errors.Default.New(fmt.Sprintf("plugin %s 
does not support blueprint protocol version 1.0.0", connection.Plugin))
                }
+               for _, stage := range plans[i] {
+                       for _, task := range stage {
+                               if task.Plugin == "dora" {
+                                       hasDoraEnrich = true
+                                       for k, v := range task.Options {
+                                               doraRules[k] = v
+                                       }
+                               }
+                       }
+               }
        }
-
        mergedPipelinePlan := MergePipelinePlans(plans...)
+       if hasDoraEnrich {
+               doraRules["tasks"] = []string{"calculateChangeLeadTime", 
"ConnectIssueDeploy"}
+               plan := core.PipelineStage{
+                       &core.PipelineTask{
+                               Plugin:  "dora",
+                               Options: doraRules,
+                       },
+               }
+               mergedPipelinePlan = append(mergedPipelinePlan, plan)
+       }
        return FormatPipelinePlans(settings.BeforePlan, mergedPipelinePlan, 
settings.AfterPlan)
 }
 

Reply via email to