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 089e8367 fix: jenkins collect folder (#2480)
089e8367 is described below

commit 089e836752daf395b5419991295d3e9dc953a8c5
Author: mappjzc <[email protected]>
AuthorDate: Wed Jul 13 22:04:03 2022 +0800

    fix: jenkins collect folder (#2480)
    
    * fix: jenkins collect folder
    
    Add queue.go
    Add list.go
    Add QueueIterator
    Add some logic fix
    
    Nddtfjiang <[email protected]>
    
    * fix: fix for 2470
    
    fix #2470
    move apiClient.Release() to plugin close.
    
    Nddtfjiang <[email protected]>
    
    * fix: fix jenkins e2e test
    
    update e2e test table csv.
    add path too jobs_test
    
    Nddtfjiang <[email protected]>
    
    * refactor: changed list and queue to helper temporary
    
    move list.go and queue.go to helper temporary
    
    Nddtfjiang <[email protected]>
---
 plugins/ae/impl/impl.go                            |  10 ++
 plugins/feishu/impl/impl.go                        |  12 ++
 plugins/gitee/impl/impl.go                         |  10 ++
 plugins/github/impl/impl.go                        |  11 ++
 plugins/gitlab/impl/impl.go                        |  10 ++
 plugins/helper/api_collector.go                    |  11 +-
 plugins/helper/api_collector_test.go               |   2 +-
 plugins/helper/iterator.go                         |  43 +++++++
 plugins/{jenkins/jenkins.go => helper/list.go}     |  42 ++++---
 plugins/helper/queue.go                            | 127 +++++++++++++++++++++
 plugins/icla/plugin_main.go                        |  12 ++
 plugins/jenkins/e2e/jobs_test.go                   |   4 +-
 .../e2e/raw_tables/_raw_jenkins_api_builds.csv     |  30 ++---
 .../e2e/raw_tables/_raw_jenkins_api_jobs.csv       |  21 ++--
 .../e2e/snapshot_tables/_tool_jenkins_builds.csv   |  30 ++---
 .../e2e/snapshot_tables/_tool_jenkins_jobs.csv     |  21 ++--
 plugins/jenkins/e2e/snapshot_tables/builds.csv     |  28 ++---
 plugins/jenkins/e2e/snapshot_tables/jobs.csv       |  19 ++-
 plugins/jenkins/impl/impl.go                       |  10 ++
 plugins/jenkins/jenkins.go                         |   2 +-
 plugins/jenkins/models/job.go                      |  18 ++-
 .../models/migrationscripts/archived/job.go        |   1 +
 .../jenkins/models/migrationscripts/init_schema.go |   2 +-
 plugins/jenkins/models/response.go                 |   1 +
 plugins/jenkins/tasks/build_collector.go           |   5 +-
 plugins/jenkins/tasks/job_collector.go             |  32 +++++-
 plugins/jenkins/tasks/job_extractor.go             |   8 ++
 plugins/jira/impl/impl.go                          |  10 ++
 plugins/tapd/impl/impl.go                          |  10 ++
 29 files changed, 416 insertions(+), 126 deletions(-)

diff --git a/plugins/ae/impl/impl.go b/plugins/ae/impl/impl.go
index e8f9c217..cfc747a4 100644
--- a/plugins/ae/impl/impl.go
+++ b/plugins/ae/impl/impl.go
@@ -37,6 +37,7 @@ var _ core.PluginInit = (*AE)(nil)
 var _ core.PluginTask = (*AE)(nil)
 var _ core.PluginApi = (*AE)(nil)
 var _ core.Migratable = (*AE)(nil)
+var _ core.CloseablePluginTask = (*AE)(nil)
 
 type AE struct{}
 
@@ -118,3 +119,12 @@ func (plugin AE) ApiResources() 
map[string]map[string]core.ApiResourceHandler {
                },
        }
 }
+
+func (plugin AE) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.AeTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/feishu/impl/impl.go b/plugins/feishu/impl/impl.go
index cc4a4e03..be72a418 100644
--- a/plugins/feishu/impl/impl.go
+++ b/plugins/feishu/impl/impl.go
@@ -18,6 +18,8 @@ limitations under the License.
 package impl
 
 import (
+       "fmt"
+
        "github.com/mitchellh/mapstructure"
        "github.com/spf13/viper"
        "gorm.io/gorm"
@@ -36,6 +38,7 @@ var _ core.PluginInit = (*Feishu)(nil)
 var _ core.PluginTask = (*Feishu)(nil)
 var _ core.PluginApi = (*Feishu)(nil)
 var _ core.Migratable = (*Feishu)(nil)
+var _ core.CloseablePluginTask = (*Feishu)(nil)
 
 type Feishu struct{}
 
@@ -122,3 +125,12 @@ func (plugin Feishu) MigrationScripts() []migration.Script 
{
 func (plugin Feishu) ApiResources() 
map[string]map[string]core.ApiResourceHandler {
        return map[string]map[string]core.ApiResourceHandler{}
 }
+
+func (plugin Feishu) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.FeishuTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/gitee/impl/impl.go b/plugins/gitee/impl/impl.go
index 90a1eb53..44f8662a 100644
--- a/plugins/gitee/impl/impl.go
+++ b/plugins/gitee/impl/impl.go
@@ -37,6 +37,7 @@ var _ core.PluginInit = (*Gitee)(nil)
 var _ core.PluginTask = (*Gitee)(nil)
 var _ core.PluginApi = (*Gitee)(nil)
 var _ core.Migratable = (*Gitee)(nil)
+var _ core.CloseablePluginTask = (*Gitee)(nil)
 
 type Gitee string
 
@@ -186,3 +187,12 @@ func (plugin Gitee) ApiResources() 
map[string]map[string]core.ApiResourceHandler
                },
        }
 }
+
+func (plugin Gitee) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.GiteeTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/github/impl/impl.go b/plugins/github/impl/impl.go
index 6bba76c1..6171a983 100644
--- a/plugins/github/impl/impl.go
+++ b/plugins/github/impl/impl.go
@@ -19,6 +19,7 @@ package impl
 
 import (
        "fmt"
+
        "github.com/apache/incubator-devlake/migration"
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/github/api"
@@ -36,6 +37,7 @@ var _ core.PluginTask = (*Github)(nil)
 var _ core.PluginApi = (*Github)(nil)
 var _ core.Migratable = (*Github)(nil)
 var _ core.PluginBlueprintV100 = (*Github)(nil)
+var _ core.CloseablePluginTask = (*Github)(nil)
 
 type Github struct{}
 
@@ -140,3 +142,12 @@ func (plugin Github) ApiResources() 
map[string]map[string]core.ApiResourceHandle
 func (plugin Github) MakePipelinePlan(connectionId uint64, scope 
[]*core.BlueprintScopeV100) (core.PipelinePlan, error) {
        return api.MakePipelinePlan(plugin.SubTaskMetas(), connectionId, scope)
 }
+
+func (plugin Github) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.GithubTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/gitlab/impl/impl.go b/plugins/gitlab/impl/impl.go
index 1550131d..b6bf0113 100644
--- a/plugins/gitlab/impl/impl.go
+++ b/plugins/gitlab/impl/impl.go
@@ -37,6 +37,7 @@ var _ core.PluginTask = (*Gitlab)(nil)
 var _ core.PluginApi = (*Gitlab)(nil)
 var _ core.Migratable = (*Gitlab)(nil)
 var _ core.PluginBlueprintV100 = (*Gitlab)(nil)
+var _ core.CloseablePluginTask = (*Gitlab)(nil)
 
 type Gitlab string
 
@@ -143,3 +144,12 @@ func (plugin Gitlab) ApiResources() 
map[string]map[string]core.ApiResourceHandle
                },
        }
 }
+
+func (plugin Gitlab) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.GitlabTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/helper/api_collector.go b/plugins/helper/api_collector.go
index 3e08b859..a9b7390d 100644
--- a/plugins/helper/api_collector.go
+++ b/plugins/helper/api_collector.go
@@ -149,14 +149,21 @@ func (collector *ApiCollector) Execute() error {
 
        collector.args.Ctx.SetProgress(0, -1)
        if collector.args.Input != nil {
+
                iterator := collector.args.Input
                defer iterator.Close()
                apiClient := collector.args.ApiClient
                if apiClient == nil {
                        return fmt.Errorf("api_collector can not Execute with 
nil apiClient")
                }
-               defer apiClient.Release()
-               for iterator.HasNext() && !apiClient.HasError() {
+
+               for {
+                       if !iterator.HasNext() || apiClient.HasError() {
+                               collector.args.ApiClient.WaitAsync()
+                               if !iterator.HasNext() || apiClient.HasError() {
+                                       break
+                               }
+                       }
                        input, err := iterator.Fetch()
                        if err != nil {
                                break
diff --git a/plugins/helper/api_collector_test.go 
b/plugins/helper/api_collector_test.go
index be829d0a..ee9cb961 100644
--- a/plugins/helper/api_collector_test.go
+++ b/plugins/helper/api_collector_test.go
@@ -41,7 +41,7 @@ func TestFetchPageUndetermined(t *testing.T) {
 
        mockInput := new(mocks.Iterator)
        mockInput.On("HasNext").Return(true).Once()
-       mockInput.On("HasNext").Return(false).Once()
+       mockInput.On("HasNext").Return(false).Twice()
        mockInput.On("Fetch").Return(nil, nil).Once()
        mockInput.On("Close").Return(nil)
 
diff --git a/plugins/helper/iterator.go b/plugins/helper/iterator.go
index 98c9be6a..7c8953b2 100644
--- a/plugins/helper/iterator.go
+++ b/plugins/helper/iterator.go
@@ -113,3 +113,46 @@ func NewDateIterator(days int) (*DateIterator, error) {
                Current:   0,
        }, nil
 }
+
+type QueueIteratorNode struct {
+       data interface{}
+       next *QueueIteratorNode
+}
+
+func (q *QueueIteratorNode) Next() interface{} {
+       if q.next == nil {
+               return nil
+       }
+       return q.next
+}
+
+func (q *QueueIteratorNode) SetNext(next interface{}) {
+       q.next, _ = next.(*QueueIteratorNode)
+}
+
+type QueueIterator struct {
+       queue *Queue
+}
+
+func (q *QueueIterator) HasNext() bool {
+       return q.queue.GetCount() > 0
+}
+
+func (q *QueueIterator) Fetch() (interface{}, error) {
+       return q.queue.PullWithOutLock(), nil
+}
+
+func (q *QueueIterator) Push(data QueueNode) {
+       q.queue.PushWitouLock(data)
+}
+
+func (q *QueueIterator) Close() error {
+       q.queue.CleanWithOutLock()
+       return nil
+}
+
+func NewQueueIterator() *QueueIterator {
+       return &QueueIterator{
+               queue: NewQueue(),
+       }
+}
diff --git a/plugins/jenkins/jenkins.go b/plugins/helper/list.go
similarity index 59%
copy from plugins/jenkins/jenkins.go
copy to plugins/helper/list.go
index fcbd14b2..b3a1381b 100644
--- a/plugins/jenkins/jenkins.go
+++ b/plugins/helper/list.go
@@ -15,23 +15,29 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 */
 
-package main
-
-import (
-       "github.com/apache/incubator-devlake/plugins/jenkins/impl"
-       "github.com/apache/incubator-devlake/runner"
-       "github.com/spf13/cobra"
-)
-
-var PluginEntry impl.Jenkins
-
-func main() {
-       jenkinsCmd := &cobra.Command{Use: "jenkins"}
-       connectionId := jenkinsCmd.Flags().Uint64P("connection", "c", 0, 
"jenkins connection id")
-       jenkinsCmd.Run = func(cmd *cobra.Command, args []string) {
-               runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
-                       "connectionId": *connectionId,
-               })
+package helper
+
+type ListBaseNode struct {
+       next interface{}
+}
+
+func (l *ListBaseNode) Next() interface{} {
+       if l.next == nil {
+               return nil
        }
-       runner.RunCmd(jenkinsCmd)
+       return l.next
+}
+
+func (l *ListBaseNode) SetNext(next interface{}) {
+       l.next = next
 }
+
+// NewListBaseNode create and init a new node
+func NewListBaseNode() *ListBaseNode {
+       return &ListBaseNode{
+               next: nil,
+       }
+}
+
+// check if is all right for interface QueueNode
+var _ QueueNode = (*ListBaseNode)(nil)
diff --git a/plugins/helper/queue.go b/plugins/helper/queue.go
new file mode 100644
index 00000000..b1e74783
--- /dev/null
+++ b/plugins/helper/queue.go
@@ -0,0 +1,127 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package helper
+
+import (
+       "sync"
+       "sync/atomic"
+)
+
+type QueueNode interface {
+       Next() interface{}
+       SetNext(next interface{})
+}
+
+type Queue struct {
+       count int64
+       head  QueueNode
+       tail  QueueNode
+       mux   sync.Mutex
+}
+
+// Push add a node to queue
+func (q *Queue) Push(node QueueNode) {
+       q.mux.Lock()
+       defer q.mux.Unlock()
+       q.PushWitouLock(node)
+}
+
+// Pull get a node from queue
+func (q *Queue) Pull(add *int64) QueueNode {
+       q.mux.Lock()
+       defer q.mux.Unlock()
+
+       node := q.PullWithOutLock()
+
+       if node == nil {
+               return nil
+       }
+       if add != nil {
+               atomic.AddInt64(add, 1)
+       }
+       return node
+}
+
+// PushWitouLock is no lock mode of Push
+func (q *Queue) PushWitouLock(node QueueNode) {
+       if q.tail == nil {
+               q.head = node
+               q.tail = node
+               q.count = 1
+       } else {
+               q.tail.SetNext(node)
+               q.tail = node
+               q.count++
+       }
+}
+
+// PullWitouLock is no lock mode of Pull
+func (q *Queue) PullWithOutLock() QueueNode {
+       var node QueueNode = nil
+
+       if q.head != nil {
+               node = q.head
+               q.head, _ = node.Next().(QueueNode)
+
+               if q.head == nil {
+                       q.tail = nil
+               }
+
+               node.SetNext(nil)
+               q.count--
+       } else {
+               q.count = 0
+       }
+       return node
+}
+
+// CleanWithOutLock is no lock mode of Clean
+func (q *Queue) CleanWithOutLock() {
+       q.count = 0
+       q.head = nil
+       q.tail = nil
+}
+
+// Clean remove all node on queue
+func (q *Queue) Clean() {
+       q.mux.Lock()
+       defer q.mux.Unlock()
+       q.CleanWithOutLock()
+}
+
+// GetCountWithOutLock is no lock mode of GetCount
+func (q *Queue) GetCountWithOutLock() int64 {
+       return q.count
+}
+
+// GetCount get the node count
+func (q *Queue) GetCount() int64 {
+       q.mux.Lock()
+       defer q.mux.Unlock()
+       return q.count
+}
+
+// NewQueue create and init a new Queue
+func NewQueue() *Queue {
+       return &Queue{
+               count: int64(0),
+               head:  nil,
+               tail:  nil,
+               mux:   sync.Mutex{},
+       }
+}
diff --git a/plugins/icla/plugin_main.go b/plugins/icla/plugin_main.go
index e72f6bfb..57972adf 100644
--- a/plugins/icla/plugin_main.go
+++ b/plugins/icla/plugin_main.go
@@ -18,6 +18,8 @@ limitations under the License.
 package main
 
 import (
+       "fmt"
+
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/icla/models"
        "github.com/apache/incubator-devlake/plugins/icla/tasks"
@@ -33,6 +35,7 @@ var _ core.PluginMeta = (*Icla)(nil)
 var _ core.PluginInit = (*Icla)(nil)
 var _ core.PluginTask = (*Icla)(nil)
 var _ core.PluginApi = (*Icla)(nil)
+var _ core.CloseablePluginTask = (*Icla)(nil)
 
 // PluginEntry is a variable exported for Framework to search and load
 var PluginEntry Icla //nolint
@@ -86,6 +89,15 @@ func (plugin Icla) ApiResources() 
map[string]map[string]core.ApiResourceHandler
        return nil
 }
 
+func (plugin Icla) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.IclaTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
+
 // standalone mode for debugging
 func main() {
        cmd := &cobra.Command{Use: "icla"}
diff --git a/plugins/jenkins/e2e/jobs_test.go b/plugins/jenkins/e2e/jobs_test.go
index a8d1fe79..da0a9b8c 100644
--- a/plugins/jenkins/e2e/jobs_test.go
+++ b/plugins/jenkins/e2e/jobs_test.go
@@ -19,9 +19,10 @@ package e2e
 
 import (
        "fmt"
-       "github.com/apache/incubator-devlake/models/domainlayer/devops"
        "testing"
 
+       "github.com/apache/incubator-devlake/models/domainlayer/devops"
+
        "github.com/apache/incubator-devlake/helpers/e2ehelper"
        "github.com/apache/incubator-devlake/plugins/jenkins/impl"
        "github.com/apache/incubator-devlake/plugins/jenkins/models"
@@ -51,6 +52,7 @@ func TestJenkinsJobsDataFlow(t *testing.T) {
                []string{
                        "connection_id",
                        "name",
+                       "path",
                        "class",
                        "color",
                        "base",
diff --git a/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_builds.csv 
b/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_builds.csv
index f5399bd1..250c8ce3 100644
--- a/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_builds.csv
+++ b/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_builds.csv
@@ -1,21 +1,9 @@
-"id","params","data","url","input","created_at"
-"1","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#205"",""duration"":843,""estimatedDuration"":845,""number"":205,""result"":""SUCCESS"",""timestamp"":1647841830657,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"2","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#204"",""duration"":849,""estimatedDuration"":845,""number"":204,""result"":""SUCCESS"",""timestamp"":1645152359034,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"3","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#203"",""duration"":823,""estimatedDuration"":845,""number"":203,""result"":""SUCCESS"",""timestamp"":1642560020873,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"4","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#202"",""duration"":814,""estimatedDuration"":845,""number"":202,""result"":""SUCCESS"",""timestamp"":1641524412998,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"5","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#201"",""duration"":872,""estimatedDuration"":845,""number"":201,""result"":""SUCCESS"",""timestamp"":1641286809958,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"6","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#200"",""duration"":826,""estimatedDuration"":845,""number"":200,""result"":""SUCCESS"",""timestamp"":1638425697887,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"7","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#199"",""duration"":711,""estimatedDuration"":845,""number"":199,""result"":""FAILURE"",""timestamp"":1637636904142,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"8","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#198"",""duration"":855,""estimatedDuration"":845,""number"":198,""result"":""SUCCESS"",""timestamp"":1635842717468,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"9","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#197"",""duration"":806,""estimatedDuration"":845,""number"":197,""result"":""SUCCESS"",""timestamp"":1635143830616,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdurat
 [...]
-"10","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#196"",""duration"":840,""estimatedDuration"":845,""number"":196,""result"":""SUCCESS"",""timestamp"":1634713846944,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"11","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#195"",""duration"":756,""estimatedDuration"":845,""number"":195,""result"":""SUCCESS"",""timestamp"":1634289009179,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"12","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#194"",""duration"":735,""estimatedDuration"":845,""number"":194,""result"":""SUCCESS"",""timestamp"":1634111680194,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"13","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#193"",""duration"":805,""estimatedDuration"":845,""number"":193,""result"":""SUCCESS"",""timestamp"":1634108932844,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"14","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#192"",""duration"":776,""estimatedDuration"":845,""number"":192,""result"":""SUCCESS"",""timestamp"":1631935845537,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"15","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#191"",""duration"":833,""estimatedDuration"":845,""number"":191,""result"":""SUCCESS"",""timestamp"":1631237966948,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"16","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#190"",""duration"":819,""estimatedDuration"":845,""number"":190,""result"":""SUCCESS"",""timestamp"":1631000546032,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"17","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#189"",""duration"":785,""estimatedDuration"":845,""number"":189,""result"":""SUCCESS"",""timestamp"":1630632234339,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"18","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#188"",""duration"":752,""estimatedDuration"":845,""number"":188,""result"":""SUCCESS"",""timestamp"":1629169239734,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"19","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#187"",""duration"":822,""estimatedDuration"":845,""number"":187,""result"":""SUCCESS"",""timestamp"":1628838712486,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
-"20","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.ParametersAction""},{""_class"":""hudson.model.CauseAction""},{},{},{}],""displayName"":""#186"",""duration"":725,""estimatedDuration"":845,""number"":186,""result"":""SUCCESS"",""timestamp"":1628674732761,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://jenkins.merico.cn/job/Create_License/api/json?tree=allBuilds%5Bnumber%2Ctimestamp%2Cdura
 [...]
\ No newline at end of file
+"id","params","data","url","input","created_at"
+75,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#8"",""duration"":11,""estimatedDuration"":10,""number"":8,""result"":""SUCCESS"",""timestamp"":1650023894336,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%
 [...]
+76,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#7"",""duration"":8,""estimatedDuration"":10,""number"":7,""result"":""SUCCESS"",""timestamp"":1650023883294,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%8
 [...]
+77,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#6"",""duration"":10,""estimatedDuration"":10,""number"":6,""result"":""SUCCESS"",""timestamp"":1650022560954,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%
 [...]
+78,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#5"",""duration"":6,""estimatedDuration"":10,""number"":5,""result"":""SUCCESS"",""timestamp"":1650022558491,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%8
 [...]
+79,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#4"",""duration"":6,""estimatedDuration"":10,""number"":4,""result"":""SUCCESS"",""timestamp"":1650022556910,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%8
 [...]
+80,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#3"",""duration"":3,""estimatedDuration"":10,""number"":3,""result"":""SUCCESS"",""timestamp"":1650017186253,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%8
 [...]
+81,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#2"",""duration"":4,""estimatedDuration"":10,""number"":2,""result"":""SUCCESS"",""timestamp"":1650017177939,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%8
 [...]
+82,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleBuild"",""actions"":[{""_class"":""hudson.model.CauseAction""},{},{""_class"":""org.jenkinsci.plugins.displayurlapi.actions.RunDisplayAction""}],""displayName"":""#1"",""duration"":57,""estimatedDuration"":10,""number"":1,""result"":""SUCCESS"",""timestamp"":1650017153775,""changeSet"":{""_class"":""hudson.scm.EmptyChangeLogSet"",""kind"":null}}","https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%
 [...]
diff --git a/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_jobs.csv 
b/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_jobs.csv
index b4e45e6a..a8178b2b 100644
--- a/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_jobs.csv
+++ b/plugins/jenkins/e2e/raw_tables/_raw_jenkins_api_jobs.csv
@@ -1,12 +1,9 @@
-"id","params","data","url","input","created_at"
-"1","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""auto_test"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"2","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""auto_test_framework"",""color"":""disabled""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"3","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""build_devlake"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"4","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""Create_License"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"5","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""Create_License_new"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"6","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""Deploy
 k8s 
single_data"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"7","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""Deploy
 k8s 
single_new"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"8","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""Deploy
 k8s 
test"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"9","{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""devlake_empty_build"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"10","{""ConnectionId"":1}","{""_class"":""org.jenkinsci.plugins.workflow.job.WorkflowJob"",""name"":""test-platform-backend"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
-"11","{""ConnectionId"":1}","{""_class"":""org.jenkinsci.plugins.workflow.job.WorkflowJob"",""name"":""test-platform-frontend"",""color"":""blue""}","https://jenkins.merico.cn/api/json?tree=jobs%5Bname%2Cclass%2Ccolor%2Cbase%5D%7B1%2C100%7D","null","2022-06-15
 15:24:40.697"
+"id","params","data","url","input","created_at"
+12,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""devlake"",""url"":""https://test.nddtf.com/job/devlake/"",""color"":""blue""}","https://test.nddtf.com/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 """"}","2022-07-13 02:22:34.044"
+13,"{""ConnectionId"":1}","{""_class"":""com.cloudbees.hudson.plugins.folder.Folder"",""name"":""dir-test"",""url"":""https://test.nddtf.com/job/dir-test/"",""jobs"":[{""_class"":""com.cloudbees.hudson.plugins.folder.Folder""},{""_class"":""org.jenkinsci.plugins.workflow.job.WorkflowJob""},{""_class"":""hudson.model.FreeStyleProject""}]}","https://test.nddtf.com/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 """"}","2022-07-13 02:22:34.044"
+14,"{""ConnectionId"":1}","{""_class"":""org.jenkinsci.plugins.workflow.job.WorkflowJob"",""name"":""测试流水线"",""url"":""https://test.nddtf.com/job/%E6%B5%8B%E8%AF%95%E6%B5%81%E6%B0%B4%E7%BA%BF/"",""color"":""notbuilt""}","https://test.nddtf.com/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 """"}","2022-07-13 02:22:34.044"
+15,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""这是一个改了名字的测试任务"",""url"":""https://test.nddtf.com/job/%E8%BF%99%E6%98%AF%E4%B8%80%E4%B8%AA%E6%94%B9%E4%BA%86%E5%90%8D%E5%AD%97%E7%9A%84%E6%B5%8B%E8%AF%95%E4%BB%BB%E5%8A%A1/"",""color"":""blue""}","https://test.nddtf.com/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 """"}","2022-07-13 02:22:34.044"
+16,"{""ConnectionId"":1}","{""_class"":""com.cloudbees.hudson.plugins.folder.Folder"",""name"":""dir-test-2"",""url"":""https://test.nddtf.com/job/dir-test/job/dir-test-2/"",""jobs"":[{""_class"":""hudson.model.FreeStyleProject""}]}","https://test.nddtf.com/job/dir-test/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 ""job/dir-test/""}","2022-07-13 02:22:34.178"
+17,"{""ConnectionId"":1}","{""_class"":""org.jenkinsci.plugins.workflow.job.WorkflowJob"",""name"":""pipeline-test"",""url"":""https://test.nddtf.com/job/dir-test/job/pipeline-test/"",""color"":""notbuilt""}","https://test.nddtf.com/job/dir-test/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 ""job/dir-test/""}","2022-07-13 02:22:34.178"
+18,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""testfile"",""url"":""https://test.nddtf.com/job/dir-test/job/testfile/"",""color"":""notbuilt""}","https://test.nddtf.com/job/dir-test/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 ""job/dir-test/""}","2022-07-13 02:22:34.178"
+19,"{""ConnectionId"":1}","{""_class"":""hudson.model.FreeStyleProject"",""name"":""free"",""url"":""https://test.nddtf.com/job/dir-test/job/dir-test-2/job/free/"",""color"":""notbuilt""}","https://test.nddtf.com/job/dir-test/job/dir-test-2/api/json?tree=jobs%5Bname%2Cclass%2Curl%2Ccolor%2Cbase%2Cjobs%5D%7B0%2C100%7D","{""Path"":
 ""job/dir-test/job/dir-test-2/""}","2022-07-13 02:22:34.500"
diff --git a/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_builds.csv 
b/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_builds.csv
index 9a5c5a04..b61c1434 100644
--- a/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_builds.csv
+++ b/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_builds.csv
@@ -1,21 +1,9 @@
-connection_id,job_name,number,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,connection_id,job_name,duration,display_name,estimated_duration,number,result,timestamp,start_time,commit_sha
-1,Create_License,186,"{""ConnectionId"":1}",_raw_jenkins_api_builds,20,,1,Create_License,725,#186,845,186,SUCCESS,1628674732761,2021-08-11T09:38:52.000+00:00,
-1,Create_License,187,"{""ConnectionId"":1}",_raw_jenkins_api_builds,19,,1,Create_License,822,#187,845,187,SUCCESS,1628838712486,2021-08-13T07:11:52.000+00:00,
-1,Create_License,188,"{""ConnectionId"":1}",_raw_jenkins_api_builds,18,,1,Create_License,752,#188,845,188,SUCCESS,1629169239734,2021-08-17T03:00:39.000+00:00,
-1,Create_License,189,"{""ConnectionId"":1}",_raw_jenkins_api_builds,17,,1,Create_License,785,#189,845,189,SUCCESS,1630632234339,2021-09-03T01:23:54.000+00:00,
-1,Create_License,190,"{""ConnectionId"":1}",_raw_jenkins_api_builds,16,,1,Create_License,819,#190,845,190,SUCCESS,1631000546032,2021-09-07T07:42:26.000+00:00,
-1,Create_License,191,"{""ConnectionId"":1}",_raw_jenkins_api_builds,15,,1,Create_License,833,#191,845,191,SUCCESS,1631237966948,2021-09-10T01:39:26.000+00:00,
-1,Create_License,192,"{""ConnectionId"":1}",_raw_jenkins_api_builds,14,,1,Create_License,776,#192,845,192,SUCCESS,1631935845537,2021-09-18T03:30:45.000+00:00,
-1,Create_License,193,"{""ConnectionId"":1}",_raw_jenkins_api_builds,13,,1,Create_License,805,#193,845,193,SUCCESS,1634108932844,2021-10-13T07:08:52.000+00:00,
-1,Create_License,194,"{""ConnectionId"":1}",_raw_jenkins_api_builds,12,,1,Create_License,735,#194,845,194,SUCCESS,1634111680194,2021-10-13T07:54:40.000+00:00,
-1,Create_License,195,"{""ConnectionId"":1}",_raw_jenkins_api_builds,11,,1,Create_License,756,#195,845,195,SUCCESS,1634289009179,2021-10-15T09:10:09.000+00:00,
-1,Create_License,196,"{""ConnectionId"":1}",_raw_jenkins_api_builds,10,,1,Create_License,840,#196,845,196,SUCCESS,1634713846944,2021-10-20T07:10:46.000+00:00,
-1,Create_License,197,"{""ConnectionId"":1}",_raw_jenkins_api_builds,9,,1,Create_License,806,#197,845,197,SUCCESS,1635143830616,2021-10-25T06:37:10.000+00:00,
-1,Create_License,198,"{""ConnectionId"":1}",_raw_jenkins_api_builds,8,,1,Create_License,855,#198,845,198,SUCCESS,1635842717468,2021-11-02T08:45:17.000+00:00,
-1,Create_License,199,"{""ConnectionId"":1}",_raw_jenkins_api_builds,7,,1,Create_License,711,#199,845,199,FAILURE,1637636904142,2021-11-23T03:08:24.000+00:00,
-1,Create_License,200,"{""ConnectionId"":1}",_raw_jenkins_api_builds,6,,1,Create_License,826,#200,845,200,SUCCESS,1638425697887,2021-12-02T06:14:57.000+00:00,
-1,Create_License,201,"{""ConnectionId"":1}",_raw_jenkins_api_builds,5,,1,Create_License,872,#201,845,201,SUCCESS,1641286809958,2022-01-04T09:00:09.000+00:00,
-1,Create_License,202,"{""ConnectionId"":1}",_raw_jenkins_api_builds,4,,1,Create_License,814,#202,845,202,SUCCESS,1641524412998,2022-01-07T03:00:12.000+00:00,
-1,Create_License,203,"{""ConnectionId"":1}",_raw_jenkins_api_builds,3,,1,Create_License,823,#203,845,203,SUCCESS,1642560020873,2022-01-19T02:40:20.000+00:00,
-1,Create_License,204,"{""ConnectionId"":1}",_raw_jenkins_api_builds,2,,1,Create_License,849,#204,845,204,SUCCESS,1645152359034,2022-02-18T02:45:59.000+00:00,
-1,Create_License,205,"{""ConnectionId"":1}",_raw_jenkins_api_builds,1,,1,Create_License,843,#205,845,205,SUCCESS,1647841830657,2022-03-21T05:50:30.000+00:00,
+connection_id,job_name,number,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,duration,display_name,estimated_duration,result,timestamp,start_time,commit_sha
+1,这是一个改了名字的测试任务,1,"{""ConnectionId"":1}",_raw_jenkins_api_builds,82,,57,#1,10,SUCCESS,1650017153775,2022-04-15T10:05:53.000+00:00,
+1,这是一个改了名字的测试任务,2,"{""ConnectionId"":1}",_raw_jenkins_api_builds,81,,4,#2,10,SUCCESS,1650017177939,2022-04-15T10:06:17.000+00:00,
+1,这是一个改了名字的测试任务,3,"{""ConnectionId"":1}",_raw_jenkins_api_builds,80,,3,#3,10,SUCCESS,1650017186253,2022-04-15T10:06:26.000+00:00,
+1,这是一个改了名字的测试任务,4,"{""ConnectionId"":1}",_raw_jenkins_api_builds,79,,6,#4,10,SUCCESS,1650022556910,2022-04-15T11:35:56.000+00:00,
+1,这是一个改了名字的测试任务,5,"{""ConnectionId"":1}",_raw_jenkins_api_builds,78,,6,#5,10,SUCCESS,1650022558491,2022-04-15T11:35:58.000+00:00,
+1,这是一个改了名字的测试任务,6,"{""ConnectionId"":1}",_raw_jenkins_api_builds,77,,10,#6,10,SUCCESS,1650022560954,2022-04-15T11:36:00.000+00:00,
+1,这是一个改了名字的测试任务,7,"{""ConnectionId"":1}",_raw_jenkins_api_builds,76,,8,#7,10,SUCCESS,1650023883294,2022-04-15T11:58:03.000+00:00,
+1,这是一个改了名字的测试任务,8,"{""ConnectionId"":1}",_raw_jenkins_api_builds,75,,11,#8,10,SUCCESS,1650023894336,2022-04-15T11:58:14.000+00:00,
diff --git a/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_jobs.csv 
b/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_jobs.csv
index 5cbe8dda..4b3db8a0 100644
--- a/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_jobs.csv
+++ b/plugins/jenkins/e2e/snapshot_tables/_tool_jenkins_jobs.csv
@@ -1,12 +1,9 @@
-connection_id,name,class,color,base,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
-1,auto_test,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,1,
-1,auto_test_framework,hudson.model.FreeStyleProject,disabled,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,2,
-1,build_devlake,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,3,
-1,Create_License,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,4,
-1,Create_License_new,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,5,
-1,Deploy k8s 
single_data,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,6,
-1,Deploy k8s 
single_new,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,7,
-1,Deploy k8s 
test,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,8,
-1,devlake_empty_build,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,9,
-1,test-platform-backend,org.jenkinsci.plugins.workflow.job.WorkflowJob,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,10,
-1,test-platform-frontend,org.jenkinsci.plugins.workflow.job.WorkflowJob,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,11,
+connection_id,name,path,class,color,base,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
+1,devlake,,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,12,
+1,dir-test,,com.cloudbees.hudson.plugins.folder.Folder,,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,13,
+1,dir-test-2,job/dir-test/,com.cloudbees.hudson.plugins.folder.Folder,,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,16,
+1,free,job/dir-test/job/dir-test-2/,hudson.model.FreeStyleProject,notbuilt,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,19,
+1,pipeline-test,job/dir-test/,org.jenkinsci.plugins.workflow.job.WorkflowJob,notbuilt,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,17,
+1,testfile,job/dir-test/,hudson.model.FreeStyleProject,notbuilt,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,18,
+1,测试流水线,,org.jenkinsci.plugins.workflow.job.WorkflowJob,notbuilt,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,14,
+1,这是一个改了名字的测试任务,,hudson.model.FreeStyleProject,blue,,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,15,
diff --git a/plugins/jenkins/e2e/snapshot_tables/builds.csv 
b/plugins/jenkins/e2e/snapshot_tables/builds.csv
index f3dca0fa..38476b9b 100644
--- a/plugins/jenkins/e2e/snapshot_tables/builds.csv
+++ b/plugins/jenkins/e2e/snapshot_tables/builds.csv
@@ -1,21 +1,9 @@
 
id,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,job_id,name,commit_sha,duration_sec,status,started_date
-jenkins:JenkinsBuild:1:Create_License:186,"{""ConnectionId"":1}",_raw_jenkins_api_builds,20,,jenkins:JenkinsJob:1:Create_License,#186,,0,SUCCESS,2021-08-11T09:38:52.000+00:00
-jenkins:JenkinsBuild:1:Create_License:187,"{""ConnectionId"":1}",_raw_jenkins_api_builds,19,,jenkins:JenkinsJob:1:Create_License,#187,,0,SUCCESS,2021-08-13T07:11:52.000+00:00
-jenkins:JenkinsBuild:1:Create_License:188,"{""ConnectionId"":1}",_raw_jenkins_api_builds,18,,jenkins:JenkinsJob:1:Create_License,#188,,0,SUCCESS,2021-08-17T03:00:39.000+00:00
-jenkins:JenkinsBuild:1:Create_License:189,"{""ConnectionId"":1}",_raw_jenkins_api_builds,17,,jenkins:JenkinsJob:1:Create_License,#189,,0,SUCCESS,2021-09-03T01:23:54.000+00:00
-jenkins:JenkinsBuild:1:Create_License:190,"{""ConnectionId"":1}",_raw_jenkins_api_builds,16,,jenkins:JenkinsJob:1:Create_License,#190,,0,SUCCESS,2021-09-07T07:42:26.000+00:00
-jenkins:JenkinsBuild:1:Create_License:191,"{""ConnectionId"":1}",_raw_jenkins_api_builds,15,,jenkins:JenkinsJob:1:Create_License,#191,,0,SUCCESS,2021-09-10T01:39:26.000+00:00
-jenkins:JenkinsBuild:1:Create_License:192,"{""ConnectionId"":1}",_raw_jenkins_api_builds,14,,jenkins:JenkinsJob:1:Create_License,#192,,0,SUCCESS,2021-09-18T03:30:45.000+00:00
-jenkins:JenkinsBuild:1:Create_License:193,"{""ConnectionId"":1}",_raw_jenkins_api_builds,13,,jenkins:JenkinsJob:1:Create_License,#193,,0,SUCCESS,2021-10-13T07:08:52.000+00:00
-jenkins:JenkinsBuild:1:Create_License:194,"{""ConnectionId"":1}",_raw_jenkins_api_builds,12,,jenkins:JenkinsJob:1:Create_License,#194,,0,SUCCESS,2021-10-13T07:54:40.000+00:00
-jenkins:JenkinsBuild:1:Create_License:195,"{""ConnectionId"":1}",_raw_jenkins_api_builds,11,,jenkins:JenkinsJob:1:Create_License,#195,,0,SUCCESS,2021-10-15T09:10:09.000+00:00
-jenkins:JenkinsBuild:1:Create_License:196,"{""ConnectionId"":1}",_raw_jenkins_api_builds,10,,jenkins:JenkinsJob:1:Create_License,#196,,0,SUCCESS,2021-10-20T07:10:46.000+00:00
-jenkins:JenkinsBuild:1:Create_License:197,"{""ConnectionId"":1}",_raw_jenkins_api_builds,9,,jenkins:JenkinsJob:1:Create_License,#197,,0,SUCCESS,2021-10-25T06:37:10.000+00:00
-jenkins:JenkinsBuild:1:Create_License:198,"{""ConnectionId"":1}",_raw_jenkins_api_builds,8,,jenkins:JenkinsJob:1:Create_License,#198,,0,SUCCESS,2021-11-02T08:45:17.000+00:00
-jenkins:JenkinsBuild:1:Create_License:199,"{""ConnectionId"":1}",_raw_jenkins_api_builds,7,,jenkins:JenkinsJob:1:Create_License,#199,,0,FAILURE,2021-11-23T03:08:24.000+00:00
-jenkins:JenkinsBuild:1:Create_License:200,"{""ConnectionId"":1}",_raw_jenkins_api_builds,6,,jenkins:JenkinsJob:1:Create_License,#200,,0,SUCCESS,2021-12-02T06:14:57.000+00:00
-jenkins:JenkinsBuild:1:Create_License:201,"{""ConnectionId"":1}",_raw_jenkins_api_builds,5,,jenkins:JenkinsJob:1:Create_License,#201,,0,SUCCESS,2022-01-04T09:00:09.000+00:00
-jenkins:JenkinsBuild:1:Create_License:202,"{""ConnectionId"":1}",_raw_jenkins_api_builds,4,,jenkins:JenkinsJob:1:Create_License,#202,,0,SUCCESS,2022-01-07T03:00:12.000+00:00
-jenkins:JenkinsBuild:1:Create_License:203,"{""ConnectionId"":1}",_raw_jenkins_api_builds,3,,jenkins:JenkinsJob:1:Create_License,#203,,0,SUCCESS,2022-01-19T02:40:20.000+00:00
-jenkins:JenkinsBuild:1:Create_License:204,"{""ConnectionId"":1}",_raw_jenkins_api_builds,2,,jenkins:JenkinsJob:1:Create_License,#204,,0,SUCCESS,2022-02-18T02:45:59.000+00:00
-jenkins:JenkinsBuild:1:Create_License:205,"{""ConnectionId"":1}",_raw_jenkins_api_builds,1,,jenkins:JenkinsJob:1:Create_License,#205,,0,SUCCESS,2022-03-21T05:50:30.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:1,"{""ConnectionId"":1}",_raw_jenkins_api_builds,82,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#1,,0,SUCCESS,2022-04-15T10:05:53.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:2,"{""ConnectionId"":1}",_raw_jenkins_api_builds,81,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#2,,0,SUCCESS,2022-04-15T10:06:17.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:3,"{""ConnectionId"":1}",_raw_jenkins_api_builds,80,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#3,,0,SUCCESS,2022-04-15T10:06:26.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:4,"{""ConnectionId"":1}",_raw_jenkins_api_builds,79,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#4,,0,SUCCESS,2022-04-15T11:35:56.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:5,"{""ConnectionId"":1}",_raw_jenkins_api_builds,78,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#5,,0,SUCCESS,2022-04-15T11:35:58.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:6,"{""ConnectionId"":1}",_raw_jenkins_api_builds,77,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#6,,0,SUCCESS,2022-04-15T11:36:00.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:7,"{""ConnectionId"":1}",_raw_jenkins_api_builds,76,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#7,,0,SUCCESS,2022-04-15T11:58:03.000+00:00
+jenkins:JenkinsBuild:1:这是一个改了名字的测试任务:8,"{""ConnectionId"":1}",_raw_jenkins_api_builds,75,,jenkins:JenkinsJob:1:这是一个改了名字的测试任务,#8,,0,SUCCESS,2022-04-15T11:58:14.000+00:00
diff --git a/plugins/jenkins/e2e/snapshot_tables/jobs.csv 
b/plugins/jenkins/e2e/snapshot_tables/jobs.csv
index 372e8a97..13ac2c44 100644
--- a/plugins/jenkins/e2e/snapshot_tables/jobs.csv
+++ b/plugins/jenkins/e2e/snapshot_tables/jobs.csv
@@ -1,12 +1,9 @@
 id,name,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark
-jenkins:JenkinsJob:1:auto_test,auto_test,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,1,
-jenkins:JenkinsJob:1:auto_test_framework,auto_test_framework,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,2,
-jenkins:JenkinsJob:1:build_devlake,build_devlake,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,3,
-jenkins:JenkinsJob:1:Create_License,Create_License,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,4,
-jenkins:JenkinsJob:1:Create_License_new,Create_License_new,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,5,
-jenkins:JenkinsJob:1:Deploy k8s single_data,Deploy k8s 
single_data,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,6,
-jenkins:JenkinsJob:1:Deploy k8s single_new,Deploy k8s 
single_new,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,7,
-jenkins:JenkinsJob:1:Deploy k8s test,Deploy k8s 
test,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,8,
-jenkins:JenkinsJob:1:devlake_empty_build,devlake_empty_build,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,9,
-jenkins:JenkinsJob:1:test-platform-backend,test-platform-backend,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,10,
-jenkins:JenkinsJob:1:test-platform-frontend,test-platform-frontend,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,11,
+jenkins:JenkinsJob:1:devlake,devlake,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,12,
+jenkins:JenkinsJob:1:dir-test,dir-test,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,13,
+jenkins:JenkinsJob:1:dir-test-2,dir-test-2,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,16,
+jenkins:JenkinsJob:1:free,free,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,19,
+jenkins:JenkinsJob:1:pipeline-test,pipeline-test,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,17,
+jenkins:JenkinsJob:1:testfile,testfile,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,18,
+jenkins:JenkinsJob:1:测试流水线,测试流水线,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,14,
+jenkins:JenkinsJob:1:这是一个改了名字的测试任务,这是一个改了名字的测试任务,"{""ConnectionId"":1}",_raw_jenkins_api_jobs,15,
diff --git a/plugins/jenkins/impl/impl.go b/plugins/jenkins/impl/impl.go
index bebf0180..d9d6f92d 100644
--- a/plugins/jenkins/impl/impl.go
+++ b/plugins/jenkins/impl/impl.go
@@ -36,6 +36,7 @@ var _ core.PluginInit = (*Jenkins)(nil)
 var _ core.PluginTask = (*Jenkins)(nil)
 var _ core.PluginApi = (*Jenkins)(nil)
 var _ core.Migratable = (*Jenkins)(nil)
+var _ core.CloseablePluginTask = (*Jenkins)(nil)
 
 type Jenkins struct{}
 
@@ -119,3 +120,12 @@ func (plugin Jenkins) ApiResources() 
map[string]map[string]core.ApiResourceHandl
                },
        }
 }
+
+func (plugin Jenkins) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.JenkinsTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/jenkins/jenkins.go b/plugins/jenkins/jenkins.go
index fcbd14b2..a693fa5c 100644
--- a/plugins/jenkins/jenkins.go
+++ b/plugins/jenkins/jenkins.go
@@ -27,7 +27,7 @@ var PluginEntry impl.Jenkins
 
 func main() {
        jenkinsCmd := &cobra.Command{Use: "jenkins"}
-       connectionId := jenkinsCmd.Flags().Uint64P("connection", "c", 0, 
"jenkins connection id")
+       connectionId := jenkinsCmd.Flags().Uint64P("connection", "c", 1, 
"jenkins connection id")
        jenkinsCmd.Run = func(cmd *cobra.Command, args []string) {
                runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
                        "connectionId": *connectionId,
diff --git a/plugins/jenkins/models/job.go b/plugins/jenkins/models/job.go
index dbac6366..13b0fd9d 100644
--- a/plugins/jenkins/models/job.go
+++ b/plugins/jenkins/models/job.go
@@ -17,13 +17,17 @@ limitations under the License.
 
 package models
 
-import "github.com/apache/incubator-devlake/models/common"
+import (
+       "github.com/apache/incubator-devlake/models/common"
+       "github.com/apache/incubator-devlake/plugins/helper"
+)
 
 // JenkinsJobProps current used jenkins job props
 type JenkinsJobProps struct {
        // collected fields
        ConnectionId uint64 `gorm:"primaryKey"`
        Name         string `gorm:"primaryKey;type:varchar(255)"`
+       Path         string `gorm:"primaryKey;type:varchar(511)"`
        Class        string `gorm:"type:varchar(255)"`
        Color        string `gorm:"type:varchar(255)"`
        Base         string `gorm:"type:varchar(255)"`
@@ -38,3 +42,15 @@ type JenkinsJob struct {
 func (JenkinsJob) TableName() string {
        return "_tool_jenkins_jobs"
 }
+
+type FolderInput struct {
+       Path string
+       *helper.ListBaseNode
+}
+
+func NewFolderInput(path string) *FolderInput {
+       return &FolderInput{
+               Path:         path,
+               ListBaseNode: helper.NewListBaseNode(),
+       }
+}
diff --git a/plugins/jenkins/models/migrationscripts/archived/job.go 
b/plugins/jenkins/models/migrationscripts/archived/job.go
index 95310876..ace15009 100644
--- a/plugins/jenkins/models/migrationscripts/archived/job.go
+++ b/plugins/jenkins/models/migrationscripts/archived/job.go
@@ -26,6 +26,7 @@ type JenkinsJobProps struct {
        // collected fields
        ConnectionId uint64 `gorm:"primaryKey"`
        Name         string `gorm:"primaryKey;type:varchar(255)"`
+       Path         string `gorm:"primaryKey;type:varchar(511)"`
        Class        string `gorm:"type:varchar(255)"`
        Color        string `gorm:"type:varchar(255)"`
        Base         string `gorm:"type:varchar(255)"`
diff --git a/plugins/jenkins/models/migrationscripts/init_schema.go 
b/plugins/jenkins/models/migrationscripts/init_schema.go
index d5975fa1..406974c4 100644
--- a/plugins/jenkins/models/migrationscripts/init_schema.go
+++ b/plugins/jenkins/models/migrationscripts/init_schema.go
@@ -79,7 +79,7 @@ func (*InitSchemas) Up(ctx context.Context, db *gorm.DB) 
error {
 }
 
 func (*InitSchemas) Version() uint64 {
-       return 20220614201236
+       return 20220712201237
 }
 
 func (*InitSchemas) Name() string {
diff --git a/plugins/jenkins/models/response.go 
b/plugins/jenkins/models/response.go
index cdeea46f..ace21939 100644
--- a/plugins/jenkins/models/response.go
+++ b/plugins/jenkins/models/response.go
@@ -41,6 +41,7 @@ type Job struct {
        Name  string `json:"name"`
        Color string `json:"color"`
        Class string `json:"_class"`
+       Jobs  *[]Job `json:"jobs"`
 }
 type Views struct {
        URL   string `json:"url"`
diff --git a/plugins/jenkins/tasks/build_collector.go 
b/plugins/jenkins/tasks/build_collector.go
index 4f6fff46..dc601421 100644
--- a/plugins/jenkins/tasks/build_collector.go
+++ b/plugins/jenkins/tasks/build_collector.go
@@ -41,13 +41,14 @@ var CollectApiBuildsMeta = core.SubTaskMeta{
 
 type SimpleJob struct {
        Name string
+       Path string
 }
 
 func CollectApiBuilds(taskCtx core.SubTaskContext) error {
        db := taskCtx.GetDal()
        data := taskCtx.GetData().(*JenkinsTaskData)
        clauses := []dal.Clause{
-               dal.Select("tjj.name"),
+               dal.Select("tjj.name,tjj.path"),
                dal.From("_tool_jenkins_jobs tjj"),
                dal.Where(`tjj.connection_id = ?`, data.Options.ConnectionId),
        }
@@ -74,7 +75,7 @@ func CollectApiBuilds(taskCtx core.SubTaskContext) error {
                ApiClient:   data.ApiClient,
                PageSize:    100,
                Input:       iterator,
-               UrlTemplate: "job/{{ .Input.Name }}/api/json",
+               UrlTemplate: "{{ .Input.Path }}job/{{ .Input.Name }}/api/json",
                /*
                        (Optional) Return query string for request, or you can 
plug them into UrlTemplate directly
                */
diff --git a/plugins/jenkins/tasks/job_collector.go 
b/plugins/jenkins/tasks/job_collector.go
index d010706c..59abde31 100644
--- a/plugins/jenkins/tasks/job_collector.go
+++ b/plugins/jenkins/tasks/job_collector.go
@@ -25,6 +25,7 @@ import (
 
        "github.com/apache/incubator-devlake/plugins/core"
        "github.com/apache/incubator-devlake/plugins/helper"
+       models "github.com/apache/incubator-devlake/plugins/jenkins/models"
 )
 
 const RAW_JOB_TABLE = "jenkins_api_jobs"
@@ -38,6 +39,8 @@ var CollectApiJobsMeta = core.SubTaskMeta{
 }
 
 func CollectApiJobs(taskCtx core.SubTaskContext) error {
+       it := helper.NewQueueIterator()
+       it.Push(models.NewFolderInput(""))
        data := taskCtx.GetData().(*JenkinsTaskData)
        incremental := false
        collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
@@ -61,15 +64,28 @@ func CollectApiJobs(taskCtx core.SubTaskContext) error {
                // jenkins api is special, 1. If the concurrency is larger than 
1, then it will report 500.
                Concurrency: 1,
 
-               UrlTemplate: "api/json",
+               UrlTemplate: "{{ .Input.Path }}api/json",
+               Input:       it,
                Query: func(reqData *helper.RequestData) (url.Values, error) {
                        query := url.Values{}
                        treeValue := fmt.Sprintf(
-                               "jobs[name,class,color,base]{%d,%d}",
+                               "jobs[name,class,url,color,base,jobs]{%d,%d}",
                                reqData.Pager.Skip, 
reqData.Pager.Skip+reqData.Pager.Size)
                        query.Set("tree", treeValue)
                        return query, nil
                },
+               Header: func(reqData *helper.RequestData) (http.Header, error) {
+                       input, ok := reqData.Input.(*models.FolderInput)
+                       if ok {
+                               return http.Header{
+                                       "Path": {
+                                               input.Path,
+                                       },
+                               }, nil
+                       } else {
+                               return nil, fmt.Errorf("empty FolderInput")
+                       }
+               },
 
                ResponseParser: func(res *http.Response) ([]json.RawMessage, 
error) {
                        var data struct {
@@ -79,6 +95,18 @@ func CollectApiJobs(taskCtx core.SubTaskContext) error {
                        if err != nil {
                                return nil, err
                        }
+                       BasePath := res.Request.Header.Get("Path")
+                       for _, rawJobs := range data.Jobs {
+                               job := &models.Job{}
+                               err := json.Unmarshal(rawJobs, job)
+                               if err != nil {
+                                       return nil, err
+                               }
+
+                               if job.Jobs != nil {
+                                       it.Push(models.NewFolderInput(BasePath 
+ "job/" + job.Name + "/"))
+                               }
+                       }
                        return data.Jobs, nil
                },
        })
diff --git a/plugins/jenkins/tasks/job_extractor.go 
b/plugins/jenkins/tasks/job_extractor.go
index 6f289008..1955c8fb 100644
--- a/plugins/jenkins/tasks/job_extractor.go
+++ b/plugins/jenkins/tasks/job_extractor.go
@@ -58,12 +58,20 @@ func ExtractApiJobs(taskCtx core.SubTaskContext) error {
                        if err != nil {
                                return nil, err
                        }
+
+                       input := &models.FolderInput{}
+                       err = json.Unmarshal(row.Input, input)
+                       if err != nil {
+                               return nil, err
+                       }
+
                        results := make([]interface{}, 0, 1)
 
                        job := &models.JenkinsJob{
                                JenkinsJobProps: models.JenkinsJobProps{
                                        ConnectionId: data.Options.ConnectionId,
                                        Name:         body.Name,
+                                       Path:         input.Path,
                                        Class:        body.Class,
                                        Color:        body.Color,
                                },
diff --git a/plugins/jira/impl/impl.go b/plugins/jira/impl/impl.go
index a9131849..5e887584 100644
--- a/plugins/jira/impl/impl.go
+++ b/plugins/jira/impl/impl.go
@@ -40,6 +40,7 @@ var _ core.PluginTask = (*Jira)(nil)
 var _ core.PluginApi = (*Jira)(nil)
 var _ core.Migratable = (*Jira)(nil)
 var _ core.PluginBlueprintV100 = (*Jira)(nil)
+var _ core.CloseablePluginTask = (*Jira)(nil)
 
 type Jira struct{}
 
@@ -187,3 +188,12 @@ func (plugin Jira) ApiResources() 
map[string]map[string]core.ApiResourceHandler
                },
        }
 }
+
+func (plugin Jira) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.JiraTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}
diff --git a/plugins/tapd/impl/impl.go b/plugins/tapd/impl/impl.go
index 353b3456..d4709740 100644
--- a/plugins/tapd/impl/impl.go
+++ b/plugins/tapd/impl/impl.go
@@ -40,6 +40,7 @@ var _ core.PluginInit = (*Tapd)(nil)
 var _ core.PluginTask = (*Tapd)(nil)
 var _ core.PluginApi = (*Tapd)(nil)
 var _ core.Migratable = (*Tapd)(nil)
+var _ core.CloseablePluginTask = (*Tapd)(nil)
 
 type Tapd struct{}
 
@@ -187,3 +188,12 @@ func (plugin Tapd) ApiResources() 
map[string]map[string]core.ApiResourceHandler
                },
        }
 }
+
+func (plugin Tapd) Close(taskCtx core.TaskContext) error {
+       data, ok := taskCtx.GetData().(*tasks.TapdTaskData)
+       if !ok {
+               return fmt.Errorf("GetData failed when try to close %+v", 
taskCtx)
+       }
+       data.ApiClient.Release()
+       return nil
+}

Reply via email to