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

klesh 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 ceefbdb04 Zentao changelog (#5257)
ceefbdb04 is described below

commit ceefbdb04a3afac49b42104954b574b8544261ad
Author: mappjzc <[email protected]>
AuthorDate: Thu May 25 20:17:42 2023 +0800

    Zentao changelog (#5257)
    
    * feat: zentao support dbget for changelog
    
    Add DBGetActionHistory for Zentao
    
    Nddtfjiang <[email protected]>
    
    * feat: add dburl support
    
    Add connection support for dbget.
    Add blueprint support for dbget.
    
    Nddtfjiang <[email protected]>
    
    * feat: add e2e test for changelog
    
    Add TestZentaoDbGetDataFlow.
    Fix some struct error.
    Fix unit test error.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix e2e not support read
    
    Fix e2e not support read field.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for review
    
    Removed BaseDbConfigReader.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for review
    
    Remove blueprint dburl option
    Add db setting in connection
    
    Nddtfjiang <[email protected]>
---
 backend/core/runner/db.go                          |   7 +-
 backend/plugins/zentao/api/blueprint_v200.go       |   5 +-
 backend/plugins/zentao/e2e/changelog_test.go       |  95 ++++++++++++++++
 .../plugins/zentao/e2e/raw_tables/zt_action.csv    |  15 +++
 .../plugins/zentao/e2e/raw_tables/zt_history.csv   |  29 +++++
 .../e2e/snapshot_tables/_tool_zentao_changelog.csv |  13 +++
 .../_tool_zentao_changelog_detail.csv              |  29 +++++
 backend/plugins/zentao/impl/impl.go                |  37 ++++++-
 .../plugins/zentao/models/archived/changelog.go    |  67 +++++++++++
 .../plugins/zentao/models/archived/connection.go   |  13 ---
 backend/plugins/zentao/models/changelog.go         |  67 +++++++++++
 backend/plugins/zentao/models/connection.go        |   5 +
 .../20230519_add_init_changelog_tables.go          |  64 +++++++++++
 .../zentao/models/migrationscripts/register.go     |   1 +
 backend/plugins/zentao/models/product.go           |   2 +-
 backend/plugins/zentao/models/remote_db.go         |  96 ++++++++++++++++
 backend/plugins/zentao/tasks/changelog_dbget.go    | 122 +++++++++++++++++++++
 backend/plugins/zentao/tasks/task_data.go          |   4 +
 18 files changed, 650 insertions(+), 21 deletions(-)

diff --git a/backend/core/runner/db.go b/backend/core/runner/db.go
index bca6cc153..abbfe2277 100644
--- a/backend/core/runner/db.go
+++ b/backend/core/runner/db.go
@@ -19,6 +19,10 @@ package runner
 
 import (
        "fmt"
+       "net/url"
+       "strings"
+       "time"
+
        "github.com/apache/incubator-devlake/core/config"
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
@@ -27,9 +31,6 @@ import (
        "gorm.io/driver/postgres"
        "gorm.io/gorm"
        gormLogger "gorm.io/gorm/logger"
-       "net/url"
-       "strings"
-       "time"
 )
 
 // NewGormDb creates a new *gorm.DB and set it up properly
diff --git a/backend/plugins/zentao/api/blueprint_v200.go 
b/backend/plugins/zentao/api/blueprint_v200.go
index 6d0805247..0352fb1f3 100644
--- a/backend/plugins/zentao/api/blueprint_v200.go
+++ b/backend/plugins/zentao/api/blueprint_v200.go
@@ -19,6 +19,9 @@ package api
 
 import (
        "fmt"
+       "strings"
+       "time"
+
        "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/models/domainlayer"
@@ -30,8 +33,6 @@ import (
        "github.com/apache/incubator-devlake/plugins/zentao/models"
        "github.com/apache/incubator-devlake/plugins/zentao/tasks"
        "github.com/go-playground/validator/v10"
-       "strings"
-       "time"
 )
 
 func MakeDataSourcePipelinePlanV200(subtaskMetas []plugin.SubTaskMeta, 
connectionId uint64, bpScopes []*plugin.BlueprintScopeV200, syncPolicy 
*plugin.BlueprintSyncPolicy) (plugin.PipelinePlan, []plugin.Scope, 
errors.Error) {
diff --git a/backend/plugins/zentao/e2e/changelog_test.go 
b/backend/plugins/zentao/e2e/changelog_test.go
new file mode 100644
index 000000000..aed72b615
--- /dev/null
+++ b/backend/plugins/zentao/e2e/changelog_test.go
@@ -0,0 +1,95 @@
+/*
+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/config"
+       "github.com/apache/incubator-devlake/core/models/common"
+       "github.com/apache/incubator-devlake/core/runner"
+       "github.com/apache/incubator-devlake/helpers/e2ehelper"
+       "github.com/apache/incubator-devlake/impls/dalgorm"
+       "github.com/apache/incubator-devlake/plugins/zentao/impl"
+       "github.com/apache/incubator-devlake/plugins/zentao/models"
+       "github.com/apache/incubator-devlake/plugins/zentao/tasks"
+       "github.com/spf13/viper"
+)
+
+func TestZentaoDbGetDataFlow(t *testing.T) {
+
+       var zentao impl.Zentao
+       dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+       cfg := config.GetConfig()
+
+       taskData := &tasks.ZentaoTaskData{
+               Options: &tasks.ZentaoOptions{
+                       ConnectionId: 1,
+                       ProjectId:    0,
+                       ProductId:    1,
+               },
+       }
+
+       dataflowTester.ImportCsvIntoTabler("./raw_tables/zt_action.csv", 
models.ZentaoRemoteDbAction{})
+       dataflowTester.ImportCsvIntoTabler("./raw_tables/zt_history.csv", 
models.ZentaoRemoteDbHistory{})
+
+       v := viper.New()
+       v.Set("DB_URL", cfg.GetString(`E2E_DB_URL`))
+       v.Set("DB_LOGGING_LEVEL", cfg.GetString("DB_LOGGING_LEVEL"))
+       v.Set("DB_IDLE_CONNS", cfg.GetInt("DB_IDLE_CONNS"))
+       v.Set("DbMaxConns", cfg.GetInt("DB_MAX_CONNS"))
+
+       rgorm, err := runner.NewGormDb(v, dataflowTester.Log)
+       if err != nil {
+               return
+       }
+       taskData.RemoteDb = dalgorm.NewDalgorm(rgorm)
+
+       // verify conversion
+       dataflowTester.FlushTabler(&models.ZentaoChangelog{})
+       dataflowTester.FlushTabler(&models.ZentaoChangelogDetail{})
+       dataflowTester.Subtask(tasks.DBGetChangelogMeta, taskData)
+
+       dataflowTester.VerifyTable(
+               models.ZentaoChangelog{},
+               "./snapshot_tables/_tool_zentao_changelog.csv",
+               e2ehelper.ColumnWithRawData(
+                       "connection_id",
+                       "id",
+                       "object_id",
+                       "execution",
+                       "actor",
+                       "action",
+                       "extra",
+                       "object_type",
+                       "project",
+                       "product",
+                       "vision",
+                       "comment",
+                       "efforted",
+                       "date",
+               ),
+       )
+
+       dataflowTester.VerifyTableWithOptions(
+               &models.ZentaoChangelogDetail{},
+               e2ehelper.TableOptions{
+                       CSVRelPath:  
"./snapshot_tables/_tool_zentao_changelog_detail.csv",
+                       IgnoreTypes: []interface{}{common.NoPKModel{}},
+               })
+}
diff --git a/backend/plugins/zentao/e2e/raw_tables/zt_action.csv 
b/backend/plugins/zentao/e2e/raw_tables/zt_action.csv
new file mode 100644
index 000000000..de443fd57
--- /dev/null
+++ b/backend/plugins/zentao/e2e/raw_tables/zt_action.csv
@@ -0,0 +1,15 @@
+"id","objectType","objectID","product","project","execution","actor","action","date","comment","extra","read","vision","efforted"
+112,"execution",1,",1,",0,1,"admin","edited","2021-04-28 
11:06:58","","","0","rnd",0
+113,"execution",2,",1,",0,2,"admin","edited","2021-04-28 
11:08:02","","","0","rnd",0
+114,"bug",1,",1,",0,1,"admin","edited","2021-04-28 11:09:08","","","0","rnd",0
+115,"bug",2,",1,",0,1,"admin","edited","2021-04-28 11:09:08","","","0","rnd",0
+116,"bug",3,",1,",0,1,"admin","edited","2021-04-28 11:09:08","","","0","rnd",0
+117,"bug",4,",1,",0,1,"admin","edited","2021-04-28 11:09:08","","","0","rnd",0
+118,"testtask",1,",1,",0,1,"admin","edited","2021-04-28 
11:10:06","","","0","rnd",0
+119,"productplan",1,",1,",0,0,"admin","edited","2021-04-28 
11:11:10","","","0","rnd",0
+120,"execution",1,",1,",0,1,"admin","started","2021-04-28 
11:12:22","","","0","rnd",0
+165,"task",10,",1,",7,1,"dev1","finished","2021-04-28 
13:17:54","","","0","rnd",0
+166,"task",11,",1,",7,1,"dev1","started","2021-04-28 
13:18:45","","","0","rnd",0
+167,"user",4,",0,",0,0,"dev1","logout","2021-04-28 13:19:30","","","0","rnd",0
+168,"user",5,",0,",0,0,"dev2","login","2021-04-28 13:19:33","","","0","rnd",0
+169,"task",14,",1,",7,1,"dev2","started","2021-04-28 
13:19:54","","","0","rnd",0
\ No newline at end of file
diff --git a/backend/plugins/zentao/e2e/raw_tables/zt_history.csv 
b/backend/plugins/zentao/e2e/raw_tables/zt_history.csv
new file mode 100644
index 000000000..5a70ae859
--- /dev/null
+++ b/backend/plugins/zentao/e2e/raw_tables/zt_history.csv
@@ -0,0 +1,29 @@
+"id","action","field","old","new","diff"
+68,112,"begin","2012-06-05","2020-06-05",""
+69,112,"end","2012-12-04","2021-12-04",""
+70,112,"days","184","391",""
+71,112,"status","done","wait",""
+72,113,"begin","2013-06-05","2021-06-05",""
+73,113,"end","2014-06-04","2022-06-04",""
+74,113,"days","365","260",""
+75,114,"type","interface","codeerror",""
+76,114,"pri","0","1",""
+77,115,"pri","0","2",""
+78,116,"pri","0","1",""
+79,117,"pri","0","1",""
+80,118,"build","trunk","1",""
+81,118,"begin","2012-06-05","2020-06-05",""
+82,118,"end","2013-06-21","2021-06-21",""
+83,119,"begin","2000-01-01","2020-06-05",""
+84,119,"end","2015-01-01","2021-06-04",""
+85,120,"status","wait","doing",""
+91,165,"consumed","0","8",""
+92,165,"realStarted","0000-00-00 00:00:00","2021-04-28 13:17:54",""
+93,165,"finishedDate","","2021-04-01 16:00",""
+94,165,"status","wait","done",""
+95,165,"left","7","0",""
+96,165,"finishedBy","","dev1",""
+97,166,"realStarted","0000-00-00 00:00:00","2021-04-01 16:00",""
+98,166,"status","wait","doing",""
+99,169,"realStarted","0000-00-00 00:00:00","2021-04-01 08:00",""
+100,169,"status","wait","doing",""
\ No newline at end of file
diff --git 
a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog.csv
new file mode 100644
index 000000000..052473571
--- /dev/null
+++ b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog.csv
@@ -0,0 +1,13 @@
+connection_id,id,object_id,execution,actor,action,extra,object_type,project,product,vision,comment,efforted,date,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
+1,112,1,1,admin,112,,execution,0,1,rnd,,0,2021-04-28T11:06:58.000+00:00,,,0,
+1,113,2,2,admin,113,,execution,0,1,rnd,,0,2021-04-28T11:08:02.000+00:00,,,0,
+1,114,1,1,admin,114,,bug,0,1,rnd,,0,2021-04-28T11:09:08.000+00:00,,,0,
+1,115,2,1,admin,115,,bug,0,1,rnd,,0,2021-04-28T11:09:08.000+00:00,,,0,
+1,116,3,1,admin,116,,bug,0,1,rnd,,0,2021-04-28T11:09:08.000+00:00,,,0,
+1,117,4,1,admin,117,,bug,0,1,rnd,,0,2021-04-28T11:09:08.000+00:00,,,0,
+1,118,1,1,admin,118,,testtask,0,1,rnd,,0,2021-04-28T11:10:06.000+00:00,,,0,
+1,119,1,0,admin,119,,productplan,0,1,rnd,,0,2021-04-28T11:11:10.000+00:00,,,0,
+1,120,1,1,admin,120,,execution,0,1,rnd,,0,2021-04-28T11:12:22.000+00:00,,,0,
+1,165,10,1,dev1,165,,task,7,1,rnd,,0,2021-04-28T13:17:54.000+00:00,,,0,
+1,166,11,1,dev1,166,,task,7,1,rnd,,0,2021-04-28T13:18:45.000+00:00,,,0,
+1,169,14,1,dev2,169,,task,7,1,rnd,,0,2021-04-28T13:19:54.000+00:00,,,0,
diff --git 
a/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog_detail.csv 
b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog_detail.csv
new file mode 100644
index 000000000..88aee2bc1
--- /dev/null
+++ 
b/backend/plugins/zentao/e2e/snapshot_tables/_tool_zentao_changelog_detail.csv
@@ -0,0 +1,29 @@
+connection_id,id,changelog_id,field,old,new,diff
+1,68,68,begin,2012-06-05,2020-06-05,
+1,69,69,end,2012-12-04,2021-12-04,
+1,70,70,days,184,391,
+1,71,71,status,done,wait,
+1,72,72,begin,2013-06-05,2021-06-05,
+1,73,73,end,2014-06-04,2022-06-04,
+1,74,74,days,365,260,
+1,75,75,type,interface,codeerror,
+1,76,76,pri,0,1,
+1,77,77,pri,0,2,
+1,78,78,pri,0,1,
+1,79,79,pri,0,1,
+1,80,80,build,trunk,1,
+1,81,81,begin,2012-06-05,2020-06-05,
+1,82,82,end,2013-06-21,2021-06-21,
+1,83,83,begin,2000-01-01,2020-06-05,
+1,84,84,end,2015-01-01,2021-06-04,
+1,85,85,status,wait,doing,
+1,91,91,consumed,0,8,
+1,92,92,realStarted,0000-00-00 00:00:00,2021-04-28 13:17:54,
+1,93,93,finishedDate,,2021-04-01 16:00,
+1,94,94,status,wait,done,
+1,95,95,left,7,0,
+1,96,96,finishedBy,,dev1,
+1,97,97,realStarted,0000-00-00 00:00:00,2021-04-01 16:00,
+1,98,98,status,wait,doing,
+1,99,99,realStarted,0000-00-00 00:00:00,2021-04-01 08:00,
+1,100,100,status,wait,doing,
diff --git a/backend/plugins/zentao/impl/impl.go 
b/backend/plugins/zentao/impl/impl.go
index 244984330..56b8257e0 100644
--- a/backend/plugins/zentao/impl/impl.go
+++ b/backend/plugins/zentao/impl/impl.go
@@ -23,11 +23,14 @@ import (
        "github.com/apache/incubator-devlake/core/context"
        "github.com/apache/incubator-devlake/core/errors"
        "github.com/apache/incubator-devlake/core/plugin"
+       "github.com/apache/incubator-devlake/core/runner"
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
+       "github.com/apache/incubator-devlake/impls/dalgorm"
        "github.com/apache/incubator-devlake/plugins/zentao/api"
        "github.com/apache/incubator-devlake/plugins/zentao/models"
        
"github.com/apache/incubator-devlake/plugins/zentao/models/migrationscripts"
        "github.com/apache/incubator-devlake/plugins/zentao/tasks"
+       "github.com/spf13/viper"
 )
 
 // make sure interface is implemented
@@ -54,6 +57,7 @@ func (p Zentao) SubTaskMetas() []plugin.SubTaskMeta {
        return []plugin.SubTaskMeta{
                tasks.ConvertProductMeta,
                tasks.ConvertProjectMeta,
+               tasks.DBGetChangelogMeta,
                tasks.CollectExecutionMeta,
                tasks.ExtractExecutionMeta,
                tasks.ConvertExecutionMeta,
@@ -95,10 +99,39 @@ func (p Zentao) PrepareTaskData(taskCtx plugin.TaskContext, 
options map[string]i
                return nil, errors.Default.Wrap(err, "unable to get Zentao API 
client instance: %v")
        }
 
-       return &tasks.ZentaoTaskData{
+       data := &tasks.ZentaoTaskData{
                Options:   op,
                ApiClient: apiClient,
-       }, nil
+       }
+
+       if connection.DbUrl != "" {
+               if connection.DbLoggingLevel == "" {
+                       connection.DbLoggingLevel = 
taskCtx.GetConfig("DB_LOGGING_LEVEL")
+               }
+
+               if connection.DbIdleConns == 0 {
+                       connection.DbIdleConns = 
taskCtx.GetConfigReader().GetInt("DB_IDLE_CONNS")
+               }
+
+               if connection.DbMaxConns == 0 {
+                       connection.DbMaxConns = 
taskCtx.GetConfigReader().GetInt("DB_MAX_CONNS")
+               }
+
+               v := viper.New()
+               v.Set("DB_URL", connection.DbUrl)
+               v.Set("DB_LOGGING_LEVEL", connection.DbLoggingLevel)
+               v.Set("DB_IDLE_CONNS", connection.DbIdleConns)
+               v.Set("DbMaxConns", connection.DbMaxConns)
+
+               rgorm, err := runner.NewGormDb(v, taskCtx.GetLogger())
+               if err != nil {
+                       return nil, errors.Default.Wrap(err, 
fmt.Sprintf("failed to connect to the zentao remote databases %s", 
connection.DbUrl))
+               }
+
+               data.RemoteDb = dalgorm.NewDalgorm(rgorm)
+       }
+
+       return data, nil
 }
 
 // PkgPath information lost when compiled as plugin(.so)
diff --git a/backend/plugins/zentao/models/archived/changelog.go 
b/backend/plugins/zentao/models/archived/changelog.go
new file mode 100644
index 000000000..249f1b15f
--- /dev/null
+++ b/backend/plugins/zentao/models/archived/changelog.go
@@ -0,0 +1,67 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package archived
+
+import (
+       "time"
+
+       
"github.com/apache/incubator-devlake/core/models/migrationscripts/archived"
+)
+
+type ZentaoChangelog struct {
+       archived.NoPKModel `json:"-"`
+       ConnectionId       uint64    `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Id                 int64     `json:"id" mapstructure:"id" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       ObjectId           int       `json:"objectId" mapstructure:"objectId" 
gorm:"index; NOT NULL"`
+       Execution          int       `json:"execution" mapstructure:"execution" 
`
+       Actor              string    `json:"actor" mapstructure:"actor" `
+       Action             string    `json:"action" mapstructure:"action"`
+       Extra              string    `json:"extra" mapstructure:"extra"`
+       ObjectType         string    `json:"objectType" 
mapstructure:"objectType"`
+       Project            int       `json:"project" mapstructure:"project"`
+       Product            string    `json:"product" mapstructure:"product"`
+       Vision             string    `json:"vision" mapstructure:"vision"`
+       Comment            string    `json:"comment" mapstructure:"comment"`
+       Efforted           string    `json:"efforted" mapstructure:"efforted"`
+       Date               time.Time `json:"date" mapstructure:"date"`
+       Read               string    `json:"read" mapstructure:"read"`
+}
+
+func (ZentaoChangelog) TableName() string {
+       return "_tool_zentao_changelog"
+}
+
+type ZentaoChangelogDetail struct {
+       archived.NoPKModel `json:"-"`
+       ConnectionId       uint64 `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Id                 int64  `json:"id" mapstructure:"id" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       ChangelogId        int64  `json:"changelogId" 
mapstructure:"changelogId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Field              string `json:"field" mapstructure:"field"`
+       Old                string `json:"old" mapstructure:"old"`
+       New                string `json:"new" mapstructure:"new"`
+       Diff               string `json:"diff" mapstructure:"diff"`
+}
+
+func (ZentaoChangelogDetail) TableName() string {
+       return "_tool_zentao_changelog_detail"
+}
+
+type ZentaoChangelogCom struct {
+       Changelog       *ZentaoChangelog
+       ChangelogDetail *ZentaoChangelogDetail
+}
diff --git a/backend/plugins/zentao/models/archived/connection.go 
b/backend/plugins/zentao/models/archived/connection.go
index 3b6caa485..dab806d98 100644
--- a/backend/plugins/zentao/models/archived/connection.go
+++ b/backend/plugins/zentao/models/archived/connection.go
@@ -33,19 +33,6 @@ type TestConnectionRequest struct {
        BasicAuth `mapstructure:",squash"`
 }
 
-// This object conforms to what the frontend currently expects.
-type ZentaoResponse struct {
-       Name string `json:"name"`
-       ID   int64  `json:"id"`
-       ZentaoConnection
-}
-
-// Using User because it requires authentication.
-type ApiUserResponse struct {
-       Id   int64
-       Name string `json:"name"`
-}
-
 func (ZentaoConnection) TableName() string {
        return "_tool_zentao_connections"
 }
diff --git a/backend/plugins/zentao/models/changelog.go 
b/backend/plugins/zentao/models/changelog.go
new file mode 100644
index 000000000..152b0bdff
--- /dev/null
+++ b/backend/plugins/zentao/models/changelog.go
@@ -0,0 +1,67 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+       "time"
+
+       "github.com/apache/incubator-devlake/core/models/common"
+)
+
+type ZentaoChangelog struct {
+       common.NoPKModel `json:"-"`
+       ConnectionId     uint64    `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Id               int64     `json:"id" mapstructure:"id" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       ObjectId         int       `json:"objectId" mapstructure:"objectId" 
gorm:"index; NOT NULL"`
+       Execution        int       `json:"execution" mapstructure:"execution" `
+       Actor            string    `json:"actor" mapstructure:"actor" `
+       Action           string    `json:"action" mapstructure:"action"`
+       Extra            string    `json:"extra" mapstructure:"extra"`
+       ObjectType       string    `json:"objectType" mapstructure:"objectType"`
+       Project          int       `json:"project" mapstructure:"project"`
+       Product          int       `json:"product" mapstructure:"product"`
+       Vision           string    `json:"vision" mapstructure:"vision"`
+       Comment          string    `json:"comment" mapstructure:"comment"`
+       Efforted         string    `json:"efforted" mapstructure:"efforted"`
+       Date             time.Time `json:"date" mapstructure:"date"`
+       Read             string    `json:"read" mapstructure:"read"`
+}
+
+func (ZentaoChangelog) TableName() string {
+       return "_tool_zentao_changelog"
+}
+
+type ZentaoChangelogDetail struct {
+       common.NoPKModel `json:"-"`
+       ConnectionId     uint64 `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Id               int64  `json:"id" mapstructure:"id" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       ChangelogId      int64  `json:"changelogId" mapstructure:"changelogId" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       Field            string `json:"field" mapstructure:"field"`
+       Old              string `json:"old" mapstructure:"old"`
+       New              string `json:"new" mapstructure:"new"`
+       Diff             string `json:"diff" mapstructure:"diff"`
+}
+
+func (ZentaoChangelogDetail) TableName() string {
+       return "_tool_zentao_changelog_detail"
+}
+
+type ZentaoChangelogCom struct {
+       Changelog       *ZentaoChangelog
+       ChangelogDetail *ZentaoChangelogDetail
+}
diff --git a/backend/plugins/zentao/models/connection.go 
b/backend/plugins/zentao/models/connection.go
index 23a1b58c9..cd39e6fbd 100644
--- a/backend/plugins/zentao/models/connection.go
+++ b/backend/plugins/zentao/models/connection.go
@@ -60,6 +60,11 @@ func (connection ZentaoConn) PrepareApiClient(apiClient 
apihelperabstract.ApiCli
 type ZentaoConn struct {
        helper.RestConnection `mapstructure:",squash"`
        helper.BasicAuth      `mapstructure:",squash"`
+
+       DbUrl          string `mapstructure:"dbUrl"  json:"dbUrl" 
gorm:"serializer:encdec"`
+       DbIdleConns    int    `json:"dbIdleConns" mapstructure:"dbIdleConns"`
+       DbLoggingLevel string `json:"dbLoggingLevel" 
mapstructure:"dbLoggingLevel"`
+       DbMaxConns     int    `json:"dbMaxConns" mapstructure:"dbMaxConns"`
 }
 
 // ZentaoConnection holds ZentaoConn plus ID/Name for database storage
diff --git 
a/backend/plugins/zentao/models/migrationscripts/20230519_add_init_changelog_tables.go
 
b/backend/plugins/zentao/models/migrationscripts/20230519_add_init_changelog_tables.go
new file mode 100644
index 000000000..ec8c676c6
--- /dev/null
+++ 
b/backend/plugins/zentao/models/migrationscripts/20230519_add_init_changelog_tables.go
@@ -0,0 +1,64 @@
+/*
+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/context"
+       "github.com/apache/incubator-devlake/core/errors"
+       "github.com/apache/incubator-devlake/helpers/migrationhelper"
+       "github.com/apache/incubator-devlake/plugins/zentao/models/archived"
+)
+
+type addInitChangelogTables struct{}
+
+// This object conforms to what the frontend currently sends.
+type ZentaoConnection20230522 struct {
+       DbUrl          string `mapstructure:"dbUrl"  json:"dbUrl" 
gorm:"serializer:encdec"`
+       DbIdleConns    int    `json:"dbIdleConns" mapstructure:"dbIdleConns"`
+       DbLoggingLevel string `json:"dbLoggingLevel" 
mapstructure:"dbLoggingLevel"`
+       DbMaxConns     int    `json:"dbMaxConns" mapstructure:"dbMaxConns"`
+}
+
+func (ZentaoConnection20230522) TableName() string {
+       return "_tool_zentao_connections"
+}
+
+func (*addInitChangelogTables) Up(basicRes context.BasicRes) errors.Error {
+       db := basicRes.GetDal()
+       err := db.DropTables(
+               &archived.ZentaoChangelog{},
+               &archived.ZentaoChangelogDetail{},
+       )
+       if err != nil {
+               return err
+       }
+       return migrationhelper.AutoMigrateTables(
+               basicRes,
+               &archived.ZentaoChangelog{},
+               &archived.ZentaoChangelogDetail{},
+               &ZentaoConnection20230522{},
+       )
+}
+
+func (*addInitChangelogTables) Version() uint64 {
+       return 20230525000001
+}
+
+func (*addInitChangelogTables) Name() string {
+       return "zentao init changelog schemas"
+}
diff --git a/backend/plugins/zentao/models/migrationscripts/register.go 
b/backend/plugins/zentao/models/migrationscripts/register.go
index ec054748c..e184d69bc 100644
--- a/backend/plugins/zentao/models/migrationscripts/register.go
+++ b/backend/plugins/zentao/models/migrationscripts/register.go
@@ -25,5 +25,6 @@ import (
 func All() []plugin.MigrationScript {
        return []plugin.MigrationScript{
                new(addInitTables),
+               new(addInitChangelogTables),
        }
 }
diff --git a/backend/plugins/zentao/models/product.go 
b/backend/plugins/zentao/models/product.go
index c67fdc39e..521286294 100644
--- a/backend/plugins/zentao/models/product.go
+++ b/backend/plugins/zentao/models/product.go
@@ -114,7 +114,7 @@ func (res ZentaoProductRes) ConvertApiScope() 
plugin.ToolLayerScope {
 
 type ZentaoProduct struct {
        common.NoPKModel `json:"-"`
-       ConnectionId     uint64 `json:"connectionid" 
mapstructure:"connectionid" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+       ConnectionId     uint64 `json:"connectionId" 
mapstructure:"connectionId" gorm:"primaryKey;type:BIGINT  NOT NULL"`
        Id               int64  `json:"id" mapstructure:"id" 
gorm:"primaryKey;type:BIGINT  NOT NULL"`
        Program          int    `json:"program" mapstructure:"program"`
        Name             string `json:"name" mapstructure:"name"`
diff --git a/backend/plugins/zentao/models/remote_db.go 
b/backend/plugins/zentao/models/remote_db.go
new file mode 100644
index 000000000..bb5439458
--- /dev/null
+++ b/backend/plugins/zentao/models/remote_db.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 models
+
+import (
+       "time"
+)
+
+type ZentaoRemoteDbHistoryBase struct {
+       Field string `gorm:"column:field"`
+       Old   string `gorm:"column:old"`
+       New   string `gorm:"column:new"`
+       Diff  string `gorm:"column:diff"`
+}
+
+type ZentaoRemoteDbHistory struct {
+       Id     int `gorm:"column:id"`
+       Action int `gorm:"column:action"`
+       ZentaoRemoteDbHistoryBase
+}
+
+func (ZentaoRemoteDbHistory) TableName() string {
+       return "zt_history"
+}
+
+type ZentaoRemoteDbAction struct {
+       Id         int       `gorm:"column:id"`
+       ObjectType string    `gorm:"column:objectType"`
+       ObjectId   int       `gorm:"column:objectID"`
+       Product    string    `gorm:"column:product"`
+       Project    int       `gorm:"column:project"`
+       Execution  int       `gorm:"column:execution"`
+       Actor      string    `gorm:"column:actor"`
+       Action     string    `gorm:"column:action"`
+       Date       time.Time `gorm:"column:date"`
+       Comment    string    `gorm:"column:comment"`
+       Extra      string    `gorm:"column:extra"`
+       Read       string    `gorm:"column:read"`
+       Vision     string    `gorm:"column:vision"`
+       Efforted   string    `gorm:"column:efforted"`
+}
+
+func (ZentaoRemoteDbAction) TableName() string {
+       return "zt_action"
+}
+
+type ZentaoRemoteDbActionHistory struct {
+       ZentaoRemoteDbAction
+       ZentaoRemoteDbHistoryBase
+
+       ActionId  int `gorm:"column:aid"`
+       HistoryId int `gorm:"column:hid"`
+}
+
+func (ah *ZentaoRemoteDbActionHistory) Convert() *ZentaoChangelogCom {
+       return &ZentaoChangelogCom{
+               &ZentaoChangelog{
+                       Id:         int64(ah.ActionId),
+                       ObjectId:   ah.ObjectId,
+                       Execution:  ah.Execution,
+                       Actor:      ah.Actor,
+                       Action:     ah.Action,
+                       Extra:      ah.Extra,
+                       ObjectType: ah.ObjectType,
+                       Project:    ah.Project,
+                       Vision:     ah.Vision,
+                       Comment:    ah.Comment,
+                       Efforted:   ah.Efforted,
+                       Date:       ah.Date,
+                       Read:       ah.Read,
+               },
+               &ZentaoChangelogDetail{
+                       Id:          int64(ah.HistoryId),
+                       ChangelogId: int64(ah.Id),
+                       Field:       ah.Field,
+                       Old:         ah.Old,
+                       New:         ah.New,
+                       Diff:        ah.Diff,
+               },
+       }
+}
diff --git a/backend/plugins/zentao/tasks/changelog_dbget.go 
b/backend/plugins/zentao/tasks/changelog_dbget.go
new file mode 100644
index 000000000..64d15630f
--- /dev/null
+++ b/backend/plugins/zentao/tasks/changelog_dbget.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 tasks
+
+import (
+       "fmt"
+       "reflect"
+
+       "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"
+       "github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ plugin.SubTaskEntryPoint = DBGetActionHistory
+
+func DBGetActionHistory(taskCtx plugin.SubTaskContext) errors.Error {
+       data := taskCtx.GetData().(*ZentaoTaskData)
+
+       // skip if no RemoteDb
+       if data.RemoteDb == nil {
+               return nil
+       }
+
+       divider := api.NewBatchSaveDivider(taskCtx, 500, "", "")
+       defer func() {
+               err1 := divider.Close()
+               if err1 != nil {
+                       panic(err1)
+               }
+       }()
+
+       return dBGetActionHistory(data, func(zcc *models.ZentaoChangelogCom) 
errors.Error {
+               batch, err := divider.ForType(reflect.TypeOf(zcc.Changelog))
+               if err != nil {
+                       return err
+               }
+               zcc.Changelog.ConnectionId = data.Options.ConnectionId
+               zcc.Changelog.Product = int(data.Options.ProductId)
+               err = batch.Add(zcc.Changelog)
+               if err != nil {
+                       return err
+               }
+               if zcc.ChangelogDetail.Id != 0 {
+                       batch, err = 
divider.ForType(reflect.TypeOf(zcc.ChangelogDetail))
+                       if err != nil {
+                               return err
+                       }
+                       zcc.ChangelogDetail.ConnectionId = 
data.Options.ConnectionId
+                       err = batch.Add(zcc.ChangelogDetail)
+                       if err != nil {
+                               return err
+                       }
+               }
+               return nil
+       })
+}
+
+var DBGetChangelogMeta = plugin.SubTaskMeta{
+       Name:             "DBGetChangelog",
+       EntryPoint:       DBGetActionHistory,
+       EnabledByDefault: true,
+       Description:      "get action and history data to be changelog from 
Zentao databases",
+       DomainTypes:      []string{plugin.DOMAIN_TYPE_TICKET},
+}
+
+// it is work for zentao version 18.3
+func dBGetActionHistory(data *ZentaoTaskData, callback 
func(*models.ZentaoChangelogCom) errors.Error) errors.Error {
+       rdb := data.RemoteDb
+       atn := (models.ZentaoRemoteDbAction{}).TableName()
+       htn := (models.ZentaoRemoteDbHistory{}).TableName()
+
+       clause := []dal.Clause{
+               dal.Select(fmt.Sprintf("*,%s.id aid,%s.id hid ", atn, htn)),
+               dal.From(atn),
+       }
+
+       if data.Options.ProductId != 0 {
+               clause = append(clause, dal.Where(fmt.Sprintf("%s.product = ?", 
atn), fmt.Sprintf(",%d,", data.Options.ProductId)))
+       }
+       if data.Options.ProjectId != 0 {
+               clause = append(clause, dal.Where(fmt.Sprintf("%s.project = ?", 
atn), data.Options.ProjectId))
+       }
+       clause = append(clause, dal.Join(fmt.Sprintf("LEFT JOIN %s on %s.action 
= %s.id", htn, htn, atn)))
+
+       cursor, err := rdb.Cursor(clause...)
+       if err != nil {
+               return err
+       }
+       defer cursor.Close()
+
+       for cursor.Next() {
+               actionHistory := &models.ZentaoRemoteDbActionHistory{}
+               err = rdb.Fetch(cursor, actionHistory)
+               if err != nil {
+                       return err
+               }
+
+               err = callback(actionHistory.Convert())
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
diff --git a/backend/plugins/zentao/tasks/task_data.go 
b/backend/plugins/zentao/tasks/task_data.go
index 83d47e7fe..406276f2e 100644
--- a/backend/plugins/zentao/tasks/task_data.go
+++ b/backend/plugins/zentao/tasks/task_data.go
@@ -19,6 +19,8 @@ package tasks
 
 import (
        "fmt"
+
+       "github.com/apache/incubator-devlake/core/dal"
        "github.com/apache/incubator-devlake/core/errors"
        helper "github.com/apache/incubator-devlake/helpers/pluginhelper/api"
        "github.com/mitchellh/mapstructure"
@@ -41,10 +43,12 @@ type ZentaoOptions struct {
        TimeAfter string `json:"timeAfter" mapstructure:"timeAfter,omitempty"`
        //TransformationRuleId                uint64 
`json:"transformationZentaoeId" mapstructure:"transformationRuleId,omitempty"`
        //*models.ZentaoTransformationRule 
`mapstructure:"transformationRules,omitempty" json:"transformationRules"`
+
 }
 
 type ZentaoTaskData struct {
        Options   *ZentaoOptions
+       RemoteDb  dal.Dal
        ApiClient *helper.ApiAsyncClient
 }
 


Reply via email to