You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by li...@apache.org on 2022/11/09 10:22:19 UTC
[incubator-devlake] branch main updated: feat: added MetricPluginBlueprintV200 interface (#3677)
This is an automated email from the ASF dual-hosted git repository.
likyh 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 2a593b92 feat: added MetricPluginBlueprintV200 interface (#3677)
2a593b92 is described below
commit 2a593b92a8f6d95b911a1ca810ea944929312a27
Author: Klesh Wong <zh...@merico.dev>
AuthorDate: Wed Nov 9 18:22:06 2022 +0800
feat: added MetricPluginBlueprintV200 interface (#3677)
* feat: added MetricPluginBlueprintV200 interface
* fix: method names conflict / add projectName
* fix: move `Make` to the beginning
* feat: added unit test for blueprint_makeplan_v200
* docs: update comments for blueprint_makeplan_v200_test
* fix: remove useless interface
* feat: save project/scopes to project_mapping table
* refactor: rename GeneratePlanJson to MakePlan
GeneratePlanJson was a pure function. but in blueprint protocol
v2.0.0, this is no longer ture, because it requires project_mapping
to be updated before blueprint being saved. Better to rename it to
avoid misconception
---
plugins/core/plugin_blueprint.go | 70 +++++++++++++-----
services/blueprint.go | 107 +++++++++------------------
services/blueprint_makeplan_v100.go | 78 ++++++++++++++++++++
services/blueprint_makeplan_v200.go | 122 +++++++++++++++++++++++++++++++
services/blueprint_makeplan_v200_test.go | 97 ++++++++++++++++++++++++
services/blueprint_test.go | 18 ++---
6 files changed, 393 insertions(+), 99 deletions(-)
diff --git a/plugins/core/plugin_blueprint.go b/plugins/core/plugin_blueprint.go
index 38daebf9..cb1d8f7e 100644
--- a/plugins/core/plugin_blueprint.go
+++ b/plugins/core/plugin_blueprint.go
@@ -68,9 +68,10 @@ PluginBlueprintV200 for project support
step 1: blueprint.settings like
{
"version": "2.0.0",
- "scopes": [
+ "connections": [
{
"plugin": "github",
+ "connectionId": 123,
"scopes": [
{ "id": null, "name": "apache/incubator-devlake" }
]
@@ -101,29 +102,64 @@ step 3: framework should maintain the project_mapping table based on the []Scope
]
*/
-// Scope represents the top level entity for a data source, i.e. github repo, gitlab project, jira board.
-// They turn into repo, board in Domain Layer.
-// In Apache Devlake, a Project is essentially a set of these top level entities, for the framework to
-// maintain these relationships dynamically and automatically, all Domain Layer Top Level Entities should
-// implement this interface
+// Scope represents the top level entity for a data source, i.e. github repo,
+// gitlab project, jira board. They turn into repo, board in Domain Layer. In
+// Apache Devlake, a Project is essentially a set of these top level entities,
+// for the framework to maintain these relationships dynamically and
+// automatically, all Domain Layer Top Level Entities should implement this
+// interface
type Scope interface {
ScopeId() string
ScopeName() string
TableName() string
}
-// PluginBlueprintV200 extends the V100 to provide support for Project to support complex metrics
-// like DORA
-type PluginBlueprintV200 interface {
- MakePipelinePlan(scopes []*BlueprintScopeV200) (PipelinePlan, []Scope, errors.Error)
+// DataSourcePluginBlueprintV200 extends the V100 to provide support for
+// Project, so that complex metrics like DORA can be implemented based on a set
+// of Data Scopes
+type DataSourcePluginBlueprintV200 interface {
+ MakeDataSourcePipelinePlanV200(connectionId uint64, scopes []*BlueprintScopeV200) (PipelinePlan, []Scope, errors.Error)
}
-// BlueprintScopeV200 contains the Plugin name and related ScopeIds, connectionId and transformationRuleId should be
-// deduced by the ScopeId
+// BlueprintConnectionV200 contains the pluginName/connectionId and related Scopes,
+type BlueprintConnectionV200 struct {
+ Plugin string `json:"plugin" validate:"required"`
+ ConnectionId uint64 `json:"connectionId" validate:"required"`
+ Scopes []*BlueprintScopeV200 `json:"scopes" validate:"required"`
+}
+
+// BlueprintScopeV200 contains the `id` and `name` for a specific scope
+// transformationRuleId should be deduced by the ScopeId
type BlueprintScopeV200 struct {
- Plugin string `json:"plugin" validate:"required"`
- Scopes []struct {
- Id string
- Name string
- }
+ Id string `json:"id"`
+ Name string `json:"name"`
+}
+
+// MetricPluginBlueprintV200 is similar to the DataSourcePluginBlueprintV200
+// but for Metric Plugin, take dora as an example, it doens't have any scope,
+// nor does it produce any, however, it does require other plugin to be
+// executed beforehand, like calcuating refdiff before it can connect PR to the
+// right Deployment keep in mind it would be called IFF the plugin was enabled
+// for the project.
+type MetricPluginBlueprintV200 interface {
+ MakeMetricPluginPipelinePlanV200(projectName string, options json.RawMessage) (PipelinePlan, errors.Error)
+}
+
+// CompositeDataSourcePluginBlueprintV200 is for unit test
+type CompositeDataSourcePluginBlueprintV200 interface {
+ PluginMeta
+ DataSourcePluginBlueprintV200
+}
+
+// CompositeMetricPluginBlueprintV200 is for unit test
+type CompositeMetricPluginBlueprintV200 interface {
+ PluginMeta
+ MetricPluginBlueprintV200
+}
+
+// CompositeMetricPluginBlueprintV200 is for unit test
+type CompositePluginBlueprintV200 interface {
+ PluginMeta
+ DataSourcePluginBlueprintV200
+ MetricPluginBlueprintV200
}
diff --git a/services/blueprint.go b/services/blueprint.go
index edb4a0e3..5c59b6fc 100644
--- a/services/blueprint.go
+++ b/services/blueprint.go
@@ -47,7 +47,7 @@ var (
// CreateBlueprint accepts a Blueprint instance and insert it to database
func CreateBlueprint(blueprint *models.Blueprint) errors.Error {
- err := validateBlueprint(blueprint)
+ err := validateBlueprintAndMakePlan(blueprint)
if err != nil {
return err
}
@@ -102,7 +102,7 @@ func GetBlueprint(blueprintId uint64) (*models.Blueprint, errors.Error) {
return blueprint, nil
}
-func validateBlueprint(blueprint *models.Blueprint) errors.Error {
+func validateBlueprintAndMakePlan(blueprint *models.Blueprint) errors.Error {
// validation
err := vld.Struct(blueprint)
if err != nil {
@@ -130,10 +130,14 @@ func validateBlueprint(blueprint *models.Blueprint) errors.Error {
return errors.Default.New("empty plan")
}
} else if blueprint.Mode == models.BLUEPRINT_MODE_NORMAL {
- blueprint.Plan, err = GeneratePlanJson(blueprint.Settings)
+ plan, err := MakePlanForBlueprint(blueprint)
if err != nil {
return errors.Default.Wrap(err, "invalid plan")
}
+ blueprint.Plan, err = errors.Convert01(json.Marshal(plan))
+ if err != nil {
+ return errors.Default.Wrap(err, "failed to markshal plan")
+ }
}
return nil
@@ -157,7 +161,7 @@ func PatchBlueprint(id uint64, body map[string]interface{}) (*models.Blueprint,
return nil, errors.Default.New("mode is not updatable")
}
// validation
- err = validateBlueprint(blueprint)
+ err = validateBlueprintAndMakePlan(blueprint)
if err != nil {
return nil, errors.BadInput.WrapRaw(err)
}
@@ -246,15 +250,15 @@ func createPipelineByBlueprint(blueprintId uint64, name string, plan core.Pipeli
return pipeline, nil
}
-// GeneratePlanJson generates pipeline plan by version
-func GeneratePlanJson(settings json.RawMessage) (json.RawMessage, errors.Error) {
+// MakePlanForBlueprint generates pipeline plan by version
+func MakePlanForBlueprint(blueprint *models.Blueprint) (core.PipelinePlan, errors.Error) {
bpSettings := new(models.BlueprintSettings)
- err := errors.Convert(json.Unmarshal(settings, bpSettings))
+ err := errors.Convert(json.Unmarshal(blueprint.Settings, bpSettings))
if err != nil {
- return nil, errors.Default.Wrap(err, fmt.Sprintf("settings:%s", string(settings)))
+ return nil, errors.Default.Wrap(err, fmt.Sprintf("settings:%s", string(blueprint.Settings)))
}
- var plan interface{}
+ var plan core.PipelinePlan
switch bpSettings.Version {
case "1.0.0":
plan, err = GeneratePlanJsonV100(bpSettings)
@@ -264,87 +268,33 @@ func GeneratePlanJson(settings json.RawMessage) (json.RawMessage, errors.Error)
if err != nil {
return nil, err
}
- return errors.Convert01(json.Marshal(plan))
+ return WrapPipelinePlans(bpSettings.BeforePlan, plan, bpSettings.AfterPlan)
}
-// GeneratePlanJsonV100 generates pipeline plan according v1.0.0 definition
-func GeneratePlanJsonV100(settings *models.BlueprintSettings) (core.PipelinePlan, errors.Error) {
- connections := make([]*core.BlueprintConnectionV100, 0)
- err := errors.Convert(json.Unmarshal(settings.Connections, &connections))
- 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 {
- return nil, errors.Default.New(fmt.Sprintf("connections[%d].scope is empty", i))
- }
- plugin, err := core.GetPlugin(connection.Plugin)
- if err != nil {
- return nil, err
- }
- if pluginBp, ok := plugin.(core.PluginBlueprintV100); ok {
- plans[i], err = pluginBp.MakePipelinePlan(connection.ConnectionId, connection.Scope)
- if err != nil {
- return nil, err
- }
- } 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 {
- plan := core.PipelineStage{
- &core.PipelineTask{
- Plugin: "dora",
- Subtasks: []string{"calculateChangeLeadTime", "ConnectIssueDeploy"},
- Options: doraRules,
- },
- }
- mergedPipelinePlan = append(mergedPipelinePlan, plan)
- }
- return FormatPipelinePlans(settings.BeforePlan, mergedPipelinePlan, settings.AfterPlan)
-}
+// WrapPipelinePlans merges multiple pipelines and append before and after pipeline
+func WrapPipelinePlans(beforePlanJson json.RawMessage, mainPlan core.PipelinePlan, afterPlanJson json.RawMessage) (core.PipelinePlan, errors.Error) {
+ beforePipelinePlan := core.PipelinePlan{}
+ afterPipelinePlan := core.PipelinePlan{}
-// FormatPipelinePlans merges multiple pipelines and append before and after pipeline
-func FormatPipelinePlans(beforePlanJson json.RawMessage, mainPlan core.PipelinePlan, afterPlanJson json.RawMessage) (core.PipelinePlan, errors.Error) {
- newPipelinePlan := core.PipelinePlan{}
if beforePlanJson != nil {
- beforePipelinePlan := core.PipelinePlan{}
err := errors.Convert(json.Unmarshal(beforePlanJson, &beforePipelinePlan))
if err != nil {
return nil, err
}
- newPipelinePlan = append(newPipelinePlan, beforePipelinePlan...)
}
-
- newPipelinePlan = append(newPipelinePlan, mainPlan...)
-
if afterPlanJson != nil {
- afterPipelinePlan := core.PipelinePlan{}
err := errors.Convert(json.Unmarshal(afterPlanJson, &afterPipelinePlan))
if err != nil {
return nil, err
}
- newPipelinePlan = append(newPipelinePlan, afterPipelinePlan...)
}
- return newPipelinePlan, nil
+
+ return SequencializePipelinePlans(beforePipelinePlan, mainPlan, afterPipelinePlan), nil
}
-// MergePipelinePlans merges multiple pipelines into one unified pipeline
-func MergePipelinePlans(plans ...core.PipelinePlan) core.PipelinePlan {
+// ParallelizePipelinePlans merges multiple pipelines into one unified plan
+// by assuming they can be executed in parallel
+func ParallelizePipelinePlans(plans ...core.PipelinePlan) core.PipelinePlan {
merged := make(core.PipelinePlan, 0)
// iterate all pipelineTasks and try to merge them into `merged`
for _, plan := range plans {
@@ -360,6 +310,17 @@ func MergePipelinePlans(plans ...core.PipelinePlan) core.PipelinePlan {
return merged
}
+// SequencializePipelinePlans merges multiple pipelines into one unified plan
+// by assuming they must be executed in sequencial order
+func SequencializePipelinePlans(plans ...core.PipelinePlan) core.PipelinePlan {
+ merged := make(core.PipelinePlan, 0)
+ // iterate all pipelineTasks and try to merge them into `merged`
+ for _, plan := range plans {
+ merged = append(merged, plan...)
+ }
+ return merged
+}
+
// TriggerBlueprint triggers blueprint immediately
func TriggerBlueprint(id uint64) (*models.Pipeline, errors.Error) {
// load record from db
diff --git a/services/blueprint_makeplan_v100.go b/services/blueprint_makeplan_v100.go
new file mode 100644
index 00000000..f3bd47d6
--- /dev/null
+++ b/services/blueprint_makeplan_v100.go
@@ -0,0 +1,78 @@
+/*
+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 services
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/models"
+ "github.com/apache/incubator-devlake/plugins/core"
+)
+
+// GeneratePlanJsonV100 generates pipeline plan according v1.0.0 definition
+func GeneratePlanJsonV100(settings *models.BlueprintSettings) (core.PipelinePlan, errors.Error) {
+ connections := make([]*core.BlueprintConnectionV100, 0)
+ err := errors.Convert(json.Unmarshal(settings.Connections, &connections))
+ 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 {
+ return nil, errors.Default.New(fmt.Sprintf("connections[%d].scope is empty", i))
+ }
+ plugin, err := core.GetPlugin(connection.Plugin)
+ if err != nil {
+ return nil, err
+ }
+ if pluginBp, ok := plugin.(core.PluginBlueprintV100); ok {
+ plans[i], err = pluginBp.MakePipelinePlan(connection.ConnectionId, connection.Scope)
+ if err != nil {
+ return nil, err
+ }
+ } 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 := ParallelizePipelinePlans(plans...)
+ if hasDoraEnrich {
+ plan := core.PipelineStage{
+ &core.PipelineTask{
+ Plugin: "dora",
+ Subtasks: []string{"calculateChangeLeadTime", "ConnectIssueDeploy"},
+ Options: doraRules,
+ },
+ }
+ mergedPipelinePlan = append(mergedPipelinePlan, plan)
+ }
+ return mergedPipelinePlan, nil
+}
diff --git a/services/blueprint_makeplan_v200.go b/services/blueprint_makeplan_v200.go
new file mode 100644
index 00000000..c4835579
--- /dev/null
+++ b/services/blueprint_makeplan_v200.go
@@ -0,0 +1,122 @@
+/*
+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 services
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/models"
+ "github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+ "github.com/apache/incubator-devlake/plugins/core"
+)
+
+// GeneratePlanJsonV200 generates pipeline plan according v2.0.0 definition
+func GeneratePlanJsonV200(
+ projectName string,
+ sources *models.BlueprintSettings,
+ metrics map[string]json.RawMessage,
+) (core.PipelinePlan, errors.Error) {
+ // generate plan and collect scopes
+ plan, scopes, err := genPlanJsonV200(projectName, sources, metrics)
+ if err != nil {
+ return nil, err
+ }
+ // refresh project_mapping table to reflect project/scopes relationship
+ if len(scopes) > 0 {
+ e := db.Where("project_name = ?", projectName).Delete(&crossdomain.ProjectMapping{}).Error
+ if e != nil {
+ return nil, errors.Convert(err)
+ }
+ e = db.Create(scopes).Error
+ if e != nil {
+ return nil, errors.Convert(err)
+ }
+ }
+ return plan, err
+}
+
+func genPlanJsonV200(
+ projectName string,
+ sources *models.BlueprintSettings,
+ metrics map[string]json.RawMessage,
+) (core.PipelinePlan, []core.Scope, errors.Error) {
+ connections := make([]*core.BlueprintConnectionV200, 0)
+ err := errors.Convert(json.Unmarshal(sources.Connections, &connections))
+ if err != nil {
+ return nil, nil, err
+ }
+
+ // make plan for data-source plugins fist. generate plan for each
+ // connections, then merge them into one legitimate plan and collect the
+ // scopes produced by the data-source plugins
+ sourcePlans := make([]core.PipelinePlan, len(connections))
+ scopes := make([]core.Scope, 0, len(connections))
+ for i, connection := range connections {
+ if len(connection.Scopes) == 0 {
+ return nil, nil, errors.Default.New(fmt.Sprintf("connections[%d].scope is empty", i))
+ }
+ plugin, err := core.GetPlugin(connection.Plugin)
+ if err != nil {
+ return nil, nil, err
+ }
+ if pluginBp, ok := plugin.(core.DataSourcePluginBlueprintV200); ok {
+ var pluginScopes []core.Scope
+ sourcePlans[i], pluginScopes, err = pluginBp.MakeDataSourcePipelinePlanV200(
+ connection.ConnectionId,
+ connection.Scopes,
+ )
+ if err != nil {
+ return nil, nil, err
+ }
+ // collect scopes for the project. a github repository may produces
+ // 2 scopes, 1 repo and 1 board
+ scopes = append(scopes, pluginScopes...)
+ } else {
+ return nil, nil, errors.Default.New(
+ fmt.Sprintf("plugin %s does not support DataSourcePluginBlueprintV200", connection.Plugin),
+ )
+ }
+ }
+ // make plans for metric plugins
+ metricPlans := make([]core.PipelinePlan, len(metrics))
+ i := 0
+ for metricPluginName, metricPluginOptJson := range metrics {
+ plugin, err := core.GetPlugin(metricPluginName)
+ if err != nil {
+ return nil, nil, err
+ }
+ if pluginBp, ok := plugin.(core.MetricPluginBlueprintV200); ok {
+ metricPlans[i], err = pluginBp.MakeMetricPluginPipelinePlanV200(projectName, metricPluginOptJson)
+ if err != nil {
+ return nil, nil, err
+ }
+ i += 1
+ } else {
+ return nil, nil, errors.Default.New(
+ fmt.Sprintf("plugin %s does not support MetricPluginBlueprintV200", metricPluginName),
+ )
+ }
+ }
+ plan := SequencializePipelinePlans(
+ ParallelizePipelinePlans(sourcePlans...),
+ ParallelizePipelinePlans(metricPlans...),
+ )
+ return plan, scopes, err
+}
diff --git a/services/blueprint_makeplan_v200_test.go b/services/blueprint_makeplan_v200_test.go
new file mode 100644
index 00000000..7cee6bf8
--- /dev/null
+++ b/services/blueprint_makeplan_v200_test.go
@@ -0,0 +1,97 @@
+/*
+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 services
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/apache/incubator-devlake/mocks"
+ "github.com/apache/incubator-devlake/models"
+ "github.com/apache/incubator-devlake/models/domainlayer"
+ "github.com/apache/incubator-devlake/models/domainlayer/code"
+ "github.com/apache/incubator-devlake/models/domainlayer/ticket"
+ "github.com/apache/incubator-devlake/plugins/core"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMakePlanV200(t *testing.T) {
+ const projectName = "TestMakePlanV200-project"
+ githubName := "TestMakePlanV200-github" // mimic github
+ // mock github plugin as a data source plugin
+ githubConnId := uint64(1)
+ githubScopes := []*core.BlueprintScopeV200{
+ {Id: "", Name: "apache/incubator-devlake"},
+ {Id: "", Name: "apache/incubator-devlake-website"},
+ }
+ githubOutputPlan := core.PipelinePlan{
+ {
+ {Plugin: githubName, Options: map[string]interface{}{"name": "apache/incubator-devlake"}},
+ {Plugin: "gitextractor", Options: map[string]interface{}{"url": "http://gihub.com/apache/incubator-devlake.git"}},
+ },
+ {
+ {Plugin: githubName, Options: map[string]interface{}{"name": "apache/incubator-devlake-website"}},
+ {Plugin: "gitextractor", Options: map[string]interface{}{"url": "http://gihub.com/apache/incubator-devlake-website.git"}},
+ },
+ }
+ githubOutputScopes := []core.Scope{
+ &code.Repo{DomainEntity: domainlayer.DomainEntity{Id: "github:GithubRepo:1:123"}, Name: "apache/incubator-devlake"},
+ &ticket.Board{DomainEntity: domainlayer.DomainEntity{Id: "github:GithubRepo:1:123"}, Name: "apache/incubator-devlake"},
+ }
+ github := new(mocks.CompositeDataSourcePluginBlueprintV200)
+ github.On("MakeDataSourcePipelinePlanV200", githubConnId, githubScopes).Return(githubOutputPlan, githubOutputScopes, nil)
+
+ // mock dora plugin as a metric plugin
+ doraName := "TestMakePlanV200-dora"
+ doraOutputPlan := core.PipelinePlan{
+ {
+ {Plugin: "refdiff", Subtasks: []string{"calculateDeploymentDiffs"}, Options: map[string]interface{}{"projectName": projectName}},
+ {Plugin: doraName},
+ },
+ }
+ dora := new(mocks.CompositeMetricPluginBlueprintV200)
+ dora.On("MakeMetricPluginPipelinePlanV200", projectName, json.RawMessage(nil)).Return(doraOutputPlan, nil)
+
+ // expectation, establish expectation before any code being launch to avoid unwanted modification
+ expectedPlan := make(core.PipelinePlan, 0)
+ expectedPlan = append(expectedPlan, githubOutputPlan...)
+ expectedPlan = append(expectedPlan, doraOutputPlan...)
+ expectedScopes := append(make([]core.Scope, 0), githubOutputScopes...)
+
+ // plugin registration
+ core.RegisterPlugin(githubName, github)
+ core.RegisterPlugin(doraName, dora)
+
+ // put them together and call GeneratePlanJsonV200
+ connections, _ := json.Marshal([]*core.BlueprintConnectionV200{
+ {Plugin: githubName, ConnectionId: githubConnId, Scopes: githubScopes},
+ })
+ sources := &models.BlueprintSettings{
+ Version: "2.0.0",
+ Connections: connections,
+ }
+ metrics := map[string]json.RawMessage{
+ doraName: nil,
+ }
+
+ plan, scopes, err := genPlanJsonV200(projectName, sources, metrics)
+ assert.Nil(t, err)
+
+ assert.Equal(t, expectedPlan, plan)
+ assert.Equal(t, expectedScopes, scopes)
+}
diff --git a/services/blueprint_test.go b/services/blueprint_test.go
index 8c7862ad..b06be8d7 100644
--- a/services/blueprint_test.go
+++ b/services/blueprint_test.go
@@ -25,7 +25,7 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestMergePipelineTasks(t *testing.T) {
+func TestParallelizePipelineTasks(t *testing.T) {
plan1 := core.PipelinePlan{
{
{Plugin: "github"},
@@ -55,8 +55,8 @@ func TestMergePipelineTasks(t *testing.T) {
},
}
- assert.Equal(t, plan1, MergePipelinePlans(plan1))
- assert.Equal(t, plan2, MergePipelinePlans(plan2))
+ assert.Equal(t, plan1, ParallelizePipelinePlans(plan1))
+ assert.Equal(t, plan2, ParallelizePipelinePlans(plan2))
assert.Equal(
t,
core.PipelinePlan{
@@ -70,7 +70,7 @@ func TestMergePipelineTasks(t *testing.T) {
{Plugin: "gitextractor2"},
},
},
- MergePipelinePlans(plan1, plan2),
+ ParallelizePipelinePlans(plan1, plan2),
)
assert.Equal(
t,
@@ -90,11 +90,11 @@ func TestMergePipelineTasks(t *testing.T) {
{Plugin: "jenkins"},
},
},
- MergePipelinePlans(plan1, plan2, plan3),
+ ParallelizePipelinePlans(plan1, plan2, plan3),
)
}
-func TestFormatPipelinePlans(t *testing.T) {
+func TestWrapPipelinePlans(t *testing.T) {
beforePlan2 := json.RawMessage(`[[{"plugin":"github"},{"plugin":"gitlab"}],[{"plugin":"gitextractor1"},{"plugin":"gitextractor2"}]]`)
mainPlan := core.PipelinePlan{
@@ -105,11 +105,11 @@ func TestFormatPipelinePlans(t *testing.T) {
afterPlan2 := json.RawMessage(`[[{"plugin":"jenkins"}],[{"plugin":"jenkins"}]]`)
- result1, err1 := FormatPipelinePlans(nil, mainPlan, nil)
+ result1, err1 := WrapPipelinePlans(nil, mainPlan, nil)
assert.Nil(t, err1)
assert.Equal(t, mainPlan, result1)
- result2, err2 := FormatPipelinePlans(beforePlan2, mainPlan, afterPlan2)
+ result2, err2 := WrapPipelinePlans(beforePlan2, mainPlan, afterPlan2)
assert.Nil(t, err2)
assert.Equal(t, core.PipelinePlan{
{
@@ -131,7 +131,7 @@ func TestFormatPipelinePlans(t *testing.T) {
},
}, result2)
- result3, err3 := FormatPipelinePlans(json.RawMessage("[]"), mainPlan, json.RawMessage("[]"))
+ result3, err3 := WrapPipelinePlans(json.RawMessage("[]"), mainPlan, json.RawMessage("[]"))
assert.Nil(t, err3)
assert.Equal(t, mainPlan, result3)
}