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 49fc227c6 Refactor Chage Lead Time - Step 5: enrich 
cicd_deployment_commits.prev_success_deployment_commit_id (#4922)
49fc227c6 is described below

commit 49fc227c6bca2492b35537aa9e362a7ae0daa886
Author: Klesh Wong <[email protected]>
AuthorDate: Fri Apr 14 10:44:55 2023 +0800

    Refactor Chage Lead Time - Step 5: enrich 
cicd_deployment_commits.prev_success_deployment_commit_id (#4922)
    
    * fix: rename cicd_deployment_commit
    
    * refactor: simplify join condition for deployment_commits_generator
    
    * feat: add data_enricher helper
    
    * feat: add prev_deployment_commit_enricher
    
    * feat: e2e test for prev_deployment_commit_enricher
    
    * fix: unit-test and comments
---
 backend/core/models/common/base.go                 |   8 ++
 ...icd_deployment.go => cicd_deployment_commit.go} |   0
 .../helpers/pluginhelper/api/batch_save_divider.go |  17 +--
 .../pluginhelper/api/batch_save_divider_test.go    |   9 +-
 backend/helpers/pluginhelper/api/data_enricher.go  | 124 +++++++++++++++++++++
 .../e2e/prev_deployment_commit_enricher_test.go    |  58 ++++++++++
 .../cicd_deployment_commits_after.csv              |  17 +++
 .../cicd_deployment_commits_before.csv             |  17 +++
 .../project_mapping.csv                            |   9 ++
 .../cicd_deployment_commits_before_enrich.csv      |  16 +++
 backend/plugins/dora/impl/impl.go                  |   1 +
 .../dora/tasks/deployment_commits_generator.go     |   4 +-
 .../dora/tasks/prev_deployment_commit_enricher.go  | 106 ++++++++++++++++++
 13 files changed, 373 insertions(+), 13 deletions(-)

diff --git a/backend/core/models/common/base.go 
b/backend/core/models/common/base.go
index 4ea92c962..1f37e73fc 100644
--- a/backend/core/models/common/base.go
+++ b/backend/core/models/common/base.go
@@ -45,6 +45,14 @@ type RawDataOrigin struct {
        RawDataRemark string `gorm:"column:_raw_data_remark" 
json:"_raw_data_remark"`
 }
 
+type GetRawDataOrigin interface {
+       GetRawDataOrigin() *RawDataOrigin
+}
+
+func (c *RawDataOrigin) GetRawDataOrigin() *RawDataOrigin {
+       return c
+}
+
 func NewNoPKModel() NoPKModel {
        now := time.Now()
        return NoPKModel{
diff --git a/backend/core/models/domainlayer/devops/cicd_deployment.go 
b/backend/core/models/domainlayer/devops/cicd_deployment_commit.go
similarity index 100%
rename from backend/core/models/domainlayer/devops/cicd_deployment.go
rename to backend/core/models/domainlayer/devops/cicd_deployment_commit.go
diff --git a/backend/helpers/pluginhelper/api/batch_save_divider.go 
b/backend/helpers/pluginhelper/api/batch_save_divider.go
index f1a10e32f..b0c3431bb 100644
--- a/backend/helpers/pluginhelper/api/batch_save_divider.go
+++ b/backend/helpers/pluginhelper/api/batch_save_divider.go
@@ -19,12 +19,13 @@ package api
 
 import (
        "fmt"
+       "reflect"
+
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/log"
        "github.com/apache/incubator-devlake/core/models/common"
-       "reflect"
 )
 
 // BatchSaveDivider creates and caches BatchSave, this is helpful when dealing 
with massive amount of data records
@@ -76,12 +77,14 @@ func (d *BatchSaveDivider) ForType(rowType reflect.Type) 
(*BatchSave, errors.Err
                }
                // all good, delete outdated records before we insertion
                d.log.Debug("deleting outdate records for %s", 
rowElemType.Name())
-               err = d.db.Delete(
-                       row,
-                       dal.Where("_raw_data_table = ? AND _raw_data_params = 
?", d.table, d.params),
-               )
-               if err != nil {
-                       return nil, err
+               if d.table != "" && d.params != "" {
+                       err = d.db.Delete(
+                               row,
+                               dal.Where("_raw_data_table = ? AND 
_raw_data_params = ?", d.table, d.params),
+                       )
+                       if err != nil {
+                               return nil, err
+                       }
                }
        }
        return batch, nil
diff --git a/backend/helpers/pluginhelper/api/batch_save_divider_test.go 
b/backend/helpers/pluginhelper/api/batch_save_divider_test.go
index 13ec366ad..8d269162e 100644
--- a/backend/helpers/pluginhelper/api/batch_save_divider_test.go
+++ b/backend/helpers/pluginhelper/api/batch_save_divider_test.go
@@ -18,13 +18,14 @@ limitations under the License.
 package api
 
 import (
+       "reflect"
+       "testing"
+       "time"
+
        "github.com/apache/incubator-devlake/core/models/common"
        "github.com/apache/incubator-devlake/helpers/unithelper"
        mockcontext "github.com/apache/incubator-devlake/mocks/core/context"
        mockdal "github.com/apache/incubator-devlake/mocks/core/dal"
-       "reflect"
-       "testing"
-       "time"
 
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/mock"
@@ -66,7 +67,7 @@ func TestBatchSaveDivider(t *testing.T) {
                },
        )
 
-       divider := NewBatchSaveDivider(mockRes, 10, "", "")
+       divider := NewBatchSaveDivider(mockRes, 10, "a", "b")
 
        // for same type should return the same BatchSave
        jiraIssue1, err := divider.ForType(reflect.TypeOf(&MockJirIssueBsd{}))
diff --git a/backend/helpers/pluginhelper/api/data_enricher.go 
b/backend/helpers/pluginhelper/api/data_enricher.go
new file mode 100644
index 000000000..0024b755f
--- /dev/null
+++ b/backend/helpers/pluginhelper/api/data_enricher.go
@@ -0,0 +1,124 @@
+/*
+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 (
+       "reflect"
+       "regexp"
+       "strings"
+
+       "github.com/apache/incubator-devlake/core/dal"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/models/common"
+       plugin "github.com/apache/incubator-devlake/core/plugin"
+)
+
+// DataEnrichHandler Accepts row from the Input and produces arbitrary records.
+// you are free to modify given `row` in place and include it in returned 
result for it to be saved.
+type DataEnrichHandler[InputRowType any] func(row *InputRowType) 
([]interface{}, errors.Error)
+
+// DataEnricherArgs includes the arguments needed for data enrichment
+type DataEnricherArgs[InputRowType any] struct {
+       Ctx       plugin.SubTaskContext
+       Name      string // Enricher name, which will be put into 
_raw_data_remark
+       Input     dal.Rows
+       Enrich    DataEnrichHandler[InputRowType]
+       BatchSize int
+}
+
+// DataEnricher helps you enrich Data with Cancellation and BatchSave supports
+type DataEnricher[InputRowType any] struct {
+       args *DataEnricherArgs[InputRowType]
+}
+
+var dataEnricherNamePattern = regexp.MustCompile(`^\w+$`)
+
+// NewDataEnricher creates a new DataEnricher
+func NewDataEnricher[InputRowType any](args DataEnricherArgs[InputRowType]) 
(*DataEnricher[InputRowType], errors.Error) {
+       // process args
+       if args.Name == "" || !dataEnricherNamePattern.MatchString(args.Name) {
+               return nil, errors.Default.New("DataEnricher: Name is require 
and should contain only word characters(a-zA-Z0-9_)")
+       }
+       if args.BatchSize == 0 {
+               args.BatchSize = 500
+       }
+       return &DataEnricher[InputRowType]{
+               args: &args,
+       }, nil
+}
+
+func (enricher *DataEnricher[InputRowType]) Execute() errors.Error {
+       // load data from database
+       db := enricher.args.Ctx.GetDal()
+
+       // batch save divider
+       divider := NewBatchSaveDivider(enricher.args.Ctx, 
enricher.args.BatchSize, "", "")
+
+       // set progress
+       enricher.args.Ctx.SetProgress(0, -1)
+
+       cursor := enricher.args.Input
+       defer cursor.Close()
+       ctx := enricher.args.Ctx.GetContext()
+       // iterate all rows
+       for cursor.Next() {
+               select {
+               case <-ctx.Done():
+                       return errors.Convert(ctx.Err())
+               default:
+               }
+               inputRow := new(InputRowType)
+               err := db.Fetch(cursor, inputRow)
+               if err != nil {
+                       return errors.Default.Wrap(err, "error fetching rows")
+               }
+
+               results, err := enricher.args.Enrich(inputRow)
+               if err != nil {
+                       return errors.Default.Wrap(err, "error calling plugin 
implementation")
+               }
+
+               for _, result := range results {
+                       // get the batch operator for the specific type
+                       batch, err := divider.ForType(reflect.TypeOf(result))
+                       if err != nil {
+                               return errors.Default.Wrap(err, "error getting 
batch from result")
+                       }
+                       // append enricher to data origin remark
+                       if getRawDataOrigin, ok := 
result.(common.GetRawDataOrigin); ok {
+                               origin := getRawDataOrigin.GetRawDataOrigin()
+                               enricherComponent := enricher.args.Name + "," 
// name is word characters only
+                               if !strings.Contains(origin.RawDataRemark, 
enricherComponent) {
+                                       origin.RawDataRemark += 
enricherComponent
+                               }
+                       }
+                       // records get saved into db when slots were max outed
+                       err = batch.Add(result)
+                       if err != nil {
+                               return errors.Default.Wrap(err, "error adding 
result to batch")
+                       }
+               }
+               enricher.args.Ctx.IncProgress(1)
+       }
+
+       // save the last batches
+       return divider.Close()
+}
+
+// Check if DataEnricher implements SubTask interface
+var _ plugin.SubTask = (*DataEnricher[any])(nil)
diff --git a/backend/plugins/dora/e2e/prev_deployment_commit_enricher_test.go 
b/backend/plugins/dora/e2e/prev_deployment_commit_enricher_test.go
new file mode 100644
index 000000000..13f7e72f2
--- /dev/null
+++ b/backend/plugins/dora/e2e/prev_deployment_commit_enricher_test.go
@@ -0,0 +1,58 @@
+/*
+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/domainlayer/crossdomain"
+       "github.com/apache/incubator-devlake/core/models/domainlayer/devops"
+       "github.com/apache/incubator-devlake/helpers/e2ehelper"
+       "github.com/apache/incubator-devlake/plugins/dora/impl"
+       "github.com/apache/incubator-devlake/plugins/dora/tasks"
+)
+
+func TestPrevSuccessDeploymentCommitEnricherDataFlow(t *testing.T) {
+       var plugin impl.Dora
+       dataflowTester := e2ehelper.NewDataFlowTester(t, "dora", plugin)
+
+       taskData := &tasks.DoraTaskData{
+               Options: &tasks.DoraOptions{
+                       ProjectName: "project1",
+               },
+       }
+       // import raw data table
+       
dataflowTester.ImportCsvIntoTabler("./prev_success_deployment_commit/project_mapping.csv",
 &crossdomain.ProjectMapping{})
+       
dataflowTester.ImportCsvIntoTabler("./prev_success_deployment_commit/cicd_deployment_commits_before.csv",
 &devops.CicdDeploymentCommit{})
+
+       // verify converter
+       dataflowTester.Subtask(tasks.EnrichPrevSuccessDeploymentCommitMeta, 
taskData)
+       dataflowTester.VerifyTableWithOptions(&devops.CicdDeploymentCommit{}, 
e2ehelper.TableOptions{
+               CSVRelPath: 
"./prev_success_deployment_commit/cicd_deployment_commits_after.csv",
+               TargetFields: []string{
+                       "id",
+                       "result",
+                       "started_date",
+                       "cicd_pipeline_id",
+                       "cicd_scope_id",
+                       "repo_url",
+                       "environment",
+                       "prev_success_deployment_commit_id",
+               },
+       })
+}
diff --git 
a/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_after.csv
 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_after.csv
new file mode 100644
index 000000000..b0d2b8f2d
--- /dev/null
+++ 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_after.csv
@@ -0,0 +1,17 @@
+id,result,started_date,cicd_pipeline_id,cicd_scope_id,repo_url,environment,prev_success_deployment_commit_id,_raw_data_remark,commit_sha,created_date
+1,SUCCESS,2023-04-10T06:51:47.000+00:00,pipeline1,cicd1,REPO111,PRODUCTION,,,1,2023-4-10
 6:51:47
+2,SUCCESS,2023-04-10T06:53:51.000+00:00,pipeline1,cicd1,REPO222,PRODUCTION,,,2,2023-4-10
 6:53:51
+3,SUCCESS,2023-04-13T07:21:16.000+00:00,pipeline2,cicd2,REPO111,PRODUCTION,,,3,2023-4-13
 7:21:16
+4,SUCCESS,2023-04-13T07:22:14.000+00:00,pipeline2,cicd2,REPO333,PRODUCTION,,,4,2023-4-13
 7:22:14
+5,SUCCESS,2023-04-13T07:28:14.000+00:00,pipeline4,cicd1,REPO111,PRODUCTION,1,,5,2023-4-13
 7:28:14
+6,SUCCESS,2023-04-13T07:29:34.000+00:00,pipeline4,cicd1,REPO222,PRODUCTION,2,,6,2023-4-13
 7:29:34
+7,SUCCESS,2023-04-13T07:31:53.000+00:00,pipeline5,cicd1,REPO111,STAGING,,,7,2023-4-13
 7:31:53
+8,SUCCESS,2023-04-13T07:36:30.000+00:00,pipeline5,cicd1,REPO111,STAGING,7,,8,2023-4-13
 7:36:30
+9,SUCCESS,2023-04-13T07:51:26.000+00:00,pipeline6,cicd1,REPO111,PRODUCTION,5,,9,2023-4-13
 7:51:26
+10,SUCCESS,2023-04-13T07:53:31.000+00:00,pipeline6,cicd1,REPO222,PRODUCTION,6,,10,2023-4-13
 7:53:31
+11,FAILURE,2023-04-13T07:54:39.000+00:00,pipeline7,cicd2,REPO111,PRODUCTION,,failed
 record should be ignored,11,2023-4-13 7:54:39
+12,SUCCESS,2023-04-13T07:55:01.000+00:00,pipeline7,cicd2,REPO333,PRODUCTION,4,,12,2023-4-13
 7:55:01
+13,SUCCESS,2023-04-13T07:56:39.000+00:00,pipeline7,cicd2,REPO111,PRODUCTION,3,retried
 and success should be ok,13,2023-4-13 7:56:39
+14,FAILURE,2023-04-13T07:57:26.000+00:00,pipeline8,cicd3,REPO111,PRODUCTION,,,14,2023-4-13
 7:57:26
+15,SUCCESS,2023-04-13T07:57:45.000+00:00,pipeline9,cicd3,REPO111,PRODUCTION,,not
 belongs to the project,15,2023-4-13 7:57:45
+16,SUCCESS,2023-04-13T07:58:24.000+00:00,pipeline10,cicd3,REPO333,,,,16,2023-4-13
 7:58:24
\ No newline at end of file
diff --git 
a/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_before.csv
 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_before.csv
new file mode 100644
index 000000000..367ca6d64
--- /dev/null
+++ 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/cicd_deployment_commits_before.csv
@@ -0,0 +1,17 @@
+id,result,started_date,cicd_pipeline_id,cicd_scope_id,repo_url,environment,prev_success_deployment_commit_id,_raw_data_remark,commit_sha,created_date
+1,SUCCESS,2023-04-10T06:51:47.000+00:00,pipeline1,cicd1,REPO111,PRODUCTION,,,1,2023-4-10
 6:51:47
+2,SUCCESS,2023-04-10T06:53:51.000+00:00,pipeline1,cicd1,REPO222,PRODUCTION,,,2,2023-4-10
 6:53:51
+3,SUCCESS,2023-04-13T07:21:16.000+00:00,pipeline2,cicd2,REPO111,PRODUCTION,,,3,2023-4-13
 7:21:16
+4,SUCCESS,2023-04-13T07:22:14.000+00:00,pipeline2,cicd2,REPO333,PRODUCTION,,,4,2023-4-13
 7:22:14
+5,SUCCESS,2023-04-13T07:28:14.000+00:00,pipeline4,cicd1,REPO111,PRODUCTION,,,5,2023-4-13
 7:28:14
+6,SUCCESS,2023-04-13T07:29:34.000+00:00,pipeline4,cicd1,REPO222,PRODUCTION,,,6,2023-4-13
 7:29:34
+7,SUCCESS,2023-04-13T07:31:53.000+00:00,pipeline5,cicd1,REPO111,STAGING,,,7,2023-4-13
 7:31:53
+8,SUCCESS,2023-04-13T07:36:30.000+00:00,pipeline5,cicd1,REPO111,STAGING,,,8,2023-4-13
 7:36:30
+9,SUCCESS,2023-04-13T07:51:26.000+00:00,pipeline6,cicd1,REPO111,PRODUCTION,,,9,2023-4-13
 7:51:26
+10,SUCCESS,2023-04-13T07:53:31.000+00:00,pipeline6,cicd1,REPO222,PRODUCTION,,,10,2023-4-13
 7:53:31
+11,FAILURE,2023-04-13T07:54:39.000+00:00,pipeline7,cicd2,REPO111,PRODUCTION,,failed
 record should be ignored,11,2023-4-13 7:54:39
+12,SUCCESS,2023-04-13T07:55:01.000+00:00,pipeline7,cicd2,REPO333,PRODUCTION,,,12,2023-4-13
 7:55:01
+13,SUCCESS,2023-04-13T07:56:39.000+00:00,pipeline7,cicd2,REPO111,PRODUCTION,,retried
 and success should be ok,13,2023-4-13 7:56:39
+14,FAILURE,2023-04-13T07:57:26.000+00:00,pipeline8,cicd3,REPO111,PRODUCTION,,,14,2023-4-13
 7:57:26
+15,SUCCESS,2023-04-13T07:57:45.000+00:00,pipeline9,cicd3,REPO111,PRODUCTION,,not
 belongs to the project,15,2023-4-13 7:57:45
+16,SUCCESS,2023-04-13T07:58:24.000+00:00,pipeline10,cicd3,REPO333,,,,16,2023-4-13
 7:58:24
\ No newline at end of file
diff --git 
a/backend/plugins/dora/e2e/prev_success_deployment_commit/project_mapping.csv 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/project_mapping.csv
new file mode 100644
index 000000000..296ab9e2f
--- /dev/null
+++ 
b/backend/plugins/dora/e2e/prev_success_deployment_commit/project_mapping.csv
@@ -0,0 +1,9 @@
+project_name,table,row_id
+project1,cicd_scopes,cicd1
+project1,cicd_scopes,cicd2
+project1,repos,repo1
+project1,repos,repo2
+project2,cicd_scopes,cicd3
+project1,boards,board1
+project1,boards,board2
+project2,boards,board3
\ No newline at end of file
diff --git 
a/backend/plugins/dora/e2e/raw_tables/cicd_deployment_commits_before_enrich.csv 
b/backend/plugins/dora/e2e/raw_tables/cicd_deployment_commits_before_enrich.csv
new file mode 100644
index 000000000..fd31d1d4b
--- /dev/null
+++ 
b/backend/plugins/dora/e2e/raw_tables/cicd_deployment_commits_before_enrich.csv
@@ -0,0 +1,16 @@
+"id","created_at","updated_at","_raw_data_params","_raw_data_table","_raw_data_id","_raw_data_remark","cicd_scope_id","cicd_pipeline_id","name","result","status","environment","created_date","started_date","duration_sec","commit_sha","ref_name","repo_id","repo_url","prev_success_deployment_commit_id"
+bitbucket:BitbucketPipeline:1:{1cf1fe33-0a60-4b54-bf95-841619d258f5}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,792,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{1cf1fe33-0a60-4b54-bf95-841619d258f5},bitbucket:BitbucketPipeline:1:Klesh-Wong/readmemd-edited-online-with-bitbucket-1681372451595,FAILURE
 [...]
+bitbucket:BitbucketPipeline:1:{28639065-9cd6-4fff-9771-2fa2d88143e4}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,795,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{28639065-9cd6-4fff-9771-2fa2d88143e4},bitbucket:BitbucketPipeline:1:master,SUCCESS,DONE,"","2023-04-13
 07:57:23.685","2023-04-13 07:57:44. [...]
+bitbucket:BitbucketPipeline:1:{36e9e1d4-33c1-4321-9112-09e15ce57f00}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-12
 08:41:54.584","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,780,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{36e9e1d4-33c1-4321-9112-09e15ce57f00},bitbucket:BitbucketPipeline:1:master,SUCCESS,DONE,"","2023-04-10
 06:51:08.154","2023-04-10 06:51:47. [...]
+bitbucket:BitbucketPipeline:1:{4a4882ad-7ecf-42a2-a287-c495dc68d81b}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,788,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{4a4882ad-7ecf-42a2-a287-c495dc68d81b},bitbucket:BitbucketPipeline:1:production,SUCCESS,DONE,PRODUCTION,"2023-04-13
 07:31:31.128","2023-04- [...]
+bitbucket:BitbucketPipeline:1:{5009185c-615d-40b2-ba1a-1da5dee75e04}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,794,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{5009185c-615d-40b2-ba1a-1da5dee75e04},bitbucket:BitbucketPipeline:1:Klesh-Wong/readmemd-edited-online-with-bitbucket-1681372618019,FAILURE
 [...]
+bitbucket:BitbucketPipeline:1:{566033c8-cdbb-41c2-8b65-3e633e264cea}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,784,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{566033c8-cdbb-41c2-8b65-3e633e264cea},bitbucket:BitbucketPipeline:1:Klesh-Wong/bitbucketpipelinesyml-edited-online-with-1681370423083,SUCC
 [...]
+bitbucket:BitbucketPipeline:1:{67933764-eef4-4453-803f-767f849da3d9}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,786,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{67933764-eef4-4453-803f-767f849da3d9},bitbucket:BitbucketPipeline:1:master,SUCCESS,DONE,"","2023-04-13
 07:27:52.416","2023-04-13 07:28:13. [...]
+bitbucket:BitbucketPipeline:1:{8052b6ec-5112-4366-bf2b-23b6ef1c608f}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,796,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{8052b6ec-5112-4366-bf2b-23b6ef1c608f},bitbucket:BitbucketPipeline:1:production,SUCCESS,DONE,PRODUCTION,"2023-04-13
 07:58:02.261","2023-04- [...]
+bitbucket:BitbucketPipeline:1:{81a18389-648c-4505-bd1e-cd06550eedf7}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,789,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{81a18389-648c-4505-bd1e-cd06550eedf7},bitbucket:BitbucketPipeline:1:Klesh-Wong/readmemd-edited-online-with-bitbucket-1681371361733,SUCCESS
 [...]
+bitbucket:BitbucketPipeline:1:{840f1e2b-bd50-4654-a817-8212a1c76274}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,793,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{840f1e2b-bd50-4654-a817-8212a1c76274},bitbucket:BitbucketPipeline:1:master,SUCCESS,DONE,"","2023-04-13
 07:54:38.809","2023-04-13 07:55:00. [...]
+bitbucket:BitbucketPipeline:1:{a7ca0bdc-2850-45aa-8b0c-3a8f843a528e}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,791,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{a7ca0bdc-2850-45aa-8b0c-3a8f843a528e},bitbucket:BitbucketPipeline:1:master,SUCCESS,DONE,"","2023-04-13
 07:53:09.218","2023-04-13 07:53:30. [...]
+bitbucket:BitbucketPipeline:1:{b3ed67b1-f577-448a-b846-40fc6179f994}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,787,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{b3ed67b1-f577-448a-b846-40fc6179f994},bitbucket:BitbucketPipeline:1:Klesh-Wong/readmemd-edited-online-with-bitbucket-1681370936423,SUCCESS
 [...]
+bitbucket:BitbucketPipeline:1:{b9ff0087-7621-4259-82de-37cd8a2e6f03}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-12
 08:41:54.584","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,783,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{b9ff0087-7621-4259-82de-37cd8a2e6f03},bitbucket:BitbucketPipeline:1:production,SUCCESS,DONE,PRODUCTION,"2023-04-10
 06:53:15.843","2023-04- [...]
+bitbucket:BitbucketPipeline:1:{db499f52-5afb-4833-810c-0ea7d4190cfc}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,785,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{db499f52-5afb-4833-810c-0ea7d4190cfc},bitbucket:BitbucketPipeline:1:production,SUCCESS,DONE,PRODUCTION,"2023-04-13
 07:21:43.105","2023-04- [...]
+bitbucket:BitbucketPipeline:1:{e804c02f-e91a-4340-b392-bfbf965e97c1}:https://bitbucket.org/zhenmianws/helloworldrepo,"2023-04-13
 08:56:48.427","2023-04-13 
08:56:48.427","{""ConnectionId"":1,""FullName"":""zhenmianws/helloworldrepo""}",_raw_bitbucket_api_pipelines,790,"",bitbucket:BitbucketRepo:1:zhenmianws/helloworldrepo,bitbucket:BitbucketPipeline:1:{e804c02f-e91a-4340-b392-bfbf965e97c1},bitbucket:BitbucketPipeline:1:production,SUCCESS,DONE,PRODUCTION,"2023-04-13
 07:51:04.418","2023-04- [...]
diff --git a/backend/plugins/dora/impl/impl.go 
b/backend/plugins/dora/impl/impl.go
index e4bbcfaa8..0ffd1400d 100644
--- a/backend/plugins/dora/impl/impl.go
+++ b/backend/plugins/dora/impl/impl.go
@@ -83,6 +83,7 @@ func (p Dora) Settings() interface{} {
 func (p Dora) SubTaskMetas() []plugin.SubTaskMeta {
        return []plugin.SubTaskMeta{
                tasks.DeploymentCommitsGeneratorMeta,
+               tasks.EnrichPrevSuccessDeploymentCommitMeta,
                tasks.EnrichTaskEnvMeta,
                tasks.CalculateChangeLeadTimeMeta,
                tasks.ConnectIncidentToDeploymentMeta,
diff --git a/backend/plugins/dora/tasks/deployment_commits_generator.go 
b/backend/plugins/dora/tasks/deployment_commits_generator.go
index 2aae4a4d7..aebe18b65 100644
--- a/backend/plugins/dora/tasks/deployment_commits_generator.go
+++ b/backend/plugins/dora/tasks/deployment_commits_generator.go
@@ -77,7 +77,7 @@ func GenerateDeploymentCommits(taskCtx plugin.SubTaskContext) 
errors.Error {
                `),
                dal.From("cicd_pipeline_commits pc"),
                dal.Join("LEFT JOIN cicd_pipelines p ON (p.id = 
pc.pipeline_id)"),
-               dal.Join("LEFT JOIN project_mapping pm ON (pm.table = ? AND 
pm.row_id = p.cicd_scope_id)"),
+               dal.Join("LEFT JOIN project_mapping pm ON (pm.table = 
'cicd_scopes' AND pm.row_id = p.cicd_scope_id)"),
                dal.Where(
                        `
                        pm.project_name = ? AND (
@@ -85,7 +85,7 @@ func GenerateDeploymentCommits(taskCtx plugin.SubTaskContext) 
errors.Error {
                                        SELECT 1 FROM cicd_tasks t WHERE 
t.pipeline_id = p.id AND t.type = p.type
                                )
                        )
-                       `, devops.STAGING, devops.PRODUCTION, "cicd_scopes", 
data.Options.ProjectName, devops.DEPLOYMENT,
+                       `, devops.TESTING, devops.STAGING, devops.PRODUCTION, 
data.Options.ProjectName, devops.DEPLOYMENT,
                ),
        )
        if err != nil {
diff --git a/backend/plugins/dora/tasks/prev_deployment_commit_enricher.go 
b/backend/plugins/dora/tasks/prev_deployment_commit_enricher.go
new file mode 100644
index 000000000..8cb3741e4
--- /dev/null
+++ b/backend/plugins/dora/tasks/prev_deployment_commit_enricher.go
@@ -0,0 +1,106 @@
+/*
+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/dal"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/core/models/domainlayer/devops"
+       "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+)
+
+var EnrichPrevSuccessDeploymentCommitMeta = plugin.SubTaskMeta{
+       Name:             "enrichPrevSuccessDeploymentCommits",
+       EntryPoint:       EnrichPrevSuccessDeploymentCommit,
+       EnabledByDefault: true,
+       Description:      "filling the prev_success_deployment_commit_id for 
cicd_deployment_commits table",
+       DomainTypes:      []string{plugin.DOMAIN_TYPE_CODE},
+}
+
+// EnrichPrevSuccessDeploymentCommit
+// Please note that deploying multiple environment (such as TESTING) copies
+// (such as testing1 and testing2) using multiple steps with Deployment tools
+// like Bitbucket or Gitlab is not supported and may result in incorrect
+// outcomes. It is recommended that you deploy all copies in a single step.
+// We arrived at this decision because we believe that deploying multiple
+// environment copies using multiple steps is not a common or reasonable
+// practice. However, if you have strong evidence to suggest otherwise, you are
+// free to file an issue on our GitHub repository.
+func EnrichPrevSuccessDeploymentCommit(taskCtx plugin.SubTaskContext) 
errors.Error {
+       db := taskCtx.GetDal()
+       data := taskCtx.GetData().(*DoraTaskData)
+       // step 1. select all successful deployments in the project and sort 
them by cicd_scope_id, repo_url, env
+       // and started_date
+       cursor, err := db.Cursor(
+               dal.Select("dc.*"),
+               dal.From("cicd_deployment_commits dc"),
+               dal.Join("LEFT JOIN project_mapping pm ON (pm.table = 
'cicd_scopes' AND pm.row_id = dc.cicd_scope_id)"),
+               dal.Where(
+                       `
+                       dc.started_date IS NOT NULL
+                       AND dc.environment IS NOT NULL AND dc.environment != ''
+                       AND dc.repo_url IS NOT NULL AND dc.repo_url != '' 
+                       AND pm.project_name = ? AND dc.result = ?
+                       `,
+                       data.Options.ProjectName, devops.SUCCESS,
+               ),
+               dal.Orderby(`dc.cicd_scope_id, dc.repo_url, dc.environment, 
dc.started_date`),
+       )
+       if err != nil {
+               return err
+       }
+       defer cursor.Close()
+
+       prev_cicd_scope_id := ""
+       prev_repo_url := ""
+       prev_env := ""
+       prev_success_deployment_id := ""
+
+       enricher, err := 
api.NewDataEnricher(api.DataEnricherArgs[devops.CicdDeploymentCommit]{
+               Ctx:   taskCtx,
+               Name:  "prev_deployment_commit_id_enricher",
+               Input: cursor,
+               Enrich: func(deploymentCommit *devops.CicdDeploymentCommit) 
([]interface{}, errors.Error) {
+                       // step 2. group them by cicd_scope_id/repo_url/env
+                       // whenever cicd_scope_id/repo_url/env shifted, it is a 
new set of consecutive deployments
+                       if prev_cicd_scope_id != deploymentCommit.CicdScopeId ||
+                               prev_repo_url != deploymentCommit.RepoUrl ||
+                               prev_env != deploymentCommit.Environment {
+                               // reset prev_success_deployment_id
+                               prev_success_deployment_id = ""
+                       }
+
+                       // now, simply connect the consecurtive deployment to 
its previous one
+                       deploymentCommit.PrevSuccessDeploymentCommitId = 
prev_success_deployment_id
+                       println("paire", deploymentCommit.Id, 
prev_cicd_scope_id)
+
+                       // preserve variables for the next record
+                       prev_cicd_scope_id = deploymentCommit.CicdScopeId
+                       prev_repo_url = deploymentCommit.RepoUrl
+                       prev_env = deploymentCommit.Environment
+                       prev_success_deployment_id = deploymentCommit.Id
+                       return []interface{}{deploymentCommit}, nil
+               },
+       })
+       if err != nil {
+               return err
+       }
+
+       return enricher.Execute()
+}

Reply via email to