You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by kl...@apache.org on 2022/11/23 06:55:50 UTC

[incubator-devlake] branch main updated: feat(zentao): add new plugin zentao (#3779)

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 941314896 feat(zentao): add new plugin zentao (#3779)
941314896 is described below

commit 94131489671a64135840c723087c45cb8c62ed8d
Author: Warren Chen <yi...@merico.dev>
AuthorDate: Wed Nov 23 14:55:44 2022 +0800

    feat(zentao): add new plugin zentao (#3779)
    
    * fix(dora): use project name as data scope to enrich env
    
    * feat(zentao): update zentao (#3763)
    
    * feat(zentao): create new plugin
    
    Relate to #2961
    
    * feat(zentao): create new plugin
    
    Relate to #2961
    
    * fix(zentao): update connection
    
    * feat(zentao): update project collector
    
    * feat(zentao):add zentao project extractor
    
    * fix(zentao): fix minor issues
    
    * feat(zentao): add execution
    
    * feat(zentao): fix execution time
    
    * feat(zentao): add zentao stories
    
    * fix(zentao):apiurl&model error
    
    * fix(zentao):restore code
    
    * feat(zentao):add zentao bugs
    
    * fix(zentao): rebase
    
    * feat(zentao):add zentao task and change stories to story
    
    * feat(zentao): finish the initial version
    
    * fix(zentao): update lint
    
    * feat(zentao): add config-ui support
    
    Co-authored-by: yuqiangabab <11...@qq.com>
    
    * fix(zentao): update icon (#3765)
    
    * fix(zentao): update progress type (#3767)
    
    * fix(jenkins): update build stages logic (#3760)
    
    * feat(dora): update logic for change lead time (#3742)
    
    * feat(dora): update logic for change lead time
    
    closes #3516
    
    * feat(dora): update e2e according to review
    
    * fix(zentao): update progress typoe
    
    * fix(zentao): update fields to float (#3769)
    
    * feat(zentao): add account (#3771)
    
    * fix(zentao): use const to indicate status and type
    
    * feat(zentao): add account
    
    * feat(zentao): add account subtasks
    
    * fix(zentao): modify issues
    
    * fix(zentao): add e2e (#3772)
    
    * feat(zentao): add interfered data to e2e (#3778)
    
    * fix(zentao): update Began and RealBegan field to time
    
    * fix(zentao): update id field type
    
    * fix(zentao): update e2e
    
    Co-authored-by: yuqiangabab <11...@qq.com>
---
 .../blueprints/ProviderTransformationSettings.jsx  |   2 +
 .../create-workflow/DataTransformations.jsx        |   2 +
 .../src/components/menus/PipelineConfigsMenu.jsx   |  10 +
 .../src/components/pipelines/StageTaskName.jsx     |   1 +
 config-ui/src/data/Providers.js                    |  29 ++-
 config-ui/src/data/availablePlugins.js             |   2 +-
 config-ui/src/data/integrations.jsx                |  22 ++
 .../zentao.js}                                     |  16 +-
 config-ui/src/hooks/useConnectionValidation.jsx    |   1 +
 config-ui/src/hooks/useIntegrations.jsx            |   2 +
 config-ui/src/images/integrations/zentao.svg       |   4 +
 .../src/pages/blueprints/blueprint-settings.jsx    |   1 +
 .../pages/configure/connections/ConnectionForm.jsx |   1 +
 config-ui/src/pages/configure/settings/zentao.jsx  |  57 ++++++
 config-ui/src/registry/plugins/zentao.json         |  55 +++++
 .../dora/e2e/calculate_change_lead_time_test.go    |   2 +-
 .../dora/e2e/connect_incident_to_deploy_test.go    |   2 +-
 plugins/dora/e2e/env_enricher_test.go              |  30 +--
 ...icd_tasks_changeleadtime.csv => cicd_tasks.csv} |   0
 .../e2e/raw_tables/cicd_tasks_for_enrich_env.csv   |  19 ++
 plugins/dora/e2e/raw_tables/lake_cicd_tasks.csv    |  25 ---
 .../e2e/raw_tables/lake_cicd_tasks_for_prefix.csv  |   2 -
 .../dora/e2e/snapshot_tables/enrich_cicd_tasks.csv |  19 ++
 .../dora/e2e/snapshot_tables/lake_cicd_tasks.csv   |  25 ---
 .../e2e/snapshot_tables/lake_cicd_tasks_prefix.csv |   2 -
 plugins/dora/tasks/cicd_task_env_enricher.go       |  74 ++-----
 plugins/helper/iso8601time.go                      |  38 ++++
 plugins/zentao/api/blueprint.go                    |  70 +++++++
 plugins/zentao/api/connection.go                   | 148 ++++++++++++++
 plugins/zentao/api/init.go                         |  39 ++++
 plugins/zentao/e2e/account_test.go                 |  63 ++++++
 plugins/zentao/e2e/bug_test.go                     |  69 +++++++
 plugins/zentao/e2e/department_test.go              |  63 ++++++
 plugins/zentao/e2e/execution_test.go               |  63 ++++++
 plugins/zentao/e2e/product_test.go                 |  63 ++++++
 .../e2e/raw_tables/_raw_zentao_api_accounts.csv    |  13 ++
 .../zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv |   7 +
 .../e2e/raw_tables/_raw_zentao_api_departments.csv |  13 ++
 .../e2e/raw_tables/_raw_zentao_api_executions.csv  |   4 +
 .../raw_tables/_raw_zentao_api_executions_real.csv |   2 +
 .../e2e/raw_tables/_raw_zentao_api_products.csv    |   3 +
 .../e2e/raw_tables/_raw_zentao_api_stories.csv     |  10 +
 .../e2e/raw_tables/_raw_zentao_api_tasks.csv       |   6 +
 .../e2e/snapshot_tables/_tool_zentao_accounts.csv  |   1 +
 .../e2e/snapshot_tables/_tool_zentao_bugs.csv      |  13 ++
 .../snapshot_tables/_tool_zentao_departments.csv   |   1 +
 .../snapshot_tables/_tool_zentao_executions.csv    |   2 +
 .../e2e/snapshot_tables/_tool_zentao_products.csv  |   2 +
 .../e2e/snapshot_tables/_tool_zentao_stories.csv   |   8 +
 .../e2e/snapshot_tables/_tool_zentao_tasks.csv     |   4 +
 .../e2e/snapshot_tables/board_issues_bug.csv       |   5 +
 .../e2e/snapshot_tables/board_issues_story.csv     |   8 +
 .../e2e/snapshot_tables/board_issues_task.csv      |   4 +
 .../e2e/snapshot_tables/boards_execution.csv       |   2 +
 .../zentao/e2e/snapshot_tables/boards_product.csv  |   2 +
 plugins/zentao/e2e/snapshot_tables/issues_bug.csv  |   5 +
 .../zentao/e2e/snapshot_tables/issues_story.csv    |   8 +
 plugins/zentao/e2e/snapshot_tables/issues_task.csv |   4 +
 plugins/zentao/e2e/snapshot_tables/teams.csv       |   1 +
 plugins/zentao/e2e/snapshot_tables/users.csv       |   1 +
 plugins/zentao/e2e/story_test.go                   |  69 +++++++
 plugins/zentao/e2e/task_test.go                    |  68 +++++++
 plugins/zentao/impl/impl.go                        | 142 +++++++++++++
 plugins/zentao/models/access_token.go              |  27 +++
 plugins/zentao/models/account.go                   |  37 ++++
 plugins/zentao/models/archived/account.go          |  37 ++++
 plugins/zentao/models/archived/bug.go              |  99 +++++++++
 plugins/zentao/models/archived/connection.go       |  70 +++++++
 plugins/zentao/models/archived/department.go       |  39 ++++
 plugins/zentao/models/archived/execution.go        |  89 ++++++++
 plugins/zentao/models/archived/product.go          |  62 ++++++
 plugins/zentao/models/archived/project.go          | 113 +++++++++++
 plugins/zentao/models/archived/story.go            |  89 ++++++++
 plugins/zentao/models/archived/task.go             |  97 +++++++++
 plugins/zentao/models/bug.go                       | 167 +++++++++++++++
 plugins/zentao/models/connection.go                |  51 +++++
 plugins/zentao/models/department.go                |  41 ++++
 plugins/zentao/models/execution.go                 | 225 +++++++++++++++++++++
 .../migrationscripts/20221121_add_init_tables.go   |  68 +++++++
 plugins/zentao/models/migrationscripts/register.go |  29 +++
 plugins/zentao/models/product.go                   | 106 ++++++++++
 plugins/zentao/models/project.go                   | 113 +++++++++++
 plugins/zentao/models/story.go                     | 144 +++++++++++++
 plugins/zentao/models/task.go                      | 166 +++++++++++++++
 plugins/zentao/tasks/account_collector.go          |  82 ++++++++
 plugins/zentao/tasks/account_convertor.go          |  91 +++++++++
 plugins/zentao/tasks/account_extractor.go          |  69 +++++++
 plugins/zentao/tasks/api_client.go                 |  95 +++++++++
 plugins/zentao/tasks/bug_collector.go              |  82 ++++++++
 plugins/zentao/tasks/bug_convertor.go              | 116 +++++++++++
 plugins/zentao/tasks/bug_extractor.go              | 138 +++++++++++++
 plugins/zentao/tasks/department_collector.go       |  82 ++++++++
 plugins/zentao/tasks/department_convertor.go       |  88 ++++++++
 plugins/zentao/tasks/department_extractor.go       |  69 +++++++
 plugins/zentao/tasks/execution_collector.go        |  81 ++++++++
 plugins/zentao/tasks/execution_convertor.go        |  93 +++++++++
 plugins/zentao/tasks/execution_extractor.go        | 127 ++++++++++++
 plugins/zentao/tasks/product_collector.go          |  80 ++++++++
 plugins/zentao/tasks/product_convertor.go          |  92 +++++++++
 plugins/zentao/tasks/product_extractor.go          | 101 +++++++++
 plugins/zentao/tasks/project_collector.go          |  79 ++++++++
 plugins/zentao/tasks/project_extractor.go          |  69 +++++++
 plugins/zentao/tasks/shared.go                     |  50 +++++
 plugins/zentao/tasks/story_collector.go            |  83 ++++++++
 plugins/zentao/tasks/story_convertor.go            | 117 +++++++++++
 plugins/zentao/tasks/story_extractor.go            | 125 ++++++++++++
 plugins/zentao/tasks/task_collector.go             |  82 ++++++++
 plugins/zentao/tasks/task_convertor.go             | 119 +++++++++++
 plugins/zentao/tasks/task_data.go                  |  62 ++++++
 plugins/zentao/tasks/task_extractor.go             | 138 +++++++++++++
 plugins/zentao/zentao.go                           |  47 +++++
 111 files changed, 5611 insertions(+), 144 deletions(-)

diff --git a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
index 22237cf1e..cefd7855c 100644
--- a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
+++ b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
@@ -25,6 +25,7 @@ import GithubSettings from '@/pages/configure/settings/github'
 import AzureSettings from '@/pages/configure/settings/azure'
 import BitbucketSettings from '@/pages/configure/settings/bitbucket'
 import GiteeSettings from '@/pages/configure/settings/gitee'
+import ZentaoSettings from '@/pages/configure/settings/zentao'
 
 // Transformation Higher-Order Component (HOC) Settings Loader
 const withTransformationSettings = (
@@ -70,6 +71,7 @@ const ProviderTransformationSettings = (props) => {
       [Providers.JIRA]: JiraSettings,
       [Providers.JENKINS]: JenkinsSettings,
       [Providers.TAPD]: TapdSettings,
+      [Providers.ZENTAO]: ZentaoSettings,
       [Providers.AZURE]: AzureSettings,
       [Providers.BITBUCKET]: BitbucketSettings,
       [Providers.GITEE]: GiteeSettings
diff --git a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
index be956819f..f00eae0d6 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -77,6 +77,7 @@ const DataTransformations = (props) => {
   const noTransformationsAvailable = useMemo(
     () =>
       [Providers.TAPD].includes(configuredConnection?.provider) ||
+      [Providers.ZENTAO].includes(configuredConnection?.provider) ||
       ([Providers.GITLAB].includes(configuredConnection?.provider) &&
         dataDomainsGroup[configuredConnection?.id].every(
           (e) => e.value !== DataDomainTypes.DEVOPS
@@ -86,6 +87,7 @@ const DataTransformations = (props) => {
       configuredConnection?.id,
       dataDomainsGroup,
       Providers.TAPD,
+      Providers.ZENTAO,
       Providers.GITLAB
     ]
   )
diff --git a/config-ui/src/components/menus/PipelineConfigsMenu.jsx b/config-ui/src/components/menus/PipelineConfigsMenu.jsx
index d45a4312e..930912b54 100644
--- a/config-ui/src/components/menus/PipelineConfigsMenu.jsx
+++ b/config-ui/src/components/menus/PipelineConfigsMenu.jsx
@@ -23,6 +23,7 @@ import { gitextractorConfig as sampleGitextractorPipelineConfig } from '@/data/p
 import { githubConfig as sampleGithubPipelineConfig } from '@/data/pipeline-config-samples/github'
 import { gitlabConfig as sampleGitlabPipelineConfig } from '@/data/pipeline-config-samples/gitlab'
 import { jiraConfig as sampleJiraPipelineConfig } from '@/data/pipeline-config-samples/jira'
+import { zentaoConfig as sampleZentaoPipelineConfig } from '@/data/pipeline-config-samples/zentao'
 import { jenkinsConfig as sampleJenkinsPipelineConfig } from '@/data/pipeline-config-samples/jenkins'
 import { feishuConfig as sampleFeishuPipelineConfig } from '@/data/pipeline-config-samples/feishu'
 import { dbtConfig as sampleDbtPipelineConfig } from '@/data/pipeline-config-samples/dbt'
@@ -137,6 +138,15 @@ const PipelineConfigsMenu = (props) => {
           )
         }
       />
+      <Menu.Item
+        icon='code'
+        text='Load Zentao Configuration'
+        onClick={() =>
+          setRawConfiguration(
+            JSON.stringify(sampleZentaoPipelineConfig, null, '  ')
+          )
+        }
+      />
     </Menu>
   )
 }
diff --git a/config-ui/src/components/pipelines/StageTaskName.jsx b/config-ui/src/components/pipelines/StageTaskName.jsx
index 763175ef2..cd640005f 100644
--- a/config-ui/src/components/pipelines/StageTaskName.jsx
+++ b/config-ui/src/components/pipelines/StageTaskName.jsx
@@ -122,6 +122,7 @@ const StageTaskName = (props) => {
                     <>{ProviderLabels.JENKINS}</>
                   )}
                   {task.plugin === Providers.TAPD && <>{ProviderLabels.TAPD}</>}
+                  {task.plugin === Providers.ZENTAO && <>{ProviderLabels.ZENTAO}</>}
                   {task.plugin === Providers.JIRA && (
                     <>Board ID {task.options.boardId}</>
                   )}
diff --git a/config-ui/src/data/Providers.js b/config-ui/src/data/Providers.js
index 1a4d4f979..eb4c63134 100644
--- a/config-ui/src/data/Providers.js
+++ b/config-ui/src/data/Providers.js
@@ -22,6 +22,7 @@ import { ReactComponent as JenkinsProviderIcon } from '@/images/integrations/jen
 import { ReactComponent as JiraProviderIcon } from '@/images/integrations/jira.svg'
 import { ReactComponent as GitHubProviderIcon } from '@/images/integrations/github.svg'
 import { ReactComponent as TapdProviderIcon } from '@/images/integrations/tapd.svg'
+import { ReactComponent as ZentaoProviderIcon } from '@/images/integrations/zentao.svg'
 import { ReactComponent as AzureProviderIcon } from '@/images/integrations/azure.svg'
 import { ReactComponent as BitbucketProviderIcon } from '@/images/integrations/bitbucket.svg'
 import { ReactComponent as GiteeProviderIcon } from '@/images/integrations/gitee.svg'
@@ -50,6 +51,7 @@ const Providers = {
   DBT: 'dbt',
   STARROCKS: 'starrocks',
   TAPD: 'tapd',
+  ZENTAO: 'zentao',
   AZURE: 'azure',
   BITBUCKET: 'bitbucket',
   GITEE: 'gitee',
@@ -76,7 +78,8 @@ const ProviderLabels = {
   AE: 'Analysis Engine (AE)',
   DBT: 'Data Build Tool (DBT)',
   STARROCKS: 'StarRocks',
-  TAPD: 'TAPD',
+  TAPD: 'Tapd',
+  ZENTAO: 'Zentao',
   AZURE: 'Azure CI',
   BITBUCKET: 'BitBucket',
   GITEE: 'Gitee',
@@ -146,6 +149,19 @@ const ProviderFormLabels = {
       </>
     )
   },
+  zentao: {
+    name: 'Connection Name',
+    endpoint: 'Endpoint URL',
+    proxy: 'Proxy URL',
+    username: 'Username',
+    password: 'Password',
+    rateLimitPerHour: (
+      <>
+        Rate Limit <sup>(per hour)</sup>
+        <RateLimitTooltip />
+      </>
+    )
+  },
   jira: {
     name: 'Connection Name',
     endpoint: 'Endpoint URL',
@@ -308,6 +324,14 @@ const ProviderFormPlaceholders = {
     password: 'eg. ************',
     rateLimitPerHour: '1000'
   },
+  zentao: {
+    name: 'eg. Zentao',
+    endpoint: 'URL eg. http://subdomain.domain:port/api.php/v1/',
+    proxy: 'eg. http://proxy.localhost:8080',
+    username: 'eg. devlake',
+    password: 'eg. ************',
+    rateLimitPerHour: '1000'
+  },
   jira: {
     name: 'eg. JIRA',
     endpoint: 'eg. https://your-domain.atlassian.net/rest/',
@@ -366,6 +390,9 @@ const ProviderIcons = {
   [Providers.TAPD]: (w, h) => (
     <TapdProviderIcon width={w || 24} height={h || 24} />
   ),
+  [Providers.ZENTAO]: (w, h) => (
+    <ZentaoProviderIcon width={w || 24} height={h || 24} />
+  ),
   [Providers.JIRA]: (w, h) => (
     <JiraProviderIcon width={w || 24} height={h || 24} />
   ),
diff --git a/config-ui/src/data/availablePlugins.js b/config-ui/src/data/availablePlugins.js
index 4433f79cb..dc5f79d2d 100644
--- a/config-ui/src/data/availablePlugins.js
+++ b/config-ui/src/data/availablePlugins.js
@@ -15,6 +15,6 @@
  * limitations under the License.
  *
  */
-const AVAILABLE_PLUGINS = ['gitlab', 'jira', 'jenkins', 'github', 'tapd']
+const AVAILABLE_PLUGINS = ['gitlab', 'jira', 'jenkins', 'github', 'tapd', 'zentao']
 
 module.exports = AVAILABLE_PLUGINS
diff --git a/config-ui/src/data/integrations.jsx b/config-ui/src/data/integrations.jsx
index 1a10f7aa0..b5f0165c3 100644
--- a/config-ui/src/data/integrations.jsx
+++ b/config-ui/src/data/integrations.jsx
@@ -30,6 +30,7 @@ import { ReactComponent as JenkinsProvider } from '@/images/integrations/jenkins
 import { ReactComponent as JiraProvider } from '@/images/integrations/jira.svg'
 import { ReactComponent as GitHubProvider } from '@/images/integrations/github.svg'
 import { ReactComponent as TapdProvider } from '@/images/integrations/tapd.svg'
+import { ReactComponent as ZentaoProvider } from '@/images/integrations/zentao.svg'
 import { ReactComponent as AzureProvider } from '@/images/integrations/azure.svg'
 import { ReactComponent as BitbucketProvider } from '@/images/integrations/bitbucket.svg'
 import { ReactComponent as GiteeProvider } from '@/images/integrations/gitee.svg'
@@ -102,6 +103,27 @@ const integrationsData = [
     // relocated to ProviderTransformationSettings since v0.12.0
     settings: {}
   },
+  {
+    id: Providers.ZENTAO,
+    type: ProviderTypes.INTEGRATION,
+    enabled: true,
+    multiConnection: true,
+    isBeta: true,
+    name: ProviderLabels.ZENTAO,
+    icon: (
+      <ZentaoProvider
+        className='providerIconSvg'
+        width='30'
+        height='30'
+        style={{ float: 'left', marginTop: '5px' }}
+      />
+    ),
+    iconDashboard: (
+      <ZentaoProvider className='providerIconSvg' width='40' height='40' />
+    ),
+    // relocated to ProviderTransformationSettings since v0.12.0
+    settings: {}
+  },
   {
     id: Providers.JIRA,
     type: ProviderTypes.INTEGRATION,
diff --git a/config-ui/src/data/availablePlugins.js b/config-ui/src/data/pipeline-config-samples/zentao.js
similarity index 79%
copy from config-ui/src/data/availablePlugins.js
copy to config-ui/src/data/pipeline-config-samples/zentao.js
index 4433f79cb..22590ee72 100644
--- a/config-ui/src/data/availablePlugins.js
+++ b/config-ui/src/data/pipeline-config-samples/zentao.js
@@ -15,6 +15,18 @@
  * limitations under the License.
  *
  */
-const AVAILABLE_PLUGINS = ['gitlab', 'jira', 'jenkins', 'github', 'tapd']
+const zentaoConfig = [
+  [
+    {
+      plugin: 'zentao',
+      options: {
+        connectionId: 1,
+        productId: 1,
+        projectId: 1,
+        executionId: 1,
+      }
+    }
+  ]
+]
 
-module.exports = AVAILABLE_PLUGINS
+export { zentaoConfig }
diff --git a/config-ui/src/hooks/useConnectionValidation.jsx b/config-ui/src/hooks/useConnectionValidation.jsx
index fbf109a67..5596c6634 100644
--- a/config-ui/src/hooks/useConnectionValidation.jsx
+++ b/config-ui/src/hooks/useConnectionValidation.jsx
@@ -96,6 +96,7 @@ function useConnectionValidation({
         }
         break
       case Providers.JIRA:
+      case Providers.ZENTAO:
       case Providers.JENKINS:
         if (!username || username.length <= 2) {
           errs.push('Username is required')
diff --git a/config-ui/src/hooks/useIntegrations.jsx b/config-ui/src/hooks/useIntegrations.jsx
index cbf202499..6b3e6645e 100644
--- a/config-ui/src/hooks/useIntegrations.jsx
+++ b/config-ui/src/hooks/useIntegrations.jsx
@@ -27,6 +27,7 @@ import GitHubGraphqlPlugin from '@/registry/plugins/github_graphql.json'
 import GitLabPlugin from '@/registry/plugins/gitlab.json'
 import JenkinsPlugin from '@/registry/plugins/jenkins.json'
 import TapdPlugin from '@/registry/plugins/tapd.json'
+import ZentaoPlugin from '@/registry/plugins/zentao.json'
 import AzurePlugin from '@/registry/plugins/azure.json'
 import BitbucketPlugin from '@/registry/plugins/bitbucket.json'
 import GiteePlugin from '@/registry/plugins/gitee.json'
@@ -51,6 +52,7 @@ function useIntegrations(
     GitLabPlugin,
     JenkinsPlugin,
     TapdPlugin,
+    ZentaoPlugin,
     AzurePlugin,
     BitbucketPlugin,
     GiteePlugin,
diff --git a/config-ui/src/images/integrations/zentao.svg b/config-ui/src/images/integrations/zentao.svg
new file mode 100644
index 000000000..dc195d067
--- /dev/null
+++ b/config-ui/src/images/integrations/zentao.svg
@@ -0,0 +1,4 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" height="1024" width="1024" viewBox="0 0 1024 1024">
+    <path d="M438.857143 932.571429c-193.828571 0-347.428571-157.257143-347.428572-347.428572 0-10.971429 7.314286-18.285714 18.285715-18.285714h329.142857c10.971429 0 18.285714 7.314286 18.285714 18.285714v329.142857c0 10.971429-7.314286 18.285714-18.285714 18.285715z m-307.2-329.142858c10.971429 153.6 135.314286 277.942857 288.914286 288.914286v-288.914286h-288.914286zM512 932.571429c-10.971429 0-18.285714-7.314286-18.285714-18.285715v-329.142857c0-10.971429 7.314286-18.285714 18.28571 [...]
+    <path d="M727.771429 365.714286c-3.657143 0-7.314286 0-10.971429-3.657143l-219.428571-175.542857 21.942857-29.257143 208.457143 168.228571 215.771428-168.228571 21.942857 29.257143-226.742857 175.542857c-3.657143 3.657143-7.314286 3.657143-10.971428 3.657143z"/>
+</svg>
\ No newline at end of file
diff --git a/config-ui/src/pages/blueprints/blueprint-settings.jsx b/config-ui/src/pages/blueprints/blueprint-settings.jsx
index 75ebd2ef0..da4ee703c 100644
--- a/config-ui/src/pages/blueprints/blueprint-settings.jsx
+++ b/config-ui/src/pages/blueprints/blueprint-settings.jsx
@@ -512,6 +512,7 @@ const BlueprintSettings = (props) => {
                 dataDomainsGroup[configuredConnection?.id]?.length > 0
               break
             case Providers.TAPD:
+            case Providers.ZENTAO:
               isValid = dataDomainsGroup[configuredConnection?.id]?.length > 0
               break
             default:
diff --git a/config-ui/src/pages/configure/connections/ConnectionForm.jsx b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
index 23b2b6ebc..b8120f241 100644
--- a/config-ui/src/pages/configure/connections/ConnectionForm.jsx
+++ b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
@@ -805,6 +805,7 @@ export default function ConnectionForm(props) {
           Providers.JIRA,
           Providers.JENKINS,
           Providers.TAPD,
+          Providers.ZENTAO,
           Providers.AZURE,
           Providers.BITBUCKET,
           Providers.GITEE
diff --git a/config-ui/src/pages/configure/settings/zentao.jsx b/config-ui/src/pages/configure/settings/zentao.jsx
new file mode 100644
index 000000000..96de85a1f
--- /dev/null
+++ b/config-ui/src/pages/configure/settings/zentao.jsx
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ *
+ */
+import React, { useEffect, useState } from 'react'
+import { useParams, useHistory } from 'react-router-dom'
+import { DataDomainTypes } from '@/data/DataDomains'
+
+import '@/styles/integration.scss'
+import '@/styles/connections.scss'
+
+export default function ZentaoSettings(props) {
+  const {
+    provider,
+    connection,
+    dataDomains = [],
+    onSettingsChange = () => {}
+  } = props
+  const history = useHistory()
+
+  const cancel = () => {
+    history.push(`/integrations/${provider.id}`)
+  }
+
+  return (
+    <>
+      <div className='headlineContainer'>
+        <h3 className='headline'>No Additional Settings</h3>
+        <p className='description'>
+          This integration doesn’t require any configuration. You can continue
+          to&nbsp;
+          <a href='#' style={{ textDecoration: 'underline' }} onClick={cancel}>
+            add other data connections
+          </a>
+          &nbsp; or trigger collection at the{' '}
+          <a href='#' style={{ textDecoration: 'underline' }} onClick={cancel}>
+            previous page
+          </a>
+          .
+        </p>
+      </div>
+    </>
+  )
+}
diff --git a/config-ui/src/registry/plugins/zentao.json b/config-ui/src/registry/plugins/zentao.json
new file mode 100644
index 000000000..b89a893fb
--- /dev/null
+++ b/config-ui/src/registry/plugins/zentao.json
@@ -0,0 +1,55 @@
+{
+  "id": "zentao",
+  "name": "ZENTAO",
+  "type": "integration",
+  "enabled": true,
+  "multiConnection": true,
+  "connectionLimit": 0,
+  "isBeta": true,
+  "isProvider": true,
+  "icon": "src/images/integrations/zentao.svg",
+  "private": false,
+  "connection": {
+    "authentication": "plain",
+    "fields": {
+      "name": { "enable": true, "required": true, "readonly": false },
+      "endpoint": { },
+      "proxy": { },
+      "username": { },
+      "password": { },
+      "rateLimitPerHour": { }
+    },
+    "labels": {
+      "name": "Connection Name",
+      "endpoint": "Endpoint URL",
+      "proxy": "Proxy URL",
+      "username": "Username",
+      "password": "Password",
+      "rateLimitPerHour": "Rate Limit (per hour)"
+    },
+    "placeholders": {
+      "name": "eg. Zentao",
+      "endpoint": "URL eg. http://subdomain.domain:port/api.php/v1/",
+      "proxy": "eg. http://proxy.localhost:8080",
+      "username": "eg. admin",
+      "password": "eg. ************",
+      "rateLimit": "1000"
+    },
+    "tooltips": {
+    }
+  },
+  "availableDataDomains": ["TICKET"],
+  "transformations": {
+    "scopes": {
+      "options": {
+      }
+    },
+    "default": {
+      "issueTypeRequirement": "",
+      "issueTypeBug": "",
+      "issueTypeIncident": "",
+      "productionPattern": "",
+      "deploymentPattern": ""
+    }
+  }
+}
\ No newline at end of file
diff --git a/plugins/dora/e2e/calculate_change_lead_time_test.go b/plugins/dora/e2e/calculate_change_lead_time_test.go
index 9667a5040..81c18107f 100644
--- a/plugins/dora/e2e/calculate_change_lead_time_test.go
+++ b/plugins/dora/e2e/calculate_change_lead_time_test.go
@@ -44,7 +44,7 @@ func TestCalculateCLTimeDataFlow(t *testing.T) {
 	dataflowTester.FlushTabler(&code.PullRequest{})
 
 	// import raw data table
-	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_tasks_changeleadtime.csv", &devops.CICDTask{})
+	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_tasks.csv", &devops.CICDTask{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/pull_requests.csv", &code.PullRequest{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/commits_diffs.csv", &code.CommitsDiff{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_pipeline_commits.csv", &devops.CiCDPipelineCommit{})
diff --git a/plugins/dora/e2e/connect_incident_to_deploy_test.go b/plugins/dora/e2e/connect_incident_to_deploy_test.go
index 4e508f5e8..0ed940a95 100644
--- a/plugins/dora/e2e/connect_incident_to_deploy_test.go
+++ b/plugins/dora/e2e/connect_incident_to_deploy_test.go
@@ -42,7 +42,7 @@ func TestConnectIncidentToDeploymentDataFlow(t *testing.T) {
 		},
 	}
 	// import raw data table
-	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_tasks_changeleadtime.csv", &devops.CICDTask{})
+	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_tasks.csv", &devops.CICDTask{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/project_mapping.csv", &crossdomain.ProjectMapping{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/board_issues.csv", &ticket.BoardIssue{})
 	dataflowTester.ImportCsvIntoTabler("./raw_tables/issues.csv", &ticket.Issue{})
diff --git a/plugins/dora/e2e/env_enricher_test.go b/plugins/dora/e2e/env_enricher_test.go
index d2aa98c90..520156a4a 100644
--- a/plugins/dora/e2e/env_enricher_test.go
+++ b/plugins/dora/e2e/env_enricher_test.go
@@ -18,6 +18,7 @@ limitations under the License.
 package e2e
 
 import (
+	"github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
 	"testing"
 
 	"github.com/apache/incubator-devlake/helpers/e2ehelper"
@@ -33,7 +34,7 @@ func TestEnrichEnvDataFlow(t *testing.T) {
 
 	taskData := &tasks.DoraTaskData{
 		Options: &tasks.DoraOptions{
-			RepoId: "github:GithubRepo:1:384111310",
+			ProjectName: "project1",
 			TransformationRules: tasks.TransformationRules{
 				ProductionPattern: "(?i)deploy",
 				StagingPattern:    "(?i)stag",
@@ -42,35 +43,14 @@ func TestEnrichEnvDataFlow(t *testing.T) {
 		},
 	}
 
-	dataflowTester.FlushTabler(&devops.CICDTask{})
-
 	// import raw data table
-	dataflowTester.ImportCsvIntoTabler("./raw_tables/lake_cicd_pipeline_commits.csv", &devops.CiCDPipelineCommit{})
-	dataflowTester.ImportCsvIntoTabler("./raw_tables/lake_cicd_tasks.csv", &devops.CICDTask{})
+	dataflowTester.ImportCsvIntoTabler("./raw_tables/project_mapping.csv", &crossdomain.ProjectMapping{})
+	dataflowTester.ImportCsvIntoTabler("./raw_tables/cicd_tasks_for_enrich_env.csv", &devops.CICDTask{})
 
 	// verify enrich with repoId
 	dataflowTester.Subtask(tasks.EnrichTaskEnvMeta, taskData)
 	dataflowTester.VerifyTableWithOptions(&devops.CICDTask{}, e2ehelper.TableOptions{
-		CSVRelPath:  "./snapshot_tables/lake_cicd_tasks.csv",
-		IgnoreTypes: []interface{}{common.NoPKModel{}},
-	})
-
-	// verify enrich with prefix
-	dataflowTester.FlushTabler(&devops.CICDTask{})
-	dataflowTester.ImportCsvIntoTabler("./raw_tables/lake_cicd_tasks_for_prefix.csv", &devops.CICDTask{})
-	taskDataPrefix := &tasks.DoraTaskData{
-		Options: &tasks.DoraOptions{
-			TransformationRules: tasks.TransformationRules{
-				ProductionPattern: "(?i)deploy",
-				StagingPattern:    "(?i)stag",
-				TestingPattern:    "(?i)test",
-			},
-			Prefix: "jenkins",
-		},
-	}
-	dataflowTester.Subtask(tasks.EnrichTaskEnvMeta, taskDataPrefix)
-	dataflowTester.VerifyTableWithOptions(&devops.CICDTask{}, e2ehelper.TableOptions{
-		CSVRelPath:  "./snapshot_tables/lake_cicd_tasks_prefix.csv",
+		CSVRelPath:  "./snapshot_tables/enrich_cicd_tasks.csv",
 		IgnoreTypes: []interface{}{common.NoPKModel{}},
 	})
 }
diff --git a/plugins/dora/e2e/raw_tables/cicd_tasks_changeleadtime.csv b/plugins/dora/e2e/raw_tables/cicd_tasks.csv
similarity index 100%
rename from plugins/dora/e2e/raw_tables/cicd_tasks_changeleadtime.csv
rename to plugins/dora/e2e/raw_tables/cicd_tasks.csv
diff --git a/plugins/dora/e2e/raw_tables/cicd_tasks_for_enrich_env.csv b/plugins/dora/e2e/raw_tables/cicd_tasks_for_enrich_env.csv
new file mode 100644
index 000000000..44c34724d
--- /dev/null
+++ b/plugins/dora/e2e/raw_tables/cicd_tasks_for_enrich_env.csv
@@ -0,0 +1,19 @@
+id,name,pipeline_id,status,result,type,environment,duration_sec,started_date,finished_date,cicd_scope_id
+task10,deployxIG,pipeline110,DONE,SUCCESS,DEPLOYMENT,,,2022-07-19 22:06:28,2022-11-13 22:37:21,cicd1
+task11,deploya,pipeline111,DONE,SUCCESS,DEPLOYMENT,,,2022-08-06 14:06:50,2022-11-13 00:07:21,cicd1
+task12,deployc,pipeline112,DONE,SUCCESS,DEPLOYMENT,,,2022-08-23 17:44:05,2022-11-02 07:21:09,cicd2
+task13,deploy,pipeline113,DONE,SUCCESS,DEPLOYMENT,,,2022-08-30 23:45:29,2022-11-28 00:46:47,cicd1
+task14,deployp0;,pipeline114,DONE,SUCCESS,DEPLOYMENT,,,2022-09-07 02:49:26,2022-11-16 20:34:01,cicd1
+task15,deployY{,pipeline115,DONE,SUCCESS,DEPLOYMENT,,,2022-09-27 01:07:50,2022-11-19 07:17:33,cicd2
+task16,deploy8',pipeline116,DONE,SUCCESS,DEPLOYMENT,,,2022-09-30 21:05:38,2022-11-08 07:56:03,cicd1
+task17,deployKd%,pipeline117,IN_PROGRESS,,DEPLOYMENT,,,2022-10-09 06:42:02,,cicd1
+task19,deploy1,pipeline119,DONE,FAILURE,DEPLOYMENT,,,2022-10-24 18:41:04,2022-11-24 04:26:48,cicd1
+task21,deploy^^.,pipeline39,DONE,FAILURE,DEPLOYMENT,,,2004-01-10 03:31:11,2022-11-28 20:41:59,cicd1
+task22,deploy,pipeline35,DONE,SUCCESS,DEPLOYMENT,,,2000-10-25 09:57:28,2022-11-28 21:24:02,cicd1
+task23,deploy,pipeline36,IN_PROGRESS,,DEPLOYMENT,,,2005-02-07 11:03:27,2022-11-05 18:18:03,cicd1
+task24,deploym,pipeline12,IN_PROGRESS,,DEPLOYMENT,,,2015-08-11 19:58:06,2022-11-01 22:31:56,cicd1
+task25,deploy$p<,pipeline26,DONE,FAILURE,,,,2014-02-06 13:42:43,2022-11-30 08:01:38,cicd3
+task26,deployb>@,pipeline20,IN_PROGRESS,,DEPLOYMENT,,,2016-08-26 05:41:49,2022-11-15 07:31:46,cicd2
+task27,deployKfn,pipeline37,DONE,SUCCESS,DEPLOYMENT,,,2003-12-13 23:19:14,2022-11-11 18:29:31,cicd2
+task28,deployl?,pipeline29,IN_PROGRESS,,DEPLOYMENT,,,2007-01-19 01:13:39,2022-11-24 05:39:46,cicd3
+task29,deployUb,pipeline27,IN_PROGRESS,,DEPLOYMENT,,,2006-05-20 18:17:13,2022-11-28 10:13:51,cicd2
diff --git a/plugins/dora/e2e/raw_tables/lake_cicd_tasks.csv b/plugins/dora/e2e/raw_tables/lake_cicd_tasks.csv
deleted file mode 100644
index 5d0b73992..000000000
--- a/plugins/dora/e2e/raw_tables/lake_cicd_tasks.csv
+++ /dev/null
@@ -1,25 +0,0 @@
-id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,name,pipeline_id,result,status,type,duration_sec,started_date,finished_date,environment
-github:GithubJob:1:8269045794,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,2,"",deploy,github:GithubRun:1:3022052551,FAILURE,DONE,"DEPLOYMENT",72,2022-09-09 11:27:13,2022-09-09 11:28:25,""
-github:GithubJob:1:8269045795,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,3,"",test (ubuntu-latest),github:GithubRun:1:3022052557,SUCCESS,DONE,"DEPLOYMENT",191,2022-09-09 11:27:42,2022-09-09 11:30:53,""
-github:GithubJob:1:8269045819,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,1,"",commit-msg,github:GithubRun:1:3022052555,SUCCESS,DONE,"DEPLOYMENT",8,2022-09-09 11:26:38,2022-09-09 11:26:46,""
-github:GithubJob:1:8269045928,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,4,"",e2e-mysql,github:GithubRun:1:3022052558,SUCCESS,DONE,"",200,2022-09-09 11:28:00,2022-09-09 11:31:20,""
-github:GithubJob:1:8269046116,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,6,"",check Apache license header,github:GithubRun:1:3022052589,SUCCESS,DONE,"",57,2022-09-09 11:28:34,2022-09-09 11:29:31,""
-github:GithubJob:1:8269415129,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,5,"",deploy,github:GithubRun:1:3022190519,FAILURE,DONE,"",84,2022-09-09 11:51:21,2022-09-09 11:52:45,""
-github:GithubJob:1:8269415130,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,9,"",test (ubuntu-latest),github:GithubRun:1:3022190524,SUCCESS,DONE,"",182,2022-09-09 11:50:31,2022-09-09 11:53:33,""
-github:GithubJob:1:8269415142,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,10,"",check Apache license header,github:GithubRun:1:3022190525,SUCCESS,DONE,"",63,2022-09-09 11:52:06,2022-09-09 11:53:09,""
-github:GithubJob:1:8269415165,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,8,"",commit-msg,github:GithubRun:1:3022190521,SUCCESS,DONE,"",10,2022-09-09 11:52:46,2022-09-09 11:52:56,""
-github:GithubJob:1:8269415315,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,7,"",deployment,github:GithubRun:1:3022190520,SUCCESS,DONE,"",242,2022-09-09 11:53:45,2022-09-09 11:57:47,""
-github:GithubJob:1:8269598312,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,14,"",e2e-mysql,github:GithubRun:1:3022255519,SUCCESS,DONE,"",828,2022-09-09 12:05:04,2022-09-09 12:18:52,""
-github:GithubJob:1:8269598316,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,11,"",test (ubuntu-latest),github:GithubRun:1:3022255511,SUCCESS,DONE,"",1016,2022-09-09 12:05:49,2022-09-09 12:22:45,""
-github:GithubJob:1:8269598318,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,12,"",lint,github:GithubRun:1:3022255516,FAILURE,DONE,"",62,2022-09-09 12:02:28,2022-09-09 12:03:30,""
-github:GithubJob:1:8269598325,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,13,"",check Apache license header,github:GithubRun:1:3022255517,SUCCESS,DONE,"",52,2022-09-09 12:03:51,2022-09-09 12:04:43,""
-github:GithubJob:1:8269598469,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,15,"",commit-msg,github:GithubRun:1:3022255524,FAILURE,DONE,"",41,2022-09-09 12:06:55,2022-09-09 12:07:36,""
-github:GithubJob:1:8269729324,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,16,"",e2e-mysql,github:GithubRun:1:3022300995,SUCCESS,DONE,"",1007,2022-09-09 12:10:08,2022-09-09 12:26:55,""
-github:GithubJob:1:8269729327,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,18,"",xx_deploy_xx,github:GithubRun:1:3022301001,FAILURE,DONE,"",82,2022-09-09 12:11:22,2022-09-09 12:12:44,""
-github:GithubJob:1:8269729550,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,19,"",build_deployment,github:GithubRun:1:3022301007,SUCCESS,DONE,"",6,2022-09-09 12:12:02,2022-09-09 12:12:08,""
-github:GithubJob:1:8269729553,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,17,"",build_deploy,github:GithubRun:1:3022300997,SUCCESS,DONE,"",57,2022-09-09 12:12:52,2022-09-09 12:13:49,""
-github:GithubJob:1:8269729821,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,20,"",deployment,github:GithubRun:1:3022301033,SUCCESS,DONE,"",991,2022-09-09 12:13:58,2022-09-09 12:30:29,""
-github:GithubJob:1:8269872730,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,21,"",deploy,github:GithubRun:1:3022348786,SUCCESS,DONE,"",1006,2022-09-09 12:21:30,2022-09-09 12:38:16,""
-github:GithubJob:1:7773010057,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,17,"",build_deploy,github:GithubRun:1:3022300997,SUCCESS,DONE,"",57,2022-09-09 12:12:52,2022-09-09 12:13:49,""
-github:GithubJob:1:7773010060,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,20,"",deployment,github:GithubRun:1:3022301033,SUCCESS,DONE,"",991,2022-09-09 12:13:58,2022-09-09 12:30:29,""
-github:GithubJob:1:7773010062,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,21,"",deploy,github:GithubRun:1:3022348786,SUCCESS,DONE,"",1006,2022-09-09 12:21:30,2022-09-09 12:38:16,""
\ No newline at end of file
diff --git a/plugins/dora/e2e/raw_tables/lake_cicd_tasks_for_prefix.csv b/plugins/dora/e2e/raw_tables/lake_cicd_tasks_for_prefix.csv
deleted file mode 100644
index bd2411157..000000000
--- a/plugins/dora/e2e/raw_tables/lake_cicd_tasks_for_prefix.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-id,created_at,updated_at,_raw_data_params,_raw_data_table,_raw_data_id,_raw_data_remark,name,pipeline_id,result,status,type,duration_sec,started_date,finished_date,environment
-jenkins:GithubJob:1:8269872730,2022-09-15 03:20:03.867,2022-09-15 03:20:03.867,"{""ConnectionId"":1,""Owner"":""apache"",""Repo"":""incubator-devlake""}",_raw_github_api_jobs,21,"",deploy,jenkins:GithubRun:1:3022348786,SUCCESS,DONE,"",1006,2022-09-09 12:21:30,2022-09-09 12:38:16,""
diff --git a/plugins/dora/e2e/snapshot_tables/enrich_cicd_tasks.csv b/plugins/dora/e2e/snapshot_tables/enrich_cicd_tasks.csv
new file mode 100644
index 000000000..3c3d03fcb
--- /dev/null
+++ b/plugins/dora/e2e/snapshot_tables/enrich_cicd_tasks.csv
@@ -0,0 +1,19 @@
+id,name,pipeline_id,result,status,type,environment,duration_sec,started_date,finished_date,cicd_scope_id
+task10,deployxIG,pipeline110,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-07-19T22:06:28.000+00:00,2022-11-13T22:37:21.000+00:00,cicd1
+task11,deploya,pipeline111,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-08-06T14:06:50.000+00:00,2022-11-13T00:07:21.000+00:00,cicd1
+task12,deployc,pipeline112,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-08-23T17:44:05.000+00:00,2022-11-02T07:21:09.000+00:00,cicd2
+task13,deploy,pipeline113,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-08-30T23:45:29.000+00:00,2022-11-28T00:46:47.000+00:00,cicd1
+task14,deployp0;,pipeline114,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-09-07T02:49:26.000+00:00,2022-11-16T20:34:01.000+00:00,cicd1
+task15,deployY{,pipeline115,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-09-27T01:07:50.000+00:00,2022-11-19T07:17:33.000+00:00,cicd2
+task16,deploy8',pipeline116,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2022-09-30T21:05:38.000+00:00,2022-11-08T07:56:03.000+00:00,cicd1
+task17,deployKd%,pipeline117,,IN_PROGRESS,DEPLOYMENT,PRODUCTION,0,2022-10-09T06:42:02.000+00:00,,cicd1
+task19,deploy1,pipeline119,FAILURE,DONE,DEPLOYMENT,PRODUCTION,0,2022-10-24T18:41:04.000+00:00,2022-11-24T04:26:48.000+00:00,cicd1
+task21,deploy^^.,pipeline39,FAILURE,DONE,DEPLOYMENT,PRODUCTION,0,2004-01-10T03:31:11.000+00:00,2022-11-28T20:41:59.000+00:00,cicd1
+task22,deploy,pipeline35,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2000-10-25T09:57:28.000+00:00,2022-11-28T21:24:02.000+00:00,cicd1
+task23,deploy,pipeline36,,IN_PROGRESS,DEPLOYMENT,PRODUCTION,0,2005-02-07T11:03:27.000+00:00,2022-11-05T18:18:03.000+00:00,cicd1
+task24,deploym,pipeline12,,IN_PROGRESS,DEPLOYMENT,PRODUCTION,0,2015-08-11T19:58:06.000+00:00,2022-11-01T22:31:56.000+00:00,cicd1
+task25,deploy$p<,pipeline26,FAILURE,DONE,,,,2014-02-06T13:42:43.000+00:00,2022-11-30T08:01:38.000+00:00,cicd3
+task26,deployb>@,pipeline20,,IN_PROGRESS,DEPLOYMENT,PRODUCTION,0,2016-08-26T05:41:49.000+00:00,2022-11-15T07:31:46.000+00:00,cicd2
+task27,deployKfn,pipeline37,SUCCESS,DONE,DEPLOYMENT,PRODUCTION,0,2003-12-13T23:19:14.000+00:00,2022-11-11T18:29:31.000+00:00,cicd2
+task28,deployl?,pipeline29,,IN_PROGRESS,DEPLOYMENT,,,2007-01-19T01:13:39.000+00:00,2022-11-24T05:39:46.000+00:00,cicd3
+task29,deployUb,pipeline27,,IN_PROGRESS,DEPLOYMENT,PRODUCTION,0,2006-05-20T18:17:13.000+00:00,2022-11-28T10:13:51.000+00:00,cicd2
diff --git a/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks.csv b/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks.csv
deleted file mode 100644
index f444b0483..000000000
--- a/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks.csv
+++ /dev/null
@@ -1,25 +0,0 @@
-id,name,pipeline_id,result,status,type,environment,duration_sec,started_date,finished_date,cicd_scope_id
-github:GithubJob:1:7773010057,build_deploy,github:GithubRun:1:3022300997,SUCCESS,DONE,,PRODUCTION,57,2022-09-09T12:12:52.000+00:00,2022-09-09T12:13:49.000+00:00,
-github:GithubJob:1:7773010060,deployment,github:GithubRun:1:3022301033,SUCCESS,DONE,,PRODUCTION,991,2022-09-09T12:13:58.000+00:00,2022-09-09T12:30:29.000+00:00,
-github:GithubJob:1:7773010062,deploy,github:GithubRun:1:3022348786,SUCCESS,DONE,,PRODUCTION,1006,2022-09-09T12:21:30.000+00:00,2022-09-09T12:38:16.000+00:00,
-github:GithubJob:1:8269045794,deploy,github:GithubRun:1:3022052551,FAILURE,DONE,DEPLOYMENT,PRODUCTION,72,2022-09-09T11:27:13.000+00:00,2022-09-09T11:28:25.000+00:00,
-github:GithubJob:1:8269045795,test (ubuntu-latest),github:GithubRun:1:3022052557,SUCCESS,DONE,DEPLOYMENT,,191,2022-09-09T11:27:42.000+00:00,2022-09-09T11:30:53.000+00:00,
-github:GithubJob:1:8269045819,commit-msg,github:GithubRun:1:3022052555,SUCCESS,DONE,DEPLOYMENT,,8,2022-09-09T11:26:38.000+00:00,2022-09-09T11:26:46.000+00:00,
-github:GithubJob:1:8269045928,e2e-mysql,github:GithubRun:1:3022052558,SUCCESS,DONE,,,200,2022-09-09T11:28:00.000+00:00,2022-09-09T11:31:20.000+00:00,
-github:GithubJob:1:8269046116,check Apache license header,github:GithubRun:1:3022052589,SUCCESS,DONE,,,57,2022-09-09T11:28:34.000+00:00,2022-09-09T11:29:31.000+00:00,
-github:GithubJob:1:8269415129,deploy,github:GithubRun:1:3022190519,FAILURE,DONE,,PRODUCTION,84,2022-09-09T11:51:21.000+00:00,2022-09-09T11:52:45.000+00:00,
-github:GithubJob:1:8269415130,test (ubuntu-latest),github:GithubRun:1:3022190524,SUCCESS,DONE,,,182,2022-09-09T11:50:31.000+00:00,2022-09-09T11:53:33.000+00:00,
-github:GithubJob:1:8269415142,check Apache license header,github:GithubRun:1:3022190525,SUCCESS,DONE,,,63,2022-09-09T11:52:06.000+00:00,2022-09-09T11:53:09.000+00:00,
-github:GithubJob:1:8269415165,commit-msg,github:GithubRun:1:3022190521,SUCCESS,DONE,,,10,2022-09-09T11:52:46.000+00:00,2022-09-09T11:52:56.000+00:00,
-github:GithubJob:1:8269415315,deployment,github:GithubRun:1:3022190520,SUCCESS,DONE,,PRODUCTION,242,2022-09-09T11:53:45.000+00:00,2022-09-09T11:57:47.000+00:00,
-github:GithubJob:1:8269598312,e2e-mysql,github:GithubRun:1:3022255519,SUCCESS,DONE,,,828,2022-09-09T12:05:04.000+00:00,2022-09-09T12:18:52.000+00:00,
-github:GithubJob:1:8269598316,test (ubuntu-latest),github:GithubRun:1:3022255511,SUCCESS,DONE,,,1016,2022-09-09T12:05:49.000+00:00,2022-09-09T12:22:45.000+00:00,
-github:GithubJob:1:8269598318,lint,github:GithubRun:1:3022255516,FAILURE,DONE,,,62,2022-09-09T12:02:28.000+00:00,2022-09-09T12:03:30.000+00:00,
-github:GithubJob:1:8269598325,check Apache license header,github:GithubRun:1:3022255517,SUCCESS,DONE,,,52,2022-09-09T12:03:51.000+00:00,2022-09-09T12:04:43.000+00:00,
-github:GithubJob:1:8269598469,commit-msg,github:GithubRun:1:3022255524,FAILURE,DONE,,,41,2022-09-09T12:06:55.000+00:00,2022-09-09T12:07:36.000+00:00,
-github:GithubJob:1:8269729324,e2e-mysql,github:GithubRun:1:3022300995,SUCCESS,DONE,,,1007,2022-09-09T12:10:08.000+00:00,2022-09-09T12:26:55.000+00:00,
-github:GithubJob:1:8269729327,xx_deploy_xx,github:GithubRun:1:3022301001,FAILURE,DONE,,PRODUCTION,82,2022-09-09T12:11:22.000+00:00,2022-09-09T12:12:44.000+00:00,
-github:GithubJob:1:8269729550,build_deployment,github:GithubRun:1:3022301007,SUCCESS,DONE,,PRODUCTION,6,2022-09-09T12:12:02.000+00:00,2022-09-09T12:12:08.000+00:00,
-github:GithubJob:1:8269729553,build_deploy,github:GithubRun:1:3022300997,SUCCESS,DONE,,PRODUCTION,57,2022-09-09T12:12:52.000+00:00,2022-09-09T12:13:49.000+00:00,
-github:GithubJob:1:8269729821,deployment,github:GithubRun:1:3022301033,SUCCESS,DONE,,PRODUCTION,991,2022-09-09T12:13:58.000+00:00,2022-09-09T12:30:29.000+00:00,
-github:GithubJob:1:8269872730,deploy,github:GithubRun:1:3022348786,SUCCESS,DONE,,PRODUCTION,1006,2022-09-09T12:21:30.000+00:00,2022-09-09T12:38:16.000+00:00,
diff --git a/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks_prefix.csv b/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks_prefix.csv
deleted file mode 100644
index deb5f3c8f..000000000
--- a/plugins/dora/e2e/snapshot_tables/lake_cicd_tasks_prefix.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-id,name,pipeline_id,result,status,type,environment,duration_sec,started_date,finished_date,cicd_scope_id
-jenkins:GithubJob:1:8269872730,deploy,jenkins:GithubRun:1:3022348786,SUCCESS,DONE,,PRODUCTION,1006,2022-09-09T12:21:30.000+00:00,2022-09-09T12:38:16.000+00:00,
diff --git a/plugins/dora/tasks/cicd_task_env_enricher.go b/plugins/dora/tasks/cicd_task_env_enricher.go
index 18400aead..5e1c424e9 100644
--- a/plugins/dora/tasks/cicd_task_env_enricher.go
+++ b/plugins/dora/tasks/cicd_task_env_enricher.go
@@ -18,7 +18,6 @@ limitations under the License.
 package tasks
 
 import (
-	"fmt"
 	"reflect"
 	"regexp"
 
@@ -40,51 +39,30 @@ var EnrichTaskEnvMeta = core.SubTaskMeta{
 func EnrichTasksEnv(taskCtx core.SubTaskContext) (err errors.Error) {
 	db := taskCtx.GetDal()
 	data := taskCtx.GetData().(*DoraTaskData)
-	repoId := data.Options.RepoId
+	projectName := data.Options.ProjectName
 
 	productionNamePattern := data.Options.ProductionPattern
-	// TODO: STAGE 2
-	// stagingNamePattern := data.Options.StagingPattern
-	// testingNamePattern := data.Options.TestingPattern
-	prefix := data.Options.Prefix
-
-	productionNameRegexp, errRegexp := regexp.Compile(productionNamePattern)
-	if errRegexp != nil {
-		return errors.Default.Wrap(errRegexp, "Regexp compile productionPattern failed")
+	productionNameRegexp, err := errors.Convert01(regexp.Compile(productionNamePattern))
+	if err != nil {
+		return err
 	}
-	// TODO: STAGE 2
-	// stagingNameRegexp, errRegexp := regexp.Compile(stagingNamePattern)
-	// if errRegexp != nil {
-	// 	return errors.Default.Wrap(errRegexp, "Regexp compile stagingPattern failed")
-	// }
-	// testingNameRegexp, errRegexp := regexp.Compile(testingNamePattern)
-	// if errRegexp != nil {
-	// 	return errors.Default.Wrap(errRegexp, "Regexp compile testingPattern failed")
-	// }
 
-	var cursor dal.Rows
-	if len(prefix) == 0 {
-		cursor, err = db.Cursor(
-			dal.From(&devops.CICDTask{}),
-			dal.Join("left join cicd_pipeline_commits cpr on cicd_tasks.pipeline_id = cpr.pipeline_id"),
-			dal.Where("status=? and repo_id=?", devops.DONE, repoId))
-	} else {
-		likeString := fmt.Sprintf(`%s:%s`, prefix, "%")
-		cursor, err = db.Cursor(
-			dal.From(&devops.CICDTask{}),
-			dal.Where("status=? and id like ? ", devops.DONE, likeString))
-	}
+	cursor, err := db.Cursor(
+		dal.From(`cicd_tasks ct`),
+		dal.Join("inner join project_mapping pm on pm.row_id = ct.cicd_scope_id and pm.table = ?", "cicd_scopes"),
+		dal.Where(`pm.project_name = ?`, projectName),
+	)
+
 	if err != nil {
 		return err
 	}
 
 	defer cursor.Close()
-
 	converter, err := helper.NewDataConverter(helper.DataConverterArgs{
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
-			Ctx:    taskCtx,
+			Ctx: taskCtx,
 			Params: DoraApiParams{
-				// TODO
+				ProjectName: projectName,
 			},
 			Table: "cicd_tasks",
 		},
@@ -93,36 +71,14 @@ func EnrichTasksEnv(taskCtx core.SubTaskContext) (err errors.Error) {
 		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
 			cicdTask := inputRow.(*devops.CICDTask)
 			results := make([]interface{}, 0, 1)
-			var EnvironmentVar string
 			if productionNamePattern == "" {
-				EnvironmentVar = devops.PRODUCTION
+				cicdTask.Environment = devops.PRODUCTION
 			} else {
 				if productEnv := productionNameRegexp.FindString(cicdTask.Name); productEnv != "" {
-					EnvironmentVar = devops.PRODUCTION
+					cicdTask.Environment = devops.PRODUCTION
 				}
 			}
-
-			// TODO: STAGE 2
-			// if stagingEnv := stagingNameRegexp.FindString(cicdTask.Name); stagingEnv != "" {
-			// 	EnvironmentVar = devops.STAGING
-			// }
-			// if testingEnv := testingNameRegexp.FindString(cicdTask.Name); testingEnv != "" {
-			// 	EnvironmentVar = devops.TESTING
-			// }
-
-			cicdPipelineFilter := &devops.CICDTask{
-				DomainEntity: cicdTask.DomainEntity,
-				PipelineId:   cicdTask.PipelineId,
-				Name:         cicdTask.Name,
-				Type:         cicdTask.Type,
-				Result:       cicdTask.Result,
-				Status:       cicdTask.Status,
-				DurationSec:  cicdTask.DurationSec,
-				StartedDate:  cicdTask.StartedDate,
-				FinishedDate: cicdTask.FinishedDate,
-				Environment:  EnvironmentVar,
-			}
-			results = append(results, cicdPipelineFilter)
+			results = append(results, cicdTask)
 			return results, nil
 		},
 	})
diff --git a/plugins/helper/iso8601time.go b/plugins/helper/iso8601time.go
index 8d4092626..564d7f739 100644
--- a/plugins/helper/iso8601time.go
+++ b/plugins/helper/iso8601time.go
@@ -18,6 +18,7 @@ limitations under the License.
 package helper
 
 import (
+	"database/sql/driver"
 	"fmt"
 	"regexp"
 	"strings"
@@ -63,6 +64,14 @@ func init() {
 			Matcher: regexp.MustCompile(`[+-][\d]{2}:[\d]{2}$`),
 			Format:  "2006-01-02T15:04:05-07:00",
 		},
+		{
+			Matcher: regexp.MustCompile(`[\d]{4}-[\d]{2}-[\d]{2} [\d]{2}:[\d]{2}:[\d]{2}$`),
+			Format:  "2006-01-02 15:04:05",
+		},
+		{
+			Matcher: regexp.MustCompile(`[+-][\d]{2}-[\d]{2}$`),
+			Format:  "2006-01-02",
+		},
 	}
 }
 
@@ -91,6 +100,9 @@ func (jt *Iso8601Time) UnmarshalJSON(b []byte) error {
 	if timeString == "null" {
 		return nil
 	}
+	if strings.Contains(timeString, "0000-00-00") {
+		return nil
+	}
 	timeString = strings.Trim(timeString, `"`)
 	t, err := ConvertStringToTime(timeString)
 	if err != nil {
@@ -131,3 +143,29 @@ func Iso8601TimeToTime(iso8601Time *Iso8601Time) *time.Time {
 	t := iso8601Time.ToTime()
 	return &t
 }
+
+// Value FIXME ...
+func (jt *Iso8601Time) Value() (driver.Value, error) {
+	if jt == nil {
+		return nil, nil
+	}
+	var zeroTime time.Time
+	t := jt.time
+	if t.UnixNano() == zeroTime.UnixNano() {
+		return nil, nil
+	}
+	return t, nil
+}
+
+// Scan FIXME ...
+func (jt *Iso8601Time) Scan(v interface{}) error {
+	value, ok := v.(time.Time)
+	if ok {
+		*jt = Iso8601Time{
+			time:   value,
+			format: time.RFC3339,
+		}
+		return nil
+	}
+	return fmt.Errorf("can not convert %v to timestamp", v)
+}
diff --git a/plugins/zentao/api/blueprint.go b/plugins/zentao/api/blueprint.go
new file mode 100644
index 000000000..942de5b69
--- /dev/null
+++ b/plugins/zentao/api/blueprint.go
@@ -0,0 +1,70 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func MakePipelinePlan(subtaskMetas []core.SubTaskMeta, connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, errors.Error) {
+	var err error
+	plan := make(core.PipelinePlan, len(scope))
+	for i, scopeElem := range scope {
+		taskOptions := make(map[string]interface{})
+		err = json.Unmarshal(scopeElem.Options, &taskOptions)
+		if err != nil {
+			return nil, errors.Default.WrapRaw(err)
+		}
+		taskOptions["connectionId"] = connectionId
+
+		//TODO Add transformation rules to task options
+
+		/*
+		   var transformationRules tasks.TransformationRules
+		   if len(scopeElem.Transformation) > 0 {
+		       err = json.Unmarshal(scopeElem.Transformation, &transformationRules)
+		       if err != nil {
+		           return nil, err
+		       }
+		   }
+		*/
+		//taskOptions["transformationRules"] = transformationRules
+		_, err := tasks.DecodeAndValidateTaskOptions(taskOptions)
+		if err != nil {
+			return nil, errors.Default.WrapRaw(err)
+		}
+		// subtasks
+		subtasks, err := helper.MakePipelinePlanSubtasks(subtaskMetas, scopeElem.Entities)
+		if err != nil {
+			return nil, errors.Default.WrapRaw(err)
+		}
+		plan[i] = core.PipelineStage{
+			{
+				Plugin:   "zentao",
+				Subtasks: subtasks,
+				Options:  taskOptions,
+			},
+		}
+	}
+	return plan, nil
+}
diff --git a/plugins/zentao/api/connection.go b/plugins/zentao/api/connection.go
new file mode 100644
index 000000000..93244c5e1
--- /dev/null
+++ b/plugins/zentao/api/connection.go
@@ -0,0 +1,148 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"context"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/mitchellh/mapstructure"
+	"net/http"
+)
+
+// TODO Please modify the following code to fit your needs
+func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	// process input
+	var params models.TestConnectionRequest
+	err := mapstructure.Decode(input.Body, &params)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "could not decode request parameters")
+	}
+	err = vld.Struct(params)
+	if err != nil {
+		return nil, errors.BadInput.Wrap(err, "could not validate request parameters")
+	}
+
+	authApiClient, err := helper.NewApiClient(context.TODO(), params.Endpoint, nil, 0, params.Proxy, basicRes)
+	if err != nil {
+		return nil, errors.Default.WrapRaw(err)
+	}
+
+	// request for access token
+	tokenReqBody := &models.ApiAccessTokenRequest{
+		Account:  params.Username,
+		Password: params.Password,
+	}
+	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
+	if err != nil {
+		return nil, errors.Default.WrapRaw(err)
+	}
+	tokenResBody := &models.ApiAccessTokenResponse{}
+	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
+	if err != nil {
+		return nil, errors.Default.WrapRaw(err)
+	}
+	if tokenResBody.Token == "" {
+		return nil, errors.Default.New("failed to request access token")
+	}
+
+	// output
+	return nil, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+POST /plugins/Zentao/connections
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://example.com",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func PostConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	// update from request and save to database
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.Create(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connection, Status: http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+PATCH /plugins/Zentao/connections/:connectionId
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://example.com",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func PatchConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.Patch(connection, input)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connection}, nil
+}
+
+/*
+DELETE /plugins/Zentao/connections/:connectionId
+*/
+func DeleteConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	if err != nil {
+		return nil, err
+	}
+	err = connectionHelper.Delete(connection)
+	return &core.ApiResourceOutput{Body: connection}, err
+}
+
+/*
+GET /plugins/Zentao/connections
+*/
+func ListConnections(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	var connections []models.ZentaoConnection
+	err := connectionHelper.List(&connections)
+	if err != nil {
+		return nil, err
+	}
+	return &core.ApiResourceOutput{Body: connections, Status: http.StatusOK}, nil
+}
+
+//TODO Please modify the folowing code to adapt to your plugin
+/*
+GET /plugins/Zentao/connections/:connectionId
+{
+	"name": "Zentao data connection name",
+	"endpoint": "Zentao api endpoint, i.e. https://merico.atlassian.net/rest",
+	"username": "username, usually should be email address",
+	"password": "Zentao api access token"
+}
+*/
+func GetConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
+	connection := &models.ZentaoConnection{}
+	err := connectionHelper.First(connection, input.Params)
+	return &core.ApiResourceOutput{Body: connection}, err
+}
diff --git a/plugins/zentao/api/init.go b/plugins/zentao/api/init.go
new file mode 100644
index 000000000..6774e1482
--- /dev/null
+++ b/plugins/zentao/api/init.go
@@ -0,0 +1,39 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package api
+
+import (
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/go-playground/validator/v10"
+	"github.com/spf13/viper"
+	"gorm.io/gorm"
+)
+
+var vld *validator.Validate
+var connectionHelper *helper.ConnectionApiHelper
+var basicRes core.BasicRes
+
+func Init(config *viper.Viper, logger core.Logger, database *gorm.DB) {
+	basicRes = helper.NewDefaultBasicRes(config, logger, database)
+	vld = validator.New()
+	connectionHelper = helper.NewConnectionHelper(
+		basicRes,
+		vld,
+	)
+}
diff --git a/plugins/zentao/e2e/account_test.go b/plugins/zentao/e2e/account_test.go
new file mode 100644
index 000000000..67ec47667
--- /dev/null
+++ b/plugins/zentao/e2e/account_test.go
@@ -0,0 +1,63 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoAccountDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_accounts.csv",
+		"_raw_zentao_api_accounts")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoAccount{})
+	dataflowTester.Subtask(tasks.ExtractAccountMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoAccount{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_accounts.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	dataflowTester.FlushTabler(&crossdomain.Account{})
+	dataflowTester.Subtask(tasks.ConvertAccountMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&crossdomain.Account{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/users.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/bug_test.go b/plugins/zentao/e2e/bug_test.go
new file mode 100644
index 000000000..c7f9baadd
--- /dev/null
+++ b/plugins/zentao/e2e/bug_test.go
@@ -0,0 +1,69 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoBugDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_bugs.csv",
+		"_raw_zentao_api_bugs")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoBug{})
+	dataflowTester.Subtask(tasks.ExtractBugMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoBug{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_bugs.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	// verify conversion
+	dataflowTester.FlushTabler(&ticket.Issue{})
+	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.Subtask(tasks.ConvertBugMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issues_bug.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+	dataflowTester.VerifyTableWithOptions(&ticket.BoardIssue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/board_issues_bug.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/department_test.go b/plugins/zentao/e2e/department_test.go
new file mode 100644
index 000000000..c100da034
--- /dev/null
+++ b/plugins/zentao/e2e/department_test.go
@@ -0,0 +1,63 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoDepartmentDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_departments.csv",
+		"_raw_zentao_api_departments")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoDepartment{})
+	dataflowTester.Subtask(tasks.ExtractDepartmentMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoDepartment{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_departments.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	dataflowTester.FlushTabler(&crossdomain.Team{})
+	dataflowTester.Subtask(tasks.ConvertDepartmentMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&crossdomain.Team{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/teams.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/execution_test.go b/plugins/zentao/e2e/execution_test.go
new file mode 100644
index 000000000..3b0371be5
--- /dev/null
+++ b/plugins/zentao/e2e/execution_test.go
@@ -0,0 +1,63 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoExecutionDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_executions.csv",
+		"_raw_zentao_api_executions")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoExecution{})
+	dataflowTester.Subtask(tasks.ExtractExecutionMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoExecution{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_executions.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	dataflowTester.FlushTabler(&ticket.Board{})
+	dataflowTester.Subtask(tasks.ConvertExecutionMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&ticket.Board{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/boards_execution.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/product_test.go b/plugins/zentao/e2e/product_test.go
new file mode 100644
index 000000000..af7ad2887
--- /dev/null
+++ b/plugins/zentao/e2e/product_test.go
@@ -0,0 +1,63 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoProductDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  9,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_products.csv",
+		"_raw_zentao_api_products")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoProduct{})
+	dataflowTester.Subtask(tasks.ExtractProductMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoProduct{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_products.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	dataflowTester.FlushTabler(&ticket.Board{})
+	dataflowTester.Subtask(tasks.ConvertProductMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&ticket.Board{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/boards_product.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv
new file mode 100644
index 000000000..f1aaf1229
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv
@@ -0,0 +1,13 @@
+id,params,data,url,input,created_at
+31,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+32,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+33,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+34,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+35,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+36,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+37,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+38,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+39,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+40,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+41,"{""ConnectionId"":3,""ProductId"":2,""ExecutionId"":1,""ProjectId"":3}","{""id"":11,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
+42,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":3,""ProjectId"":3}","{""id"":12,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:57.970
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv
new file mode 100644
index 000000000..d69bb7129
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv
@@ -0,0 +1,7 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":4,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":11,""execution"":1,""plan"":0,""story"":4,""storyVersion"":1,""task"":9,""toTask"":0,""toStory"":0,""title"":""\u552e\u540e\u670d\u52a1\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\ [...]
+2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":10,""execution"":1,""plan"":0,""story"":3,""storyVersion"":2,""task"":6,""toTask"":0,""toStory"":0,""title"":""\u6210\u679c\u5c55\u793a\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\ [...]
+3,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u00 [...]
+4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb\ [...]
+5,"{""ConnectionId"":2,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":6,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":9,""execution"":1,""plan"":1,""story"":2,""storyVersion"":1,""task"":15,""toTask"":0,""toStory"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u9875\u9762\u95ee\u9898"",""keywords"":""hh"",""severity"":3,""pri"":2,""type"":""codeerror"",""os"":"",windows"",""browser"":"",chrome"",""hardware"":"""",""found"":"""",""steps"":""\u00 [...]
+6,"{""ConnectionId"":3,""ProductId"":3,""ExecutionId"":11,""ProjectId"":1}","{""id"":5,""project"":7,""product"":3,""injection"":0,""identify"":0,""branch"":0,""module"":8,""execution"":1,""plan"":0,""story"":1,""storyVersion"":1,""task"":1,""toTask"":0,""toStory"":0,""title"":""\u9996\u9875\u9875\u9762\u95ee\u9898"",""keywords"":"""",""severity"":3,""pri"":1,""type"":""codeerror"",""os"":"""",""browser"":"""",""hardware"":"""",""found"":"""",""steps"":""\u003Cp\u003E[\u6b65\u9aa4]\u8fdb [...]
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv
new file mode 100644
index 000000000..88bd599d5
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv
@@ -0,0 +1,13 @@
+id,params,data,url,input,created_at
+31,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":10,""dept"":1,""account"":""testManager"",""realname"":""\u6d4b\u8bd5\u7ecf\u7406"",""role"":""qd"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+32,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":9,""dept"":3,""account"":""tester3"",""realname"":""\u6d4b\u8bd5\u4e19"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+33,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":8,""dept"":3,""account"":""tester2"",""realname"":""\u6d4b\u8bd5\u4e59"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+34,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":7,""dept"":3,""account"":""tester1"",""realname"":""\u6d4b\u8bd5\u7532"",""role"":""qa"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+35,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":6,""dept"":2,""account"":""dev3"",""realname"":""\u5f00\u53d1\u4e19"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+36,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":5,""dept"":2,""account"":""dev2"",""realname"":""\u5f00\u53d1\u4e59"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+37,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":4,""dept"":2,""account"":""dev1"",""realname"":""\u5f00\u53d1\u7532"",""role"":""dev"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+38,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":3,""dept"":6,""account"":""projectManager"",""realname"":""\u9879\u76ee\u7ecf\u7406"",""role"":""pm"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+39,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":2,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+40,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":1,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+41,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":4,""ProjectId"":3}","{""id"":12,""dept"":5,""account"":""productManager"",""realname"":""\u4ea7\u54c1\u7ecf\u7406"",""role"":""po"",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
+42,"{""ConnectionId"":3,""ProductId"":1,""ExecutionId"":1,""ProjectId"":3}","{""id"":11,""dept"":0,""account"":""devlake"",""realname"":""devlake"",""role"":"""",""pinyin"":"""",""email"":""""}",http://iwater.red:8000/api.php/v1/users?limit=100&page=1,null,2022-11-21 11:04:58.124
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv
new file mode 100644
index 000000000..500d4a4c9
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv
@@ -0,0 +1,4 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":1,""ExecutionId"":12,""ProjectId"":1}","{""id"":12,""project"":1091,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":1091,""path"":"",1091,12,"",""grade"":1,""name"":""TR5"",""code"":""0.1.3"",""begin"":""2022-11-01"",""end"":""2022-11-03"",""realBegan"":""2022-07-07"",""realEnd"":null,""days"":"""",""status"": [...]
+2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":7,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days" [...]
+3,"{""ConnectionId"":2,""ProductId"":4,""ExecutionId"":1,""ProjectId"":1}","{""id"":11,""project"":7,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":7,""path"":"",7,1,"",""grade"":1,""name"":""\u4f01\u4e1a\u7f51\u7ad9\u7b2c\u4e00\u671f"",""code"":""coWeb1"",""begin"":""2022-05-01"",""end"":""2022-06-01"",""realBegan"":null,""realEnd"":null,""days [...]
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv
new file mode 100644
index 000000000..9c755cee3
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions_real.csv
@@ -0,0 +1,2 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":1091,""model"":"""",""type"":""sprint"",""lifetime"":""short"",""budget"":""0"",""budgetUnit"":""CNY"",""attribute"":"""",""percent"":0,""milestone"":""0"",""output"":"""",""auth"":"""",""parent"":1091,""path"":"",1091,12,"",""grade"":1,""name"":""TR5"",""code"":""0.1.3"",""begin"":""2022-11-01"",""end"":""2022-11-03"",""realBegan"":""2022-07-07"",""realEnd"":null,""days"":0,""status"":""don [...]
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv
new file mode 100644
index 000000000..c8b7dc499
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv
@@ -0,0 +1,3 @@
+id,params,data,url,input,created_at
+4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":9,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":"" [...]
+5,"{""ConnectionId"":2,""ProductId"":2,""ExecutionId"":9,""ProjectId"":1}","{""id"":3,""program"":10,""name"":""\u4ea7\u54c1\u540d\u79f01"",""code"":""\u4ea7\u54c1\u4ee3\u53f72"",""bind"":""0"",""line"":31,""type"":""normal"",""status"":""normal"",""subStatus"":"""",""desc"":""\u003Cspan style=\""background-color:#FFFFFF;\""\u003E\u4ea7\u54c1\u63cf\u8ff01\u003C\/span\u003E"",""PO"":{""id"":1,""account"":""devlake"",""avatar"":"""",""realname"":""devlake""},""QD"":{""id"":1,""account"":"" [...]
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv
new file mode 100644
index 000000000..78fb04a5f
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv
@@ -0,0 +1,10 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":7,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":7,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5173\u4e8e\u6211\u4eec\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...]
+2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":6,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":6,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u5408\u4f5c\u6d3d\u8c08\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...]
+3,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":5,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":5,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u8bda\u8058\u82f1\u624d\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""reviewing"",""subStatus"":"""",""color"":"""",""stage"":""planned"",""stagedB [...]
+4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":4,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":4,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u552e\u540e\u670d\u52a1\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developed"",""stagedBy [...]
+5,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":3,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u6210\u679c\u5c55\u793a\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":0,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedB [...]
+6,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""st [...]
+7,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto" [...]
+8,"{""ConnectionId"":2,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":8,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":2,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u65b0\u95fb\u4e2d\u5fc3\u7684\u8bbe\u8ba1\u548c\u5f00\u53d1\u3002"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""projected"",""st [...]
+9,"{""ConnectionId"":3,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":9,""vision"":""rnd"",""parent"":0,""product"":3,""branch"":0,""module"":1,""plan"":""1"",""source"":""po"",""sourceNote"":"""",""fromBug"":0,""feedback"":0,""title"":""\u9996\u9875\u8bbe\u8ba1\u548c\u5f00\u53d1"",""keywords"":"""",""type"":""story"",""category"":""feature"",""pri"":1,""estimate"":1,""status"":""active"",""subStatus"":"""",""color"":"""",""stage"":""developing"",""stagedBy"":"""",""mailto" [...]
diff --git a/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv
new file mode 100644
index 000000000..5b9361a62
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv
@@ -0,0 +1,6 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""project"":13,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":0,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account"":"" [...]
+2,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":2,""project"":13,""parent"":0,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""accoun [...]
+4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""project"":13,""parent"":-1,""execution"":1,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account" [...]
+5,"{""ConnectionId"":3,""ProductId"":1,""ExecutionId"":4,""ProjectId"":1}","{""id"":2,""project"":13,""parent"":0,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":12.1,""consumed"":2.1,""left"":10,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""accoun [...]
+6,"{""ConnectionId"":2,""ProductId"":1,""ExecutionId"":3,""ProjectId"":1}","{""id"":3,""project"":13,""parent"":-1,""execution"":9,""module"":0,""design"":0,""story"":0,""storyVersion"":1,""designVersion"":0,""fromBug"":0,""feedback"":0,""fromIssue"":0,""name"":""\u4efb\u52a1\u540d\u79f0"",""type"":""devel"",""mode"":"""",""pri"":3,""estimate"":11.2,""consumed"":0,""left"":0,""deadline"":""2022-10-01"",""status"":""wait"",""subStatus"":"""",""color"":"""",""mailto"":[{""id"":2,""account" [...]
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_accounts.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_accounts.csv
new file mode 100644
index 000000000..675868aa5
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_accounts.csv
@@ -0,0 +1 @@
+connection_id,id,account,avatar,realname,role,dept
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv
new file mode 100644
index 000000000..1f49d1a4c
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_bugs.csv
@@ -0,0 +1,13 @@
+connection_id,id,project,product,injection,identify,branch,module,execution,plan,story,story_version,task,to_task,to_story,title,keywords,severity,pri,type,os,browser,hardware,found,steps,status,sub_status,color,confirmed,activated_count,activated_date,feedback_by,notify_email,opened_by_id,opened_by_name,opened_date,opened_build,assigned_to_id,assigned_to_name,assigned_date,deadline,resolved_by_id,resolution,resolved_build,resolved_date,closed_by_id,closed_date,duplicate_bug,link_bug,fee [...]
+1,1,7,3,0,0,0,8,1,0,1,1,1,0,0,首页页面问题,,3,1,codeerror,,,,,"<p>[步骤]进入首页</p>
+<p>[结果]出现乱码&nbsp;&nbsp;&nbsp;&nbsp;</p>
+<p>[期望]正常显示</p>",active,,,0,0,,,,7,测试甲,2012-06-05T02:56:11.000+00:00,主干,4,开发甲,2012-06-05T02:56:11.000+00:00,,0,,,,0,,0,,0,0,0,0,,,,,,,0,0,2021-04-28T03:09:08.000+00:00,0,1,3,0,激活,normal
+1,2,7,3,0,0,0,9,1,1,2,1,15,0,0,新闻中心页面问题,hh,3,2,codeerror,",windows",",chrome",,,"<p>[步骤]进入新闻中心</p>
+<p>[结果]页面出现乱码</p>
+<p>[期望]正常显示rew</p>",delay,,,1,1,2022-10-05T04:16:44.000+00:00,,1114255335@qq.com,7,测试甲,2012-06-05T02:57:11.000+00:00,主干,0,,2022-10-05T04:19:22.000+00:00,2022-10-06,0,,,,0,,0,,0,0,0,0,,,,,,,1,1,2022-10-05T04:19:22.000+00:00,0,2,3,0,过期Bug,normal
+1,3,7,3,0,0,0,10,1,0,3,2,6,0,0,成果展示页面问题,,3,1,codeerror,,,,,"<p>[步骤]进入成果展示&nbsp;&nbsp;&nbsp;&nbsp;</p>
+<p>[结果]乱码</p>
+<p>[期望]正常显示</p>",active,,,0,0,,,,8,测试乙,2012-06-05T02:58:22.000+00:00,主干,4,开发甲,2012-06-05T02:58:22.000+00:00,,0,,,,0,,0,,0,0,0,0,,,,,,,0,0,2021-04-28T03:09:08.000+00:00,0,1,3,0,激活,normal
+1,4,7,3,0,0,0,11,1,0,4,1,9,0,0,售后服务页面问题,,3,1,codeerror,,,,,"<p>[步骤]进入售后服务</p>
+<p>[结果]乱码</p>
+<p>[期望]正常显示</p>",resolved,,,1,0,,,,9,测试丙,2012-06-05T03:00:19.000+00:00,主干,9,测试丙,2022-10-05T04:10:08.000+00:00,,1,fixed,主干,2022-10-05T04:09:59.000+00:00,0,,0,,0,0,0,0,,,,,,,0,1,2022-10-05T04:10:08.000+00:00,0,1,3,0,已解决,normal
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_departments.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_departments.csv
new file mode 100644
index 000000000..01aca951f
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_departments.csv
@@ -0,0 +1 @@
+connection_id,id,name,parent,path,grade,order_in,position,dept_function,manager,manager_name
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv
new file mode 100644
index 000000000..10e80b83e
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_executions.csv
@@ -0,0 +1,2 @@
+connection_id,id,project,model,type,lifetime,budget,budget_unit,attribute,percent,milestone,output,auth,parent,path,grade,name,code,plan_begin,plan_end,real_began,real_end,days,status,sub_status,pri,description,version,parent_version,plan_duration,real_duration,opened_by_id,opened_date,opened_version,last_edited_by_id,last_edited_date,closed_by_id,closed_date,canceled_by_id,canceled_date,suspended_date,po_id,pm_id,qd_id,rd_id,team,acl,order_in,vision,display_cards,fluid_board,deleted,tot [...]
+1,1,7,,sprint,short,0,CNY,,0,0,,,7,",7,1,",1,企业网站第一期,coWeb1,2022-05-01T00:00:00.000+00:00,2022-06-01T00:00:00.000+00:00,,,0,doing,,1,开发企业网站的基本雏形。<br />,0,0,0,0,0,,,1,2022-11-21T06:00:56.000+00:00,0,,0,,,2,3,10,2,公司开发团队,open,5,rnd,0,0,0,11753,52,51.5,27.5,0,65.2,0
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_products.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_products.csv
new file mode 100644
index 000000000..eff1a9f33
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_products.csv
@@ -0,0 +1,2 @@
+connection_id,id,program,name,code,bind,line,type,status,sub_status,description,po_id,qd_id,rd_id,acl,reviewer,created_by_id,created_date,created_version,order_in,deleted,plans,releases,builds,cases,projects,executions,bugs,docs,progress,case_review
+1,3,10,产品名称1,产品代号2,0,31,normal,normal,,"<span style=""background-color:#FFFFFF;"">产品描述1</span>",1,1,1,private,"devlake,dev1",1,2022-11-17T06:42:25.000+00:00,17.6,15,0,1,0,0,0,0,0,0,0,12.121,0
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv
new file mode 100644
index 000000000..2ece4f19a
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_stories.csv
@@ -0,0 +1,8 @@
+connection_id,id,product,branch,version,order_in,vision,parent,module,plan,source,source_note,from_bug,feedback,title,keywords,type,category,pri,estimate,status,sub_status,color,stage,lib,from_story,from_version,opened_by_id,opened_by_name,opened_date,assigned_to_id,assigned_to_name,assigned_date,approved_date,last_edited_id,last_edited_date,changed_date,reviewed_by_id,reviewed_date,closed_id,closed_date,closed_reason,activated_date,to_bug,child_stories,link_stories,link_requirements,dup [...]
+1,1,3,0,1,0,rnd,0,1,1,po,,0,0,首页设计和开发,,story,feature,1,1,active,,,developing,0,0,1,2,产品经理,2012-06-05T02:09:49.000+00:00,2,产品经理,,,2,2012-06-05T02:25:19.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,2,3,0,1,0,rnd,0,2,1,po,,0,0,新闻中心的设计和开发。,,story,feature,1,1,active,,,projected,0,0,1,2,产品经理,2012-06-05T02:16:37.000+00:00,2,产品经理,2012-06-05T02:16:37.000+00:00,,2,2012-06-05T02:25:33.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,3,3,0,2,0,rnd,0,3,1,po,,0,0,成果展示的设计和开发,,story,feature,1,0,active,,,developing,0,0,1,2,产品经理,2012-06-05T02:18:10.000+00:00,2,产品经理,2012-06-05T02:18:10.000+00:00,,2,2012-06-05T02:25:38.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,4,3,0,1,0,rnd,0,4,1,po,,0,0,售后服务的设计和开发,,story,feature,1,1,active,,,developed,0,0,1,2,产品经理,2012-06-05T02:20:16.000+00:00,2,产品经理,2012-06-05T02:20:16.000+00:00,,2,2012-06-05T02:25:42.000+00:00,,0,2012-06-04T16:00:00.000+00:00,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,5,3,0,1,0,rnd,0,5,1,po,,0,0,诚聘英才的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:21:39.000+00:00,2,产品经理,2012-06-05T02:21:39.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,6,3,0,1,0,rnd,0,6,1,po,,0,0,合作洽谈的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:23:11.000+00:00,2,产品经理,2012-06-05T02:23:11.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
+1,7,3,0,1,0,rnd,0,7,1,po,,0,0,关于我们的设计和开发,,story,feature,1,1,reviewing,,,planned,0,0,1,2,产品经理,2012-06-05T02:24:19.000+00:00,2,产品经理,2012-06-05T02:24:19.000+00:00,,0,,,0,,0,,,,0,,,,0,0,,,0,0,1,1.0版本 
diff --git a/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv
new file mode 100644
index 000000000..50c898873
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/_tool_zentao_tasks.csv
@@ -0,0 +1,4 @@
+connection_id,id,execution_id,project,parent,execution,module,design,story,story_version,design_version,from_bug,feedback,from_issue,name,type,mode,pri,estimate,consumed,deadline,status,sub_status,color,description,version,opened_by_id,opened_by_name,opened_date,assigned_to_id,assigned_to_name,assigned_date,est_started,real_started,finished_id,finished_date,finished_list,canceled_id,canceled_date,closed_by_id,closed_date,plan_duration,real_duration,closed_reason,last_edited_id,last_edite [...]
+1,1,1,13,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,0,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,21.11
+1,2,1,13,0,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,12.1,2.1,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,3
+1,3,1,13,-1,1,0,0,0,1,0,0,0,0,任务名称,devel,,3,11.2,0,2022-10-01,wait,,,任务描述<span> </span><br /><div><br /></div>,1,1,devlake,2022-09-19T01:50:37.000+00:00,5,开发乙,2022-09-19T01:50:37.000+00:00,2022-09-20,,0,,,0,,0,,0,0,,0,,,0,0,0,,,,,0,rnd,0,,0,0,,开发乙,3,0,43.22121
diff --git a/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv b/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv
new file mode 100644
index 000000000..af59f0f72
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/board_issues_bug.csv
@@ -0,0 +1,5 @@
+board_id,issue_id
+zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:1
+zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:2
+zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:3
+zentao:ZentaoProduct:1:3,zentao:ZentaoBug:1:4
diff --git a/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv b/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv
new file mode 100644
index 000000000..2bfddefa4
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/board_issues_story.csv
@@ -0,0 +1,8 @@
+board_id,issue_id
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:1
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:2
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:3
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:4
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:5
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:6
+zentao:ZentaoProduct:1:3,zentao:ZentaoStory:1:7
diff --git a/plugins/zentao/e2e/snapshot_tables/board_issues_task.csv b/plugins/zentao/e2e/snapshot_tables/board_issues_task.csv
new file mode 100644
index 000000000..dbbf76bbd
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/board_issues_task.csv
@@ -0,0 +1,4 @@
+board_id,issue_id
+zentao:ZentaoProduct:1:3,zentao:ZentaoTask:1:1
+zentao:ZentaoProduct:1:3,zentao:ZentaoTask:1:2
+zentao:ZentaoProduct:1:3,zentao:ZentaoTask:1:3
diff --git a/plugins/zentao/e2e/snapshot_tables/boards_execution.csv b/plugins/zentao/e2e/snapshot_tables/boards_execution.csv
new file mode 100644
index 000000000..e596c62f9
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/boards_execution.csv
@@ -0,0 +1,2 @@
+id,name,description,url,created_date,type
+zentao:ZentaoExecution:1:1,企业网站第一期,开发企业网站的基本雏形。<br />,",7,1,",,sprint
diff --git a/plugins/zentao/e2e/snapshot_tables/boards_product.csv b/plugins/zentao/e2e/snapshot_tables/boards_product.csv
new file mode 100644
index 000000000..1da72455a
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/boards_product.csv
@@ -0,0 +1,2 @@
+id,name,description,url,created_date,type
+zentao:ZentaoProduct:1:3,产品名称1,"<span style=""background-color:#FFFFFF;"">产品描述1</span>",,2022-11-17T06:42:25.000+00:00,normal
diff --git a/plugins/zentao/e2e/snapshot_tables/issues_bug.csv b/plugins/zentao/e2e/snapshot_tables/issues_bug.csv
new file mode 100644
index 000000000..9a3babe2a
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/issues_bug.csv
@@ -0,0 +1,5 @@
+id,url,icon_url,issue_key,title,description,epic_key,type,status,original_status,story_point,resolution_date,created_date,updated_date,lead_time_minutes,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component,deployment_id
+zentao:ZentaoBug:1:1,,,1,首页页面问题,,,BUG,IN_PROGRESS,active,0,,2012-06-05T02:56:11.000+00:00,2021-04-28T03:09:08.000+00:00,0,zentao:ZentaoStory:1:1,,0,0,0,7,测试甲,4,开发甲,,,
+zentao:ZentaoBug:1:2,,,2,新闻中心页面问题,,,BUG,IN_PROGRESS,delay,0,,2012-06-05T02:57:11.000+00:00,2022-10-05T04:19:22.000+00:00,0,zentao:ZentaoStory:1:2,,0,0,0,7,测试甲,0,,,,
+zentao:ZentaoBug:1:3,,,3,成果展示页面问题,,,BUG,IN_PROGRESS,active,0,,2012-06-05T02:58:22.000+00:00,2021-04-28T03:09:08.000+00:00,0,zentao:ZentaoStory:1:3,,0,0,0,8,测试乙,4,开发甲,,,
+zentao:ZentaoBug:1:4,,,4,售后服务页面问题,,,BUG,DONE,resolved,0,,2012-06-05T03:00:19.000+00:00,2022-10-05T04:10:08.000+00:00,0,zentao:ZentaoStory:1:4,,0,0,0,9,测试丙,9,测试丙,,,
diff --git a/plugins/zentao/e2e/snapshot_tables/issues_story.csv b/plugins/zentao/e2e/snapshot_tables/issues_story.csv
new file mode 100644
index 000000000..e480b42f6
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/issues_story.csv
@@ -0,0 +1,8 @@
+id,url,icon_url,issue_key,title,description,epic_key,type,status,original_status,story_point,resolution_date,created_date,updated_date,lead_time_minutes,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component,deployment_id
+zentao:ZentaoStory:1:1,,,1,首页设计和开发,,,REQUIREMENT,IN_PROGRESS,developing,0,,2012-06-05T02:09:49.000+00:00,2012-06-05T02:25:19.000+00:00,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:2,,,2,新闻中心的设计和开发。,,,REQUIREMENT,IN_PROGRESS,projected,0,,2012-06-05T02:16:37.000+00:00,2012-06-05T02:25:33.000+00:00,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:3,,,3,成果展示的设计和开发,,,REQUIREMENT,IN_PROGRESS,developing,0,,2012-06-05T02:18:10.000+00:00,2012-06-05T02:25:38.000+00:00,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:4,,,4,售后服务的设计和开发,,,REQUIREMENT,IN_PROGRESS,developed,0,,2012-06-05T02:20:16.000+00:00,2012-06-05T02:25:42.000+00:00,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:5,,,5,诚聘英才的设计和开发,,,REQUIREMENT,IN_PROGRESS,planned,0,,2012-06-05T02:21:39.000+00:00,,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:6,,,6,合作洽谈的设计和开发,,,REQUIREMENT,IN_PROGRESS,planned,0,,2012-06-05T02:23:11.000+00:00,,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
+zentao:ZentaoStory:1:7,,,7,关于我们的设计和开发,,,REQUIREMENT,IN_PROGRESS,planned,0,,2012-06-05T02:24:19.000+00:00,,0,zentao:ZentaoStory:1:0,,0,0,0,2,产品经理,2,产品经理,,,
diff --git a/plugins/zentao/e2e/snapshot_tables/issues_task.csv b/plugins/zentao/e2e/snapshot_tables/issues_task.csv
new file mode 100644
index 000000000..96505fad2
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/issues_task.csv
@@ -0,0 +1,4 @@
+id,url,icon_url,issue_key,title,description,epic_key,type,status,original_status,story_point,resolution_date,created_date,updated_date,lead_time_minutes,parent_issue_id,priority,original_estimate_minutes,time_spent_minutes,time_remaining_minutes,creator_id,creator_name,assignee_id,assignee_name,severity,component,deployment_id
+zentao:ZentaoTask:1:1,,,1,任务名称,任务描述<span> </span><br /><div><br /></div>,,TASK,TODO,wait,0,,2022-09-19T01:50:37.000+00:00,,0,zentao:ZentaoStory:1:0,,0,0,0,1,devlake,5,开发乙,,,
+zentao:ZentaoTask:1:2,,,2,任务名称,任务描述<span> </span><br /><div><br /></div>,,TASK,TODO,wait,0,,2022-09-19T01:50:37.000+00:00,,0,zentao:ZentaoStory:1:0,,0,0,0,1,devlake,5,开发乙,,,
+zentao:ZentaoTask:1:3,,,3,任务名称,任务描述<span> </span><br /><div><br /></div>,,TASK,TODO,wait,0,,2022-09-19T01:50:37.000+00:00,,0,zentao:ZentaoStory:1:-1,,0,0,0,1,devlake,5,开发乙,,,
diff --git a/plugins/zentao/e2e/snapshot_tables/teams.csv b/plugins/zentao/e2e/snapshot_tables/teams.csv
new file mode 100644
index 000000000..356c60dd4
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/teams.csv
@@ -0,0 +1 @@
+id,name,alias,parent_id,sorting_index
diff --git a/plugins/zentao/e2e/snapshot_tables/users.csv b/plugins/zentao/e2e/snapshot_tables/users.csv
new file mode 100644
index 000000000..605226ab7
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/users.csv
@@ -0,0 +1 @@
+id,email,full_name,user_name,avatar_url,organization,created_date,status
diff --git a/plugins/zentao/e2e/story_test.go b/plugins/zentao/e2e/story_test.go
new file mode 100644
index 000000000..c4bdf01d8
--- /dev/null
+++ b/plugins/zentao/e2e/story_test.go
@@ -0,0 +1,69 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoStoryDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_stories.csv",
+		"_raw_zentao_api_stories")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoStory{})
+	dataflowTester.Subtask(tasks.ExtractStoryMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoStory{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_stories.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	// verify conversion
+	dataflowTester.FlushTabler(&ticket.Issue{})
+	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.Subtask(tasks.ConvertStoryMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issues_story.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+	dataflowTester.VerifyTableWithOptions(&ticket.BoardIssue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/board_issues_story.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/e2e/task_test.go b/plugins/zentao/e2e/task_test.go
new file mode 100644
index 000000000..355635259
--- /dev/null
+++ b/plugins/zentao/e2e/task_test.go
@@ -0,0 +1,68 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"testing"
+
+	"github.com/apache/incubator-devlake/helpers/e2ehelper"
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"github.com/apache/incubator-devlake/plugins/zentao/tasks"
+)
+
+func TestZentaoTaskDataFlow(t *testing.T) {
+
+	var zentao impl.Zentao
+	dataflowTester := e2ehelper.NewDataFlowTester(t, "zentao", zentao)
+
+	taskData := &tasks.ZentaoTaskData{
+		Options: &tasks.ZentaoOptions{
+			ConnectionId: 1,
+			ProjectId:    1,
+			ProductId:    3,
+			ExecutionId:  1,
+		},
+	}
+
+	// import raw data table
+	dataflowTester.ImportCsvIntoRawTable("./raw_tables/_raw_zentao_api_tasks.csv",
+		"_raw_zentao_api_tasks")
+
+	// verify extraction
+	dataflowTester.FlushTabler(&models.ZentaoTask{})
+	dataflowTester.Subtask(tasks.ExtractTaskMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&models.ZentaoTask{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/_tool_zentao_tasks.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+
+	dataflowTester.FlushTabler(&ticket.Issue{})
+	dataflowTester.FlushTabler(&ticket.BoardIssue{})
+	dataflowTester.Subtask(tasks.ConvertTaskMeta, taskData)
+	dataflowTester.VerifyTableWithOptions(&ticket.Issue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/issues_task.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+	dataflowTester.VerifyTableWithOptions(&ticket.BoardIssue{}, e2ehelper.TableOptions{
+		CSVRelPath:  "./snapshot_tables/board_issues_task.csv",
+		IgnoreTypes: []interface{}{common.NoPKModel{}},
+	})
+}
diff --git a/plugins/zentao/impl/impl.go b/plugins/zentao/impl/impl.go
new file mode 100644
index 000000000..8b10c1e9e
--- /dev/null
+++ b/plugins/zentao/impl/impl.go
@@ -0,0 +1,142 @@
+/*
+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 impl
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"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"
+	"gorm.io/gorm"
+)
+
+// make sure interface is implemented
+var _ core.PluginMeta = (*Zentao)(nil)
+var _ core.PluginInit = (*Zentao)(nil)
+var _ core.PluginTask = (*Zentao)(nil)
+var _ core.PluginApi = (*Zentao)(nil)
+var _ core.PluginBlueprintV100 = (*Zentao)(nil)
+var _ core.CloseablePluginTask = (*Zentao)(nil)
+
+type Zentao struct{}
+
+func (plugin Zentao) Description() string {
+	return "collect some Zentao data"
+}
+
+func (plugin Zentao) Init(config *viper.Viper, logger core.Logger, db *gorm.DB) errors.Error {
+	api.Init(config, logger, db)
+	return nil
+}
+
+func (plugin Zentao) SubTaskMetas() []core.SubTaskMeta {
+	// TODO add your sub task here
+	return []core.SubTaskMeta{
+		tasks.CollectProductMeta,
+		tasks.ExtractProductMeta,
+		tasks.ConvertProductMeta,
+		tasks.CollectExecutionMeta,
+		tasks.ExtractExecutionMeta,
+		tasks.ConvertExecutionMeta,
+		tasks.CollectStoryMeta,
+		tasks.ExtractStoryMeta,
+		tasks.ConvertStoryMeta,
+		tasks.CollectBugMeta,
+		tasks.ExtractBugMeta,
+		tasks.ConvertBugMeta,
+		tasks.CollectTaskMeta,
+		tasks.ExtractTaskMeta,
+		tasks.ConvertTaskMeta,
+		tasks.CollectAccountMeta,
+		tasks.ExtractAccountMeta,
+		tasks.ConvertAccountMeta,
+		tasks.CollectDepartmentMeta,
+		tasks.ExtractDepartmentMeta,
+		tasks.ConvertDepartmentMeta,
+	}
+}
+
+func (plugin Zentao) PrepareTaskData(taskCtx core.TaskContext, options map[string]interface{}) (interface{}, errors.Error) {
+	op, err := tasks.DecodeAndValidateTaskOptions(options)
+	if err != nil {
+		return nil, errors.Default.Wrap(err, "could not decode Zentao options")
+	}
+	connectionHelper := helper.NewConnectionHelper(
+		taskCtx,
+		nil,
+	)
+	connection := &models.ZentaoConnection{}
+	err = connectionHelper.FirstById(connection, op.ConnectionId)
+	if err != nil {
+		return nil, errors.Default.Wrap(err, "unable to get Zentao connection by the given connection ID: %v")
+	}
+
+	apiClient, err := tasks.NewZentaoApiClient(taskCtx, connection)
+	if err != nil {
+		return nil, errors.Default.Wrap(err, "unable to get Zentao API client instance: %v")
+	}
+
+	return &tasks.ZentaoTaskData{
+		Options:   op,
+		ApiClient: apiClient,
+	}, nil
+}
+
+// PkgPath information lost when compiled as plugin(.so)
+func (plugin Zentao) RootPkgPath() string {
+	return "github.com/apache/incubator-devlake/plugins/zentao"
+}
+
+func (plugin Zentao) MigrationScripts() []core.MigrationScript {
+	return migrationscripts.All()
+}
+
+func (plugin Zentao) ApiResources() map[string]map[string]core.ApiResourceHandler {
+	return map[string]map[string]core.ApiResourceHandler{
+		"test": {
+			"POST": api.TestConnection,
+		},
+		"connections": {
+			"POST": api.PostConnections,
+			"GET":  api.ListConnections,
+		},
+		"connections/:connectionId": {
+			"GET":    api.GetConnection,
+			"PATCH":  api.PatchConnection,
+			"DELETE": api.DeleteConnection,
+		},
+	}
+}
+
+func (plugin Zentao) MakePipelinePlan(connectionId uint64, scope []*core.BlueprintScopeV100) (core.PipelinePlan, errors.Error) {
+	return api.MakePipelinePlan(plugin.SubTaskMetas(), connectionId, scope)
+}
+
+func (plugin Zentao) Close(taskCtx core.TaskContext) errors.Error {
+	data, ok := taskCtx.GetData().(*tasks.ZentaoTaskData)
+	if !ok {
+		return errors.Default.New(fmt.Sprintf("GetData failed when try to close %+v", taskCtx))
+	}
+	data.ApiClient.Release()
+	return nil
+}
diff --git a/plugins/zentao/models/access_token.go b/plugins/zentao/models/access_token.go
new file mode 100644
index 000000000..60bf26178
--- /dev/null
+++ b/plugins/zentao/models/access_token.go
@@ -0,0 +1,27 @@
+/*
+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
+
+type ApiAccessTokenRequest struct {
+	Account  string `json:"account"`
+	Password string `json:"password"`
+}
+
+type ApiAccessTokenResponse struct {
+	Token string `json:"token"`
+}
diff --git a/plugins/zentao/models/account.go b/plugins/zentao/models/account.go
new file mode 100644
index 000000000..07b94c307
--- /dev/null
+++ b/plugins/zentao/models/account.go
@@ -0,0 +1,37 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+)
+
+type ZentaoAccount struct {
+	common.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Account      string `json:"account" gorm:"type:varchar(100);index"`
+	Avatar       string `json:"avatar" gorm:"type:varchar(255)"`
+	Realname     string `json:"realname" gorm:"type:varchar(100);index"`
+	Role         string `json:"role" gorm:"type:varchar(100);index"`
+	Dept         int64  `json:"dept" gorm:"type:BIGINT  NOT NULL;index"`
+}
+
+func (ZentaoAccount) TableName() string {
+	return "_tool_zentao_accounts"
+}
diff --git a/plugins/zentao/models/archived/account.go b/plugins/zentao/models/archived/account.go
new file mode 100644
index 000000000..eb7a81d88
--- /dev/null
+++ b/plugins/zentao/models/archived/account.go
@@ -0,0 +1,37 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+)
+
+type ZentaoAccount struct {
+	archived.NoPKModel
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Account      string `json:"account" gorm:"type:varchar(100);index"`
+	Avatar       string `json:"avatar" gorm:"type:varchar(255)"`
+	Realname     string `json:"realname" gorm:"type:varchar(100);index"`
+	Role         string `json:"role" gorm:"type:varchar(100);index"`
+	Dept         int64  `json:"dept" gorm:"type:BIGINT  NOT NULL;index"`
+}
+
+func (ZentaoAccount) TableName() string {
+	return "_tool_zentao_accounts"
+}
diff --git a/plugins/zentao/models/archived/bug.go b/plugins/zentao/models/archived/bug.go
new file mode 100644
index 000000000..373fabafb
--- /dev/null
+++ b/plugins/zentao/models/archived/bug.go
@@ -0,0 +1,99 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoBug struct {
+	archived.NoPKModel
+	ConnectionId   uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID             int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project        int64               `json:"project"`
+	Product        int64               `json:"product"`
+	Injection      int                 `json:"injection"`
+	Identify       int                 `json:"identify"`
+	Branch         int                 `json:"branch"`
+	Module         int                 `json:"module"`
+	Execution      int64               `json:"execution"`
+	Plan           int                 `json:"plan"`
+	Story          int64               `json:"story"`
+	StoryVersion   int                 `json:"storyVersion"`
+	Task           int                 `json:"task"`
+	ToTask         int                 `json:"toTask"`
+	ToStory        int64               `json:"toStory"`
+	Title          string              `json:"title"`
+	Keywords       string              `json:"keywords"`
+	Severity       int                 `json:"severity"`
+	Pri            int                 `json:"pri"`
+	Type           string              `json:"type"`
+	Os             string              `json:"os"`
+	Browser        string              `json:"browser"`
+	Hardware       string              `json:"hardware"`
+	Found          string              `json:"found"`
+	Steps          string              `json:"steps"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Color          string              `json:"color"`
+	Confirmed      int                 `json:"confirmed"`
+	ActivatedCount int                 `json:"activatedCount"`
+	ActivatedDate  *helper.Iso8601Time `json:"activatedDate"`
+	FeedbackBy     string              `json:"feedbackBy"`
+	NotifyEmail    string              `json:"notifyEmail"`
+	OpenedById     int64
+	OpenedByName   string
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedBuild    string              `json:"openedBuild"`
+	AssignedToId   int64
+	AssignedToName string
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	Deadline       string              `json:"deadline"`
+	ResolvedById   int64
+	Resolution     string              `json:"resolution"`
+	ResolvedBuild  string              `json:"resolvedBuild"`
+	ResolvedDate   *helper.Iso8601Time `json:"resolvedDate"`
+	ClosedById     int64
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	DuplicateBug   int                 `json:"duplicateBug"`
+	LinkBug        string              `json:"linkBug"`
+	Feedback       int                 `json:"feedback"`
+	Result         int                 `json:"result"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	NumOfLine      string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	RepoType       string              `json:"repoType"`
+	IssueKey       string              `json:"issueKey"`
+	Testtask       int                 `json:"testtask"`
+	LastEditedById int64
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	Deleted        bool                `json:"deleted"`
+	PriOrder       string              `json:"priOrder"`
+	SeverityOrder  int                 `json:"severityOrder"`
+	Needconfirm    bool                `json:"needconfirm"`
+	StatusName     string              `json:"statusName"`
+	ProductStatus  string              `json:"productStatus"`
+}
+
+func (ZentaoBug) TableName() string {
+	return "_tool_zentao_bugs"
+}
diff --git a/plugins/zentao/models/archived/connection.go b/plugins/zentao/models/archived/connection.go
new file mode 100644
index 000000000..d4cb2462d
--- /dev/null
+++ b/plugins/zentao/models/archived/connection.go
@@ -0,0 +1,70 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+)
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type ZentaoConnection struct {
+	RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	BasicAuth `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+	Endpoint  string `json:"endpoint"`
+	Proxy     string `json:"proxy"`
+	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"
+}
+
+type BasicAuth struct {
+	Username string `mapstructure:"username" validate:"required" json:"username"`
+	Password string `mapstructure:"password" validate:"required" json:"password"`
+}
+
+type RestConnection struct {
+	BaseConnection   `mapstructure:",squash"`
+	Endpoint         string `mapstructure:"endpoint" validate:"required" json:"endpoint"`
+	Proxy            string `mapstructure:"proxy" json:"proxy"`
+	RateLimitPerHour int    `comment:"api request rate limt per hour" json:"rateLimit"`
+}
+
+type BaseConnection struct {
+	Name string `gorm:"type:varchar(100);uniqueIndex" json:"name" validate:"required"`
+	archived.Model
+}
diff --git a/plugins/zentao/models/archived/department.go b/plugins/zentao/models/archived/department.go
new file mode 100644
index 000000000..08738aaaa
--- /dev/null
+++ b/plugins/zentao/models/archived/department.go
@@ -0,0 +1,39 @@
+/*
+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 "github.com/apache/incubator-devlake/models/migrationscripts/archived"
+
+type ZentaoDepartment struct {
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Name         string `json:"name" gorm:"type:varchar(100);index"`
+	Parent       int    `json:"parent" gorm:"type:varchar(100)"`
+	Path         string `json:"path" gorm:"type:varchar(100)"`
+	Grade        int    `json:"grade"`
+	OrderIn      int    `json:"order"`
+	Position     string `json:"position" gorm:"type:varchar(100)"`
+	DeptFunction string `json:"function" gorm:"type:varchar(100)"`
+	Manager      string `json:"manager" gorm:"type:varchar(100)"`
+	ManagerName  string `json:"managerName" gorm:"type:varchar(100)"`
+	archived.NoPKModel
+}
+
+func (ZentaoDepartment) TableName() string {
+	return "_tool_zentao_departments"
+}
diff --git a/plugins/zentao/models/archived/execution.go b/plugins/zentao/models/archived/execution.go
new file mode 100644
index 000000000..669b38e6a
--- /dev/null
+++ b/plugins/zentao/models/archived/execution.go
@@ -0,0 +1,89 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoExecution struct {
+	ConnectionId   uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Id             int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project        int64               `json:"project"`
+	Model          string              `json:"model"`
+	Type           string              `json:"type"`
+	Lifetime       string              `json:"lifetime"`
+	Budget         string              `json:"budget"`
+	BudgetUnit     string              `json:"budgetUnit"`
+	Attribute      string              `json:"attribute"`
+	Percent        int                 `json:"percent"`
+	Milestone      string              `json:"milestone"`
+	Output         string              `json:"output"`
+	Auth           string              `json:"auth"`
+	Parent         int64               `json:"parent"`
+	Path           string              `json:"path"`
+	Grade          int                 `json:"grade"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	PlanBegin      *helper.Iso8601Time `json:"begin"`
+	PlanEnd        *helper.Iso8601Time `json:"end"`
+	RealBegan      *helper.Iso8601Time `json:"realBegan"`
+	RealEnd        *helper.Iso8601Time `json:"realEnd"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Pri            string              `json:"pri"`
+	Description    string              `json:"desc"`
+	Version        int                 `json:"version"`
+	ParentVersion  int                 `json:"parentVersion"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
+	OpenedById     int64
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
+	LastEditedById int64
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ClosedById     int64
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	CanceledById   int64
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+	POId           int64
+	PMId           int64
+	QDId           int64
+	RDId           int64
+	Team           string  `json:"team"`
+	Acl            string  `json:"acl"`
+	OrderIn        int     `json:"order"`
+	Vision         string  `json:"vision"`
+	DisplayCards   int     `json:"displayCards"`
+	FluidBoard     string  `json:"fluidBoard"`
+	Deleted        bool    `json:"deleted"`
+	TotalHours     float64 `json:"totalHours"`
+	TotalEstimate  float64 `json:"totalEstimate"`
+	TotalConsumed  float64 `json:"totalConsumed"`
+	TotalLeft      float64 `json:"totalLeft"`
+	ProjectId      int64
+	Progress       float64 `json:"progress"`
+	CaseReview     bool    `json:"caseReview"`
+	archived.NoPKModel
+}
+
+func (ZentaoExecution) TableName() string {
+	return "_tool_zentao_executions"
+}
diff --git a/plugins/zentao/models/archived/product.go b/plugins/zentao/models/archived/product.go
new file mode 100644
index 000000000..0a1e19cc5
--- /dev/null
+++ b/plugins/zentao/models/archived/product.go
@@ -0,0 +1,62 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoProduct struct {
+	ConnectionId   uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Id             int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Program        int    `json:"program"`
+	Name           string `json:"name"`
+	Code           string `json:"code"`
+	Bind           string `json:"bind"`
+	Line           int    `json:"line"`
+	Type           string `json:"type"`
+	Status         string `json:"status"`
+	SubStatus      string `json:"subStatus"`
+	Description    string `json:"desc"`
+	POId           int64
+	QDId           int64
+	RDId           int64
+	Acl            string `json:"acl"`
+	Reviewer       string `json:"reviewer"`
+	CreatedById    int64
+	CreatedDate    *helper.Iso8601Time `json:"createdDate"`
+	CreatedVersion string              `json:"createdVersion"`
+	OrderIn        int                 `json:"order"`
+	Deleted        string              `json:"deleted"`
+	Plans          int                 `json:"plans"`
+	Releases       int                 `json:"releases"`
+	Builds         int                 `json:"builds"`
+	Cases          int                 `json:"cases"`
+	Projects       int                 `json:"projects"`
+	Executions     int                 `json:"executions"`
+	Bugs           int                 `json:"bugs"`
+	Docs           int                 `json:"docs"`
+	Progress       float64             `json:"progress"`
+	CaseReview     bool                `json:"caseReview"`
+	archived.NoPKModel
+}
+
+func (ZentaoProduct) TableName() string {
+	return "_tool_zentao_products"
+}
diff --git a/plugins/zentao/models/archived/project.go b/plugins/zentao/models/archived/project.go
new file mode 100644
index 000000000..26ca01df2
--- /dev/null
+++ b/plugins/zentao/models/archived/project.go
@@ -0,0 +1,113 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoProject struct {
+	archived.NoPKModel
+	ConnectionId  uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID            int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project       int64               `json:"project"`
+	Model         string              `json:"model"`
+	Type          string              `json:"type"`
+	Lifetime      string              `json:"lifetime"`
+	Budget        string              `json:"budget"`
+	BudgetUnit    string              `json:"budgetUnit"`
+	Attribute     string              `json:"attribute"`
+	Percent       int                 `json:"percent"`
+	Milestone     string              `json:"milestone"`
+	Output        string              `json:"output"`
+	Auth          string              `json:"auth"`
+	Parent        int64               `json:"parent"`
+	Path          string              `json:"path"`
+	Grade         int                 `json:"grade"`
+	Name          string              `json:"name"`
+	Code          string              `json:"code"`
+	PlanBegin     *helper.Iso8601Time `json:"begin"`
+	PlanEnd       *helper.Iso8601Time `json:"end"`
+	RealBegan     *helper.Iso8601Time `json:"realBegan"`
+	RealEnd       *helper.Iso8601Time `json:"realEnd"`
+	Days          int                 `json:"days"`
+	Status        string              `json:"status"`
+	SubStatus     string              `json:"subStatus"`
+	Pri           string              `json:"pri"`
+	Description   string              `json:"desc"`
+	Version       int                 `json:"version"`
+	ParentVersion int                 `json:"parentVersion"`
+	PlanDuration  int                 `json:"planDuration"`
+	RealDuration  int                 `json:"realDuration"`
+	//OpenedBy       string    `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
+	LastEditedBy   string              `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ClosedBy       string              `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	CanceledBy     string              `json:"canceledBy"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+	PO             string              `json:"PO"`
+	PM             `json:"PM"`
+	QD             string `json:"QD"`
+	RD             string `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	Whitelist      `json:"whitelist" gorm:"-"`
+	OrderIn        int    `json:"order"`
+	Vision         string `json:"vision"`
+	DisplayCards   int    `json:"displayCards"`
+	FluidBoard     string `json:"fluidBoard"`
+	Deleted        bool   `json:"deleted"`
+	Delay          int    `json:"delay"`
+	Hours          `json:"hours"`
+	TeamCount      int    `json:"teamCount"`
+	LeftTasks      string `json:"leftTasks"`
+	//TeamMembers   []interface{} `json:"teamMembers" gorm:"-"`
+	TotalEstimate float64 `json:"totalEstimate"`
+	TotalConsumed float64 `json:"totalConsumed"`
+	TotalLeft     float64 `json:"totalLeft"`
+	Progress      float64 `json:"progress"`
+	TotalReal     int     `json:"totalReal"`
+}
+type PM struct {
+	PmId       int64  `json:"id"`
+	PmAccount  string `json:"account"`
+	PmAvatar   string `json:"avatar"`
+	PmRealname string `json:"realname"`
+}
+type Whitelist []struct {
+	WhitelistID       int64  `json:"id"`
+	WhitelistAccount  string `json:"account"`
+	WhitelistAvatar   string `json:"avatar"`
+	WhitelistRealname string `json:"realname"`
+}
+type Hours struct {
+	HoursTotalEstimate float64 `json:"totalEstimate"`
+	HoursTotalConsumed float64 `json:"totalConsumed"`
+	HoursTotalLeft     float64 `json:"totalLeft"`
+	HoursProgress      float64 `json:"progress"`
+	HoursTotalReal     int     `json:"totalReal"`
+}
+
+func (ZentaoProject) TableName() string {
+	return "_tool_zentao_projects"
+}
diff --git a/plugins/zentao/models/archived/story.go b/plugins/zentao/models/archived/story.go
new file mode 100644
index 000000000..6a2b43bd7
--- /dev/null
+++ b/plugins/zentao/models/archived/story.go
@@ -0,0 +1,89 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoStory struct {
+	archived.NoPKModel
+	ConnectionId uint64  `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64   `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Project      int64   `json:"project"`
+	Product      int64   `json:"product"`
+	Branch       int     `json:"branch"`
+	Version      int     `json:"version"`
+	OrderIn      int     `json:"order"`
+	Vision       string  `json:"vision"`
+	Parent       int64   `json:"parent"`
+	Module       int     `json:"module"`
+	Plan         string  `json:"plan"`
+	Source       string  `json:"source"`
+	SourceNote   string  `json:"sourceNote"`
+	FromBug      int     `json:"fromBug"`
+	Feedback     int     `json:"feedback"`
+	FeedbackBy   string  `json:"feedbackBy"`
+	Title        string  `json:"title"`
+	Keywords     string  `json:"keywords"`
+	Type         string  `json:"type"`
+	Category     string  `json:"category"`
+	Pri          int     `json:"pri"`
+	Estimate     float64 `json:"estimate"`
+	Status       string  `json:"status"`
+	SubStatus    string  `json:"subStatus"`
+	Color        string  `json:"color"`
+	Stage        string  `json:"stage"`
+	StagedById   int64   `json:"stagedBy"`
+	//Mailto           []interface{} `json:"mailto"`
+	Lib              int   `json:"lib"`
+	FromStory        int64 `json:"fromStory"`
+	FromVersion      int   `json:"fromVersion"`
+	OpenedById       int64
+	OpenedByName     string
+	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
+	AssignedToId     int64
+	AssignedToName   string
+	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate     *helper.Iso8601Time `json:"approvedDate"`
+	LastEditedId     int64
+	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedDate      *helper.Iso8601Time `json:"changedDate"`
+	ReviewedById     int64               `json:"reviewedBy"`
+	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
+	ClosedId         int64
+	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
+	ClosedReason     string              `json:"closedReason"`
+	ActivatedDate    *helper.Iso8601Time `json:"activatedDate"`
+	ToBug            int                 `json:"toBug"`
+	ChildStories     string              `json:"childStories"`
+	LinkStories      string              `json:"linkStories"`
+	LinkRequirements string              `json:"linkRequirements"`
+	DuplicateStory   int64               `json:"duplicateStory"`
+	StoryChanged     string              `json:"storyChanged"`
+	NotifyEmail      string              `json:"notifyEmail"`
+	URChanged        string              `json:"URChanged"`
+	Deleted          bool                `json:"deleted"`
+	PriOrder         string              `json:"priOrder"`
+	PlanTitle        string              `json:"planTitle"`
+}
+
+func (ZentaoStory) TableName() string {
+	return "_tool_zentao_stories"
+}
diff --git a/plugins/zentao/models/archived/task.go b/plugins/zentao/models/archived/task.go
new file mode 100644
index 000000000..62c9db05f
--- /dev/null
+++ b/plugins/zentao/models/archived/task.go
@@ -0,0 +1,97 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package archived
+
+import (
+	"github.com/apache/incubator-devlake/models/migrationscripts/archived"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoTask struct {
+	archived.NoPKModel
+	ConnectionId  uint64  `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ExecutionId   int64   `json:"execution_id"`
+	ID            int64   `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project       int64   `json:"project"`
+	Parent        int64   `json:"parent"`
+	Execution     int64   `json:"execution"`
+	Module        int     `json:"module"`
+	Design        int     `json:"design"`
+	Story         int64   `json:"story"`
+	StoryVersion  int     `json:"storyVersion"`
+	DesignVersion int     `json:"designVersion"`
+	FromBug       int     `json:"fromBug"`
+	Feedback      int     `json:"feedback"`
+	FromIssue     int     `json:"fromIssue"`
+	Name          string  `json:"name"`
+	Type          string  `json:"type"`
+	Mode          string  `json:"mode"`
+	Pri           int     `json:"pri"`
+	Estimate      float64 `json:"estimate"`
+	Consumed      float64 `json:"consumed"`
+	Deadline      string  `json:"deadline"`
+	Status        string  `json:"status"`
+	SubStatus     string  `json:"subStatus"`
+	Color         string  `json:"color"`
+	//Mailto        interface{} `json:"mailto"`
+	Description        string `json:"desc"`
+	Version            int    `json:"version"`
+	OpenedById         int64
+	OpenedByName       string
+	OpenedDate         *helper.Iso8601Time `json:"openedDate"`
+	AssignedToId       int64
+	AssignedToName     string
+	AssignedDate       *helper.Iso8601Time `json:"assignedDate"`
+	EstStarted         string              `json:"estStarted"`
+	RealStarted        *helper.Iso8601Time `json:"realStarted"`
+	FinishedId         int64
+	FinishedDate       *helper.Iso8601Time `json:"finishedDate"`
+	FinishedList       string              `json:"finishedList"`
+	CanceledId         int64
+	CanceledDate       *helper.Iso8601Time `json:"canceledDate"`
+	ClosedById         int64
+	ClosedDate         *helper.Iso8601Time `json:"closedDate"`
+	PlanDuration       int                 `json:"planDuration"`
+	RealDuration       int                 `json:"realDuration"`
+	ClosedReason       string              `json:"closedReason"`
+	LastEditedId       int64
+	LastEditedDate     *helper.Iso8601Time `json:"lastEditedDate"`
+	ActivatedDate      *helper.Iso8601Time `json:"activatedDate"`
+	OrderIn            int                 `json:"order"`
+	Repo               int                 `json:"repo"`
+	Mr                 int                 `json:"mr"`
+	Entry              string              `json:"entry"`
+	NumOfLine          string              `json:"lines"`
+	V1                 string              `json:"v1"`
+	V2                 string              `json:"v2"`
+	Deleted            bool                `json:"deleted"`
+	Vision             string              `json:"vision"`
+	StoryID            int64               `json:"storyID"`
+	StoryTitle         string              `json:"storyTitle"`
+	Branch             int                 `json:"branch"`
+	LatestStoryVersion int                 `json:"latestStoryVersion"`
+	StoryStatus        string              `json:"storyStatus"`
+	AssignedToRealName string              `json:"assignedToRealName"`
+	PriOrder           string              `json:"priOrder"`
+	NeedConfirm        bool                `json:"needConfirm"`
+	Progress           float64             `json:"progress"`
+}
+
+func (ZentaoTask) TableName() string {
+	return "_tool_zentao_tasks"
+}
diff --git a/plugins/zentao/models/bug.go b/plugins/zentao/models/bug.go
new file mode 100644
index 000000000..86273234c
--- /dev/null
+++ b/plugins/zentao/models/bug.go
@@ -0,0 +1,167 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoBugRes struct {
+	ID             int64               `json:"id"`
+	Project        int64               `json:"project"`
+	Product        int64               `json:"product"`
+	Injection      int                 `json:"injection"`
+	Identify       int                 `json:"identify"`
+	Branch         int                 `json:"branch"`
+	Module         int                 `json:"module"`
+	Execution      int64               `json:"execution"`
+	Plan           int                 `json:"plan"`
+	Story          int64               `json:"story"`
+	StoryVersion   int                 `json:"storyVersion"`
+	Task           int                 `json:"task"`
+	ToTask         int                 `json:"toTask"`
+	ToStory        int64               `json:"toStory"`
+	Title          string              `json:"title"`
+	Keywords       string              `json:"keywords"`
+	Severity       int                 `json:"severity"`
+	Pri            int                 `json:"pri"`
+	Type           string              `json:"type"`
+	Os             string              `json:"os"`
+	Browser        string              `json:"browser"`
+	Hardware       string              `json:"hardware"`
+	Found          string              `json:"found"`
+	Steps          string              `json:"steps"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Color          string              `json:"color"`
+	Confirmed      int                 `json:"confirmed"`
+	ActivatedCount int                 `json:"activatedCount"`
+	ActivatedDate  *helper.Iso8601Time `json:"activatedDate"`
+	FeedbackBy     string              `json:"feedbackBy"`
+	NotifyEmail    string              `json:"notifyEmail"`
+	OpenedBy       *ZentaoAccount      `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedBuild    string              `json:"openedBuild"`
+	AssignedTo     *ZentaoAccount      `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	Deadline       string              `json:"deadline"`
+	ResolvedBy     *ZentaoAccount      `json:"resolvedBy"`
+	Resolution     string              `json:"resolution"`
+	ResolvedBuild  string              `json:"resolvedBuild"`
+	ResolvedDate   *helper.Iso8601Time `json:"resolvedDate"`
+	ClosedBy       *ZentaoAccount      `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	DuplicateBug   int                 `json:"duplicateBug"`
+	LinkBug        string              `json:"linkBug"`
+	Feedback       int                 `json:"feedback"`
+	Result         int                 `json:"result"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	NumOfLine      string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	RepoType       string              `json:"repoType"`
+	IssueKey       string              `json:"issueKey"`
+	Testtask       int                 `json:"testtask"`
+	LastEditedBy   *ZentaoAccount      `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	Deleted        bool                `json:"deleted"`
+	PriOrder       string              `json:"priOrder"`
+	SeverityOrder  int                 `json:"severityOrder"`
+	Needconfirm    bool                `json:"needconfirm"`
+	StatusName     string              `json:"statusName"`
+	ProductStatus  string              `json:"productStatus"`
+}
+
+type ZentaoBug struct {
+	common.NoPKModel
+	ConnectionId   uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID             int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project        int64               `json:"project"`
+	Product        int64               `json:"product"`
+	Injection      int                 `json:"injection"`
+	Identify       int                 `json:"identify"`
+	Branch         int                 `json:"branch"`
+	Module         int                 `json:"module"`
+	Execution      int64               `json:"execution"`
+	Plan           int                 `json:"plan"`
+	Story          int64               `json:"story"`
+	StoryVersion   int                 `json:"storyVersion"`
+	Task           int                 `json:"task"`
+	ToTask         int                 `json:"toTask"`
+	ToStory        int64               `json:"toStory"`
+	Title          string              `json:"title"`
+	Keywords       string              `json:"keywords"`
+	Severity       int                 `json:"severity"`
+	Pri            int                 `json:"pri"`
+	Type           string              `json:"type"`
+	Os             string              `json:"os"`
+	Browser        string              `json:"browser"`
+	Hardware       string              `json:"hardware"`
+	Found          string              `json:"found"`
+	Steps          string              `json:"steps"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Color          string              `json:"color"`
+	Confirmed      int                 `json:"confirmed"`
+	ActivatedCount int                 `json:"activatedCount"`
+	ActivatedDate  *helper.Iso8601Time `json:"activatedDate"`
+	FeedbackBy     string              `json:"feedbackBy"`
+	NotifyEmail    string              `json:"notifyEmail"`
+	OpenedById     int64
+	OpenedByName   string
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedBuild    string              `json:"openedBuild"`
+	AssignedToId   int64
+	AssignedToName string
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	Deadline       string              `json:"deadline"`
+	ResolvedById   int64
+	Resolution     string              `json:"resolution"`
+	ResolvedBuild  string              `json:"resolvedBuild"`
+	ResolvedDate   *helper.Iso8601Time `json:"resolvedDate"`
+	ClosedById     int64
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	DuplicateBug   int                 `json:"duplicateBug"`
+	LinkBug        string              `json:"linkBug"`
+	Feedback       int                 `json:"feedback"`
+	Result         int                 `json:"result"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	NumOfLine      string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	RepoType       string              `json:"repoType"`
+	IssueKey       string              `json:"issueKey"`
+	Testtask       int                 `json:"testtask"`
+	LastEditedById int64
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	Deleted        bool                `json:"deleted"`
+	PriOrder       string              `json:"priOrder"`
+	SeverityOrder  int                 `json:"severityOrder"`
+	Needconfirm    bool                `json:"needconfirm"`
+	StatusName     string              `json:"statusName"`
+	ProductStatus  string              `json:"productStatus"`
+}
+
+func (ZentaoBug) TableName() string {
+	return "_tool_zentao_bugs"
+}
diff --git a/plugins/zentao/models/connection.go b/plugins/zentao/models/connection.go
new file mode 100644
index 000000000..84f608a56
--- /dev/null
+++ b/plugins/zentao/models/connection.go
@@ -0,0 +1,51 @@
+/*
+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 "github.com/apache/incubator-devlake/plugins/helper"
+
+// TODO Please modify the following code to fit your needs
+// This object conforms to what the frontend currently sends.
+type ZentaoConnection struct {
+	helper.RestConnection `mapstructure:",squash"`
+	//TODO you may need to use helper.BasicAuth instead of helper.AccessToken
+	helper.BasicAuth `mapstructure:",squash"`
+}
+
+type TestConnectionRequest struct {
+	Endpoint         string `json:"endpoint"`
+	Proxy            string `json:"proxy"`
+	helper.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/plugins/zentao/models/department.go b/plugins/zentao/models/department.go
new file mode 100644
index 000000000..08f63c1ac
--- /dev/null
+++ b/plugins/zentao/models/department.go
@@ -0,0 +1,41 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+)
+
+type ZentaoDepartment struct {
+	ConnectionId uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Name         string `json:"name" gorm:"type:varchar(100);index"`
+	Parent       int64  `json:"parent" gorm:"type:varchar(100)"`
+	Path         string `json:"path" gorm:"type:varchar(100)"`
+	Grade        int    `json:"grade"`
+	OrderIn      int    `json:"order"`
+	Position     string `json:"position" gorm:"type:varchar(100)"`
+	DeptFunction string `json:"function" gorm:"type:varchar(100)"`
+	Manager      string `json:"manager" gorm:"type:varchar(100)"`
+	ManagerName  string `json:"managerName" gorm:"type:varchar(100)"`
+	common.NoPKModel
+}
+
+func (ZentaoDepartment) TableName() string {
+	return "_tool_zentao_departments"
+}
diff --git a/plugins/zentao/models/execution.go b/plugins/zentao/models/execution.go
new file mode 100644
index 000000000..337da46fb
--- /dev/null
+++ b/plugins/zentao/models/execution.go
@@ -0,0 +1,225 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoExecutionRes struct {
+	ID             int64               `json:"id"`
+	Project        int64               `json:"project"`
+	Model          string              `json:"model"`
+	Type           string              `json:"type"`
+	Lifetime       string              `json:"lifetime"`
+	Budget         string              `json:"budget"`
+	BudgetUnit     string              `json:"budgetUnit"`
+	Attribute      string              `json:"attribute"`
+	Percent        int                 `json:"percent"`
+	Milestone      string              `json:"milestone"`
+	Output         string              `json:"output"`
+	Auth           string              `json:"auth"`
+	Parent         int64               `json:"parent"`
+	Path           string              `json:"path"`
+	Grade          int                 `json:"grade"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	PlanBegin      *helper.Iso8601Time `json:"begin"`
+	PlanEnd        *helper.Iso8601Time `json:"end"`
+	RealBegan      *helper.Iso8601Time `json:"realBegan"`
+	RealEnd        *helper.Iso8601Time `json:"realEnd"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Pri            string              `json:"pri"`
+	Description    string              `json:"desc"`
+	Version        int                 `json:"version"`
+	ParentVersion  int                 `json:"parentVersion"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
+	OpenedBy       *ZentaoAccount      `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
+	LastEditedBy   *ZentaoAccount      `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ClosedBy       *ZentaoAccount      `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	CanceledBy     *ZentaoAccount      `json:"canceledBy"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+	PO             *ZentaoAccount      `json:"PO"`
+	PM             *ZentaoAccount      `json:"PM"`
+	QD             *ZentaoAccount      `json:"QD"`
+	RD             *ZentaoAccount      `json:"RD"`
+	Team           string              `json:"team"`
+	Acl            string              `json:"acl"`
+	Whitelist      []*ZentaoAccount    `json:"whitelist"`
+	OrderIn        int                 `json:"order"`
+	Vision         string              `json:"vision"`
+	DisplayCards   int                 `json:"displayCards"`
+	FluidBoard     string              `json:"fluidBoard"`
+	Deleted        bool                `json:"deleted"`
+	TotalHours     float64             `json:"totalHours"`
+	TotalEstimate  float64             `json:"totalEstimate"`
+	TotalConsumed  float64             `json:"totalConsumed"`
+	TotalLeft      float64             `json:"totalLeft"`
+	ProjectInfo    struct {
+		ID             int64               `json:"id"`
+		Project        int64               `json:"project"`
+		Model          string              `json:"model"`
+		Type           string              `json:"type"`
+		Lifetime       string              `json:"lifetime"`
+		Budget         string              `json:"budget"`
+		BudgetUnit     string              `json:"budgetUnit"`
+		Attribute      string              `json:"attribute"`
+		Percent        int                 `json:"percent"`
+		Milestone      string              `json:"milestone"`
+		Output         string              `json:"output"`
+		Auth           string              `json:"auth"`
+		Parent         int64               `json:"parent"`
+		Path           string              `json:"path"`
+		Grade          int                 `json:"grade"`
+		Name           string              `json:"name"`
+		Code           string              `json:"code"`
+		PlanBegin      *helper.Iso8601Time `json:"begin"`
+		PlanEnd        *helper.Iso8601Time `json:"end"`
+		RealBegan      *helper.Iso8601Time `json:"realBegan"`
+		RealEnd        *helper.Iso8601Time `json:"realEnd"`
+		Status         string              `json:"status"`
+		SubStatus      string              `json:"subStatus"`
+		Pri            string              `json:"pri"`
+		Description    string              `json:"desc"`
+		Version        int                 `json:"version"`
+		ParentVersion  int                 `json:"parentVersion"`
+		PlanDuration   int                 `json:"planDuration"`
+		RealDuration   int                 `json:"realDuration"`
+		OpenedBy       string              `json:"openedBy"`
+		OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+		OpenedVersion  string              `json:"openedVersion"`
+		LastEditedBy   string              `json:"lastEditedBy"`
+		LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+		ClosedBy       string              `json:"closedBy"`
+		ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+		CanceledBy     string              `json:"canceledBy"`
+		CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+		SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+		PO             string              `json:"PO"`
+		PM             string              `json:"PM"`
+		QD             string              `json:"QD"`
+		RD             string              `json:"RD"`
+		Team           string              `json:"team"`
+		Acl            string              `json:"acl"`
+		Whitelist      string              `json:"whitelist"`
+		OrderIn        int                 `json:"order"`
+		Vision         string              `json:"vision"`
+		DisplayCards   int                 `json:"displayCards"`
+		FluidBoard     string              `json:"fluidBoard"`
+		Deleted        string              `json:"deleted"`
+	} `json:"projectInfo"`
+	Progress    float64 `json:"progress"`
+	TeamMembers []struct {
+		ID         int64   `json:"id"`
+		Root       int     `json:"root"`
+		Type       string  `json:"type"`
+		Account    string  `json:"account"`
+		Role       string  `json:"role"`
+		Position   string  `json:"position"`
+		Limited    string  `json:"limited"`
+		Join       string  `json:"join"`
+		Hours      int     `json:"hours"`
+		Estimate   string  `json:"estimate"`
+		Consumed   string  `json:"consumed"`
+		Left       string  `json:"left"`
+		OrderIn    int     `json:"order"`
+		TotalHours float64 `json:"totalHours"`
+		UserID     int64   `json:"userID"`
+		Realname   string  `json:"realname"`
+	} `json:"teamMembers"`
+	Products []struct {
+		ID    int64         `json:"id"`
+		Name  string        `json:"name"`
+		Plans []interface{} `json:"plans"`
+	} `json:"products"`
+	CaseReview bool `json:"caseReview"`
+}
+
+type ZentaoExecution struct {
+	ConnectionId   uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Id             int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project        int64               `json:"project"`
+	Model          string              `json:"model"`
+	Type           string              `json:"type"`
+	Lifetime       string              `json:"lifetime"`
+	Budget         string              `json:"budget"`
+	BudgetUnit     string              `json:"budgetUnit"`
+	Attribute      string              `json:"attribute"`
+	Percent        int                 `json:"percent"`
+	Milestone      string              `json:"milestone"`
+	Output         string              `json:"output"`
+	Auth           string              `json:"auth"`
+	Parent         int64               `json:"parent"`
+	Path           string              `json:"path"`
+	Grade          int                 `json:"grade"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	PlanBegin      *helper.Iso8601Time `json:"begin"`
+	PlanEnd        *helper.Iso8601Time `json:"end"`
+	RealBegan      *helper.Iso8601Time `json:"realBegan"`
+	RealEnd        *helper.Iso8601Time `json:"realEnd"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Pri            string              `json:"pri"`
+	Description    string              `json:"desc"`
+	Version        int                 `json:"version"`
+	ParentVersion  int                 `json:"parentVersion"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
+	OpenedById     int64
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
+	LastEditedById int64
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ClosedById     int64
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	CanceledById   int64
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+	POId           int64
+	PMId           int64
+	QDId           int64
+	RDId           int64
+	Team           string  `json:"team"`
+	Acl            string  `json:"acl"`
+	OrderIn        int     `json:"order"`
+	Vision         string  `json:"vision"`
+	DisplayCards   int     `json:"displayCards"`
+	FluidBoard     string  `json:"fluidBoard"`
+	Deleted        bool    `json:"deleted"`
+	TotalHours     float64 `json:"totalHours"`
+	TotalEstimate  float64 `json:"totalEstimate"`
+	TotalConsumed  float64 `json:"totalConsumed"`
+	TotalLeft      float64 `json:"totalLeft"`
+	ProjectId      int64
+	Progress       float64 `json:"progress"`
+	CaseReview     bool    `json:"caseReview"`
+	common.NoPKModel
+}
+
+func (ZentaoExecution) TableName() string {
+	return "_tool_zentao_executions"
+}
diff --git a/plugins/zentao/models/migrationscripts/20221121_add_init_tables.go b/plugins/zentao/models/migrationscripts/20221121_add_init_tables.go
new file mode 100644
index 000000000..732f960e8
--- /dev/null
+++ b/plugins/zentao/models/migrationscripts/20221121_add_init_tables.go
@@ -0,0 +1,68 @@
+/*
+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/errors"
+	"github.com/apache/incubator-devlake/helpers/migrationhelper"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/zentao/models/archived"
+)
+
+type addInitTables struct{}
+
+func (*addInitTables) Up(basicRes core.BasicRes) errors.Error {
+	db := basicRes.GetDal()
+	err := db.DropTables(
+		&archived.ZentaoConnection{},
+		&archived.ZentaoProject{},
+		&archived.ZentaoProduct{},
+		&archived.ZentaoExecution{},
+		&archived.ZentaoStory{},
+		&archived.ZentaoBug{},
+		&archived.ZentaoTask{},
+		"_tool_zentao_bugs`",
+		"_tool_zentao_executions`",
+		"_tool_zentao_products`",
+		"_tool_zentao_stories`",
+		"_tool_zentao_tasks`",
+	)
+	if err != nil {
+		return err
+	}
+	return migrationhelper.AutoMigrateTables(
+		basicRes,
+		&archived.ZentaoConnection{},
+		&archived.ZentaoProject{},
+		&archived.ZentaoProduct{},
+		&archived.ZentaoExecution{},
+		&archived.ZentaoStory{},
+		&archived.ZentaoBug{},
+		&archived.ZentaoTask{},
+		&archived.ZentaoAccount{},
+		&archived.ZentaoDepartment{},
+	)
+}
+
+func (*addInitTables) Version() uint64 {
+	return 20221121000001
+}
+
+func (*addInitTables) Name() string {
+	return "zentao init schemas"
+}
diff --git a/plugins/zentao/models/migrationscripts/register.go b/plugins/zentao/models/migrationscripts/register.go
new file mode 100644
index 000000000..c0c766cd6
--- /dev/null
+++ b/plugins/zentao/models/migrationscripts/register.go
@@ -0,0 +1,29 @@
+/*
+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/plugins/core"
+)
+
+// All return all the migration scripts
+func All() []core.MigrationScript {
+	return []core.MigrationScript{
+		new(addInitTables),
+	}
+}
diff --git a/plugins/zentao/models/product.go b/plugins/zentao/models/product.go
new file mode 100644
index 000000000..b9e895f82
--- /dev/null
+++ b/plugins/zentao/models/product.go
@@ -0,0 +1,106 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package models
+
+import (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoProductRes struct {
+	ID             int64               `json:"id"`
+	Program        int                 `json:"program"`
+	Name           string              `json:"name"`
+	Code           string              `json:"code"`
+	Bind           string              `json:"bind"`
+	Line           int                 `json:"line"`
+	Type           string              `json:"type"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Description    string              `json:"desc"`
+	PO             *ZentaoAccount      `json:"PO"`
+	QD             *ZentaoAccount      `json:"QD"`
+	RD             *ZentaoAccount      `json:"RD"`
+	Feedback       interface{}         `json:"feedback"`
+	Acl            string              `json:"acl"`
+	Whitelist      []interface{}       `json:"whitelist"`
+	Reviewer       string              `json:"reviewer"`
+	CreatedBy      *ZentaoAccount      `json:"createdBy"`
+	CreatedDate    *helper.Iso8601Time `json:"createdDate"`
+	CreatedVersion string              `json:"createdVersion"`
+	OrderIn        int                 `json:"order"`
+	Vision         string              `json:"vision"`
+	Deleted        string              `json:"deleted"`
+	Stories        struct {
+		Active    int `json:"active"`
+		Reviewing int `json:"reviewing"`
+		int       `json:""`
+		Draft     int `json:"draft"`
+		Closed    int `json:"closed"`
+		Changing  int `json:"changing"`
+	} `json:"stories"`
+	Plans      int     `json:"plans"`
+	Releases   int     `json:"releases"`
+	Builds     int     `json:"builds"`
+	Cases      int     `json:"cases"`
+	Projects   int     `json:"projects"`
+	Executions int     `json:"executions"`
+	Bugs       int     `json:"bugs"`
+	Docs       int     `json:"docs"`
+	Progress   float64 `json:"progress"`
+	CaseReview bool    `json:"caseReview"`
+}
+
+type ZentaoProduct struct {
+	ConnectionId   uint64 `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Id             int64  `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Program        int    `json:"program"`
+	Name           string `json:"name"`
+	Code           string `json:"code"`
+	Bind           string `json:"bind"`
+	Line           int    `json:"line"`
+	Type           string `json:"type"`
+	Status         string `json:"status"`
+	SubStatus      string `json:"subStatus"`
+	Description    string `json:"desc"`
+	POId           int64
+	QDId           int64
+	RDId           int64
+	Acl            string `json:"acl"`
+	Reviewer       string `json:"reviewer"`
+	CreatedById    int64
+	CreatedDate    *helper.Iso8601Time `json:"createdDate"`
+	CreatedVersion string              `json:"createdVersion"`
+	OrderIn        int                 `json:"order"`
+	Deleted        string              `json:"deleted"`
+	Plans          int                 `json:"plans"`
+	Releases       int                 `json:"releases"`
+	Builds         int                 `json:"builds"`
+	Cases          int                 `json:"cases"`
+	Projects       int                 `json:"projects"`
+	Executions     int                 `json:"executions"`
+	Bugs           int                 `json:"bugs"`
+	Docs           int                 `json:"docs"`
+	Progress       float64             `json:"progress"`
+	CaseReview     bool                `json:"caseReview"`
+	common.NoPKModel
+}
+
+func (ZentaoProduct) TableName() string {
+	return "_tool_zentao_products"
+}
diff --git a/plugins/zentao/models/project.go b/plugins/zentao/models/project.go
new file mode 100644
index 000000000..c97130fe2
--- /dev/null
+++ b/plugins/zentao/models/project.go
@@ -0,0 +1,113 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoProject struct {
+	common.NoPKModel
+	ConnectionId  uint64              `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID            int64               `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project       int64               `json:"project"`
+	Model         string              `json:"model"`
+	Type          string              `json:"type"`
+	Lifetime      string              `json:"lifetime"`
+	Budget        string              `json:"budget"`
+	BudgetUnit    string              `json:"budgetUnit"`
+	Attribute     string              `json:"attribute"`
+	Percent       int                 `json:"percent"`
+	Milestone     string              `json:"milestone"`
+	Output        string              `json:"output"`
+	Auth          string              `json:"auth"`
+	Parent        int64               `json:"parent"`
+	Path          string              `json:"path"`
+	Grade         int                 `json:"grade"`
+	Name          string              `json:"name"`
+	Code          string              `json:"code"`
+	PlanBegin     *helper.Iso8601Time `json:"begin"`
+	PlanEnd       *helper.Iso8601Time `json:"end"`
+	RealBegan     *helper.Iso8601Time `json:"realBegan"`
+	RealEnd       *helper.Iso8601Time `json:"realEnd"`
+	Days          int                 `json:"days"`
+	Status        string              `json:"status"`
+	SubStatus     string              `json:"subStatus"`
+	Pri           string              `json:"pri"`
+	Description   string              `json:"desc"`
+	Version       int                 `json:"version"`
+	ParentVersion int                 `json:"parentVersion"`
+	PlanDuration  int                 `json:"planDuration"`
+	RealDuration  int                 `json:"realDuration"`
+	//OpenedBy       string    `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	OpenedVersion  string              `json:"openedVersion"`
+	LastEditedBy   string              `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ClosedBy       string              `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	CanceledBy     string              `json:"canceledBy"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	SuspendedDate  *helper.Iso8601Time `json:"suspendedDate"`
+	PO             string              `json:"PO"`
+	PM             `json:"PM"`
+	QD             string `json:"QD"`
+	RD             string `json:"RD"`
+	Team           string `json:"team"`
+	Acl            string `json:"acl"`
+	Whitelist      `json:"whitelist" gorm:"-"`
+	OrderIn        int    `json:"order"`
+	Vision         string `json:"vision"`
+	DisplayCards   int    `json:"displayCards"`
+	FluidBoard     string `json:"fluidBoard"`
+	Deleted        bool   `json:"deleted"`
+	Delay          int    `json:"delay"`
+	Hours          `json:"hours"`
+	TeamCount      int    `json:"teamCount"`
+	LeftTasks      string `json:"leftTasks"`
+	//TeamMembers   []interface{} `json:"teamMembers" gorm:"-"`
+	TotalEstimate float64 `json:"totalEstimate"`
+	TotalConsumed float64 `json:"totalConsumed"`
+	TotalLeft     float64 `json:"totalLeft"`
+	Progress      float64 `json:"progress"`
+	TotalReal     int     `json:"totalReal"`
+}
+type PM struct {
+	PmId       int64  `json:"id"`
+	PmAccount  string `json:"account"`
+	PmAvatar   string `json:"avatar"`
+	PmRealname string `json:"realname"`
+}
+type Whitelist []struct {
+	WhitelistID       int64  `json:"id"`
+	WhitelistAccount  string `json:"account"`
+	WhitelistAvatar   string `json:"avatar"`
+	WhitelistRealname string `json:"realname"`
+}
+type Hours struct {
+	HoursTotalEstimate float64 `json:"totalEstimate"`
+	HoursTotalConsumed float64 `json:"totalConsumed"`
+	HoursTotalLeft     float64 `json:"totalLeft"`
+	HoursProgress      float64 `json:"progress"`
+	HoursTotalReal     float64 `json:"totalReal"`
+}
+
+func (ZentaoProject) TableName() string {
+	return "_tool_zentao_projects"
+}
diff --git a/plugins/zentao/models/story.go b/plugins/zentao/models/story.go
new file mode 100644
index 000000000..a737e50e1
--- /dev/null
+++ b/plugins/zentao/models/story.go
@@ -0,0 +1,144 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoStoryRes struct {
+	ID               int64               `json:"id"`
+	Vision           string              `json:"vision"`
+	Parent           int64               `json:"parent"`
+	Product          int64               `json:"product"`
+	Branch           int                 `json:"branch"`
+	Module           int                 `json:"module"`
+	Plan             string              `json:"plan"`
+	Source           string              `json:"source"`
+	SourceNote       string              `json:"sourceNote"`
+	FromBug          int                 `json:"fromBug"`
+	Feedback         int                 `json:"feedback"`
+	Title            string              `json:"title"`
+	Keywords         string              `json:"keywords"`
+	Type             string              `json:"type"`
+	Category         string              `json:"category"`
+	Pri              int                 `json:"pri"`
+	Estimate         float64             `json:"estimate"`
+	Status           string              `json:"status"`
+	SubStatus        string              `json:"subStatus"`
+	Color            string              `json:"color"`
+	Stage            string              `json:"stage"`
+	Mailto           []interface{}       `json:"mailto"`
+	Lib              int                 `json:"lib"`
+	FromStory        int64               `json:"fromStory"`
+	FromVersion      int                 `json:"fromVersion"`
+	OpenedBy         *ZentaoAccount      `json:"openedBy"`
+	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo       *ZentaoAccount      `json:"assignedTo"`
+	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate     *helper.Iso8601Time `json:"approvedDate"`
+	LastEditedBy     *ZentaoAccount      `json:"lastEditedBy"`
+	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedBy        string              `json:"changedBy"`
+	ChangedDate      *helper.Iso8601Time `json:"changedDate"`
+	ReviewedBy       *ZentaoAccount      `json:"reviewedBy"`
+	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
+	ClosedBy         *ZentaoAccount      `json:"closedBy"`
+	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
+	ClosedReason     string              `json:"closedReason"`
+	ActivatedDate    *helper.Iso8601Time `json:"activatedDate"`
+	ToBug            int                 `json:"toBug"`
+	ChildStories     string              `json:"childStories"`
+	LinkStories      string              `json:"linkStories"`
+	LinkRequirements string              `json:"linkRequirements"`
+	DuplicateStory   int64               `json:"duplicateStory"`
+	Version          int                 `json:"version"`
+	StoryChanged     string              `json:"storyChanged"`
+	FeedbackBy       string              `json:"feedbackBy"`
+	NotifyEmail      string              `json:"notifyEmail"`
+	URChanged        string              `json:"URChanged"`
+	Deleted          bool                `json:"deleted"`
+	PriOrder         string              `json:"priOrder"`
+	PlanTitle        string              `json:"planTitle"`
+	ProductStatus    string              `json:"productStatus"`
+}
+
+type ZentaoStory struct {
+	common.NoPKModel
+	ConnectionId uint64  `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ID           int64   `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL" `
+	Product      int64   `json:"product"`
+	Branch       int     `json:"branch"`
+	Version      int     `json:"version"`
+	OrderIn      int     `json:"order"`
+	Vision       string  `json:"vision"`
+	Parent       int64   `json:"parent"`
+	Module       int     `json:"module"`
+	Plan         string  `json:"plan"`
+	Source       string  `json:"source"`
+	SourceNote   string  `json:"sourceNote"`
+	FromBug      int     `json:"fromBug"`
+	Feedback     int     `json:"feedback"`
+	Title        string  `json:"title"`
+	Keywords     string  `json:"keywords"`
+	Type         string  `json:"type"`
+	Category     string  `json:"category"`
+	Pri          int     `json:"pri"`
+	Estimate     float64 `json:"estimate"`
+	Status       string  `json:"status"`
+	SubStatus    string  `json:"subStatus"`
+	Color        string  `json:"color"`
+	Stage        string  `json:"stage"`
+	//Mailto           []interface{} `json:"mailto"`
+	Lib              int   `json:"lib"`
+	FromStory        int64 `json:"fromStory"`
+	FromVersion      int   `json:"fromVersion"`
+	OpenedById       int64
+	OpenedByName     string
+	OpenedDate       *helper.Iso8601Time `json:"openedDate"`
+	AssignedToId     int64
+	AssignedToName   string
+	AssignedDate     *helper.Iso8601Time `json:"assignedDate"`
+	ApprovedDate     *helper.Iso8601Time `json:"approvedDate"`
+	LastEditedId     int64
+	LastEditedDate   *helper.Iso8601Time `json:"lastEditedDate"`
+	ChangedDate      *helper.Iso8601Time `json:"changedDate"`
+	ReviewedById     int64               `json:"reviewedBy"`
+	ReviewedDate     *helper.Iso8601Time `json:"reviewedDate"`
+	ClosedId         int64
+	ClosedDate       *helper.Iso8601Time `json:"closedDate"`
+	ClosedReason     string              `json:"closedReason"`
+	ActivatedDate    *helper.Iso8601Time `json:"activatedDate"`
+	ToBug            int                 `json:"toBug"`
+	ChildStories     string              `json:"childStories"`
+	LinkStories      string              `json:"linkStories"`
+	LinkRequirements string              `json:"linkRequirements"`
+	DuplicateStory   int64               `json:"duplicateStory"`
+	StoryChanged     string              `json:"storyChanged"`
+	FeedbackBy       string              `json:"feedbackBy"`
+	NotifyEmail      string              `json:"notifyEmail"`
+	URChanged        string              `json:"URChanged"`
+	Deleted          bool                `json:"deleted"`
+	PriOrder         string              `json:"priOrder"`
+	PlanTitle        string              `json:"planTitle"`
+}
+
+func (ZentaoStory) TableName() string {
+	return "_tool_zentao_stories"
+}
diff --git a/plugins/zentao/models/task.go b/plugins/zentao/models/task.go
new file mode 100644
index 000000000..47501a239
--- /dev/null
+++ b/plugins/zentao/models/task.go
@@ -0,0 +1,166 @@
+/*
+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 (
+	"github.com/apache/incubator-devlake/models/common"
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+type ZentaoTaskRes struct {
+	Id             int64               `json:"id"`
+	Project        int64               `json:"project"`
+	Parent         int64               `json:"parent"`
+	Execution      int64               `json:"execution"`
+	Module         int                 `json:"module"`
+	Design         int                 `json:"design"`
+	Story          int64               `json:"story"`
+	StoryVersion   int                 `json:"storyVersion"`
+	DesignVersion  int                 `json:"designVersion"`
+	FromBug        int                 `json:"fromBug"`
+	Feedback       int                 `json:"feedback"`
+	FromIssue      int                 `json:"fromIssue"`
+	Name           string              `json:"name"`
+	Type           string              `json:"type"`
+	Mode           string              `json:"mode"`
+	Pri            int                 `json:"pri"`
+	Estimate       float64             `json:"estimate"`
+	Consumed       float64             `json:"consumed"`
+	Deadline       string              `json:"deadline"`
+	Status         string              `json:"status"`
+	SubStatus      string              `json:"subStatus"`
+	Color          string              `json:"color"`
+	Mailto         []*ZentaoAccount    `json:"mailto"`
+	Description    string              `json:"desc"`
+	Version        int                 `json:"version"`
+	OpenedBy       *ZentaoAccount      `json:"openedBy"`
+	OpenedDate     *helper.Iso8601Time `json:"openedDate"`
+	AssignedTo     *ZentaoAccount      `json:"assignedTo"`
+	AssignedDate   *helper.Iso8601Time `json:"assignedDate"`
+	EstStarted     string              `json:"estStarted"`
+	RealStarted    *helper.Iso8601Time `json:"realStarted"`
+	FinishedBy     *ZentaoAccount      `json:"finishedBy"`
+	FinishedDate   *helper.Iso8601Time `json:"finishedDate"`
+	FinishedList   string              `json:"finishedList"`
+	CanceledBy     *ZentaoAccount      `json:"canceledBy"`
+	CanceledDate   *helper.Iso8601Time `json:"canceledDate"`
+	ClosedBy       *ZentaoAccount      `json:"closedBy"`
+	ClosedDate     *helper.Iso8601Time `json:"closedDate"`
+	PlanDuration   int                 `json:"planDuration"`
+	RealDuration   int                 `json:"realDuration"`
+	ClosedReason   string              `json:"closedReason"`
+	LastEditedBy   *ZentaoAccount      `json:"lastEditedBy"`
+	LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+	ActivatedDate  *helper.Iso8601Time `json:"activatedDate"`
+	OrderIn        int                 `json:"order"`
+	Repo           int                 `json:"repo"`
+	Mr             int                 `json:"mr"`
+	Entry          string              `json:"entry"`
+	NumOfLine      string              `json:"lines"`
+	V1             string              `json:"v1"`
+	V2             string              `json:"v2"`
+	Deleted        bool                `json:"deleted"`
+	Vision         string              `json:"vision"`
+	StoryID        int64               `json:"storyID"`
+	StoryTitle     string              `json:"storyTitle"`
+	Branch         interface {
+	} `json:"branch"`
+	LatestStoryVersion interface {
+	} `json:"latestStoryVersion"`
+	StoryStatus interface {
+	} `json:"storyStatus"`
+	AssignedToRealName string  `json:"assignedToRealName"`
+	PriOrder           string  `json:"priOrder"`
+	Delay              int     `json:"delay"`
+	NeedConfirm        bool    `json:"needConfirm"`
+	Progress           float64 `json:"progress"`
+}
+
+type ZentaoTask struct {
+	common.NoPKModel
+	ConnectionId  uint64  `gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	ExecutionId   int64   `json:"execution_id"`
+	ID            int64   `json:"id" gorm:"primaryKey;type:BIGINT  NOT NULL"`
+	Project       int64   `json:"project"`
+	Parent        int64   `json:"parent"`
+	Execution     int64   `json:"execution"`
+	Module        int     `json:"module"`
+	Design        int     `json:"design"`
+	Story         int64   `json:"story"`
+	StoryVersion  int     `json:"storyVersion"`
+	DesignVersion int     `json:"designVersion"`
+	FromBug       int     `json:"fromBug"`
+	Feedback      int     `json:"feedback"`
+	FromIssue     int     `json:"fromIssue"`
+	Name          string  `json:"name"`
+	Type          string  `json:"type"`
+	Mode          string  `json:"mode"`
+	Pri           int     `json:"pri"`
+	Estimate      float64 `json:"estimate"`
+	Consumed      float64 `json:"consumed"`
+	Deadline      string  `json:"deadline"`
+	Status        string  `json:"status"`
+	SubStatus     string  `json:"subStatus"`
+	Color         string  `json:"color"`
+	//Mailto        interface{} `json:"mailto"`
+	Description        string `json:"desc"`
+	Version            int    `json:"version"`
+	OpenedById         int64
+	OpenedByName       string
+	OpenedDate         *helper.Iso8601Time `json:"openedDate"`
+	AssignedToId       int64
+	AssignedToName     string
+	AssignedDate       *helper.Iso8601Time `json:"assignedDate"`
+	EstStarted         string              `json:"estStarted"`
+	RealStarted        *helper.Iso8601Time `json:"realStarted"`
+	FinishedId         int64
+	FinishedDate       *helper.Iso8601Time `json:"finishedDate"`
+	FinishedList       string              `json:"finishedList"`
+	CanceledId         int64
+	CanceledDate       *helper.Iso8601Time `json:"canceledDate"`
+	ClosedById         int64
+	ClosedDate         *helper.Iso8601Time `json:"closedDate"`
+	PlanDuration       int                 `json:"planDuration"`
+	RealDuration       int                 `json:"realDuration"`
+	ClosedReason       string              `json:"closedReason"`
+	LastEditedId       int64
+	LastEditedDate     *helper.Iso8601Time `json:"lastEditedDate"`
+	ActivatedDate      *helper.Iso8601Time `json:"activatedDate"`
+	OrderIn            int                 `json:"order"`
+	Repo               int                 `json:"repo"`
+	Mr                 int                 `json:"mr"`
+	Entry              string              `json:"entry"`
+	NumOfLine          string              `json:"lines"`
+	V1                 string              `json:"v1"`
+	V2                 string              `json:"v2"`
+	Deleted            bool                `json:"deleted"`
+	Vision             string              `json:"vision"`
+	StoryID            int64               `json:"storyID"`
+	StoryTitle         string              `json:"storyTitle"`
+	Branch             int                 `json:"branch"`
+	LatestStoryVersion int                 `json:"latestStoryVersion"`
+	StoryStatus        string              `json:"storyStatus"`
+	AssignedToRealName string              `json:"assignedToRealName"`
+	PriOrder           string              `json:"priOrder"`
+	NeedConfirm        bool                `json:"needConfirm"`
+	Progress           float64             `json:"progress"`
+}
+
+func (ZentaoTask) TableName() string {
+	return "_tool_zentao_tasks"
+}
diff --git a/plugins/zentao/tasks/account_collector.go b/plugins/zentao/tasks/account_collector.go
new file mode 100644
index 000000000..a7f664462
--- /dev/null
+++ b/plugins/zentao/tasks/account_collector.go
@@ -0,0 +1,82 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_ACCOUNT_TABLE = "zentao_api_accounts"
+
+var _ core.SubTaskEntryPoint = CollectAccount
+
+func CollectAccount(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_ACCOUNT_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "/users",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Users []json.RawMessage `json:"users"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector")
+			}
+			return data.Users, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectAccountMeta = core.SubTaskMeta{
+	Name:             "CollectAccount",
+	EntryPoint:       CollectAccount,
+	EnabledByDefault: true,
+	Description:      "Collect Account data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/account_convertor.go b/plugins/zentao/tasks/account_convertor.go
new file mode 100644
index 000000000..be17d1172
--- /dev/null
+++ b/plugins/zentao/tasks/account_convertor.go
@@ -0,0 +1,91 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertAccount
+
+var ConvertAccountMeta = core.SubTaskMeta{
+	Name:             "convertAccount",
+	EntryPoint:       ConvertAccount,
+	EnabledByDefault: true,
+	Description:      "convert Zentao account",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertAccount(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	accountIdGen := didgen.NewDomainIdGenerator(&models.ZentaoAccount{})
+	deptIdGen := didgen.NewDomainIdGenerator(&models.ZentaoDepartment{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoAccount{}),
+		dal.Where(`_tool_zentao_accounts.connection_id = ?`, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoAccount{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_ACCOUNT_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolEntity := inputRow.(*models.ZentaoAccount)
+
+			domainEntity := &crossdomain.Account{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: accountIdGen.Generate(toolEntity.ConnectionId, toolEntity.ID),
+				},
+				FullName:     toolEntity.Realname,
+				UserName:     toolEntity.Account,
+				AvatarUrl:    toolEntity.Avatar,
+				Organization: deptIdGen.Generate(toolEntity.ConnectionId, toolEntity.Dept),
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainEntity)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/account_extractor.go b/plugins/zentao/tasks/account_extractor.go
new file mode 100644
index 000000000..f982d837e
--- /dev/null
+++ b/plugins/zentao/tasks/account_extractor.go
@@ -0,0 +1,69 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractAccount
+
+var ExtractAccountMeta = core.SubTaskMeta{
+	Name:             "extractAccount",
+	EntryPoint:       ExtractAccount,
+	EnabledByDefault: true,
+	Description:      "extract Zentao account",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractAccount(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_ACCOUNT_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			account := &models.ZentaoAccount{}
+			err := json.Unmarshal(row.Data, account)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			account.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, account)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/api_client.go b/plugins/zentao/tasks/api_client.go
new file mode 100644
index 000000000..aff7b4d69
--- /dev/null
+++ b/plugins/zentao/tasks/api_client.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 tasks
+
+import (
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+func NewZentaoApiClient(taskCtx core.TaskContext, connection *models.ZentaoConnection) (*helper.ApiAsyncClient, error) {
+	authApiClient, err := helper.NewApiClient(taskCtx.GetContext(), connection.Endpoint, nil, 0, connection.Proxy, taskCtx)
+	if err != nil {
+		return nil, err
+	}
+
+	// request for access token
+	tokenReqBody := &models.ApiAccessTokenRequest{
+		Account:  connection.Username,
+		Password: connection.Password,
+	}
+	tokenRes, err := authApiClient.Post("/tokens", nil, tokenReqBody, nil)
+	if err != nil {
+		return nil, err
+	}
+	tokenResBody := &models.ApiAccessTokenResponse{}
+	err = helper.UnmarshalResponse(tokenRes, tokenResBody)
+	if err != nil {
+		return nil, err
+	}
+	if tokenResBody.Token == "" {
+		return nil, errors.Default.New("failed to request access token")
+	}
+	// real request apiClient
+	apiClient, err := helper.NewApiClient(taskCtx.GetContext(), connection.Endpoint, nil, 0, connection.Proxy, taskCtx)
+	if err != nil {
+		return nil, err
+	}
+	// set token
+	apiClient.SetHeaders(map[string]string{
+		"Token": fmt.Sprintf("%v", tokenResBody.Token),
+	})
+
+	// create rate limit calculator
+	rateLimiter := &helper.ApiRateLimitCalculator{
+		UserRateLimitPerHour: connection.RateLimitPerHour,
+		DynamicRateLimit: func(res *http.Response) (int, time.Duration, errors.Error) {
+			rateLimitHeader := res.Header.Get("RateLimit-Limit")
+			if rateLimitHeader == "" {
+				// use default
+				return 0, 0, nil
+			}
+			rateLimit, err := strconv.Atoi(rateLimitHeader)
+			if err != nil {
+				return 0, 0, errors.Default.Wrap(err, "failed to parse RateLimit-Limit header: %w")
+			}
+			// seems like {{ .plugin-ame }} rate limit is on minute basis
+			return rateLimit, 1 * time.Minute, nil
+		},
+	}
+	asyncApiClient, err := helper.CreateAsyncApiClient(
+		taskCtx,
+		apiClient,
+		rateLimiter,
+	)
+	if err != nil {
+		return nil, err
+	}
+	return asyncApiClient, nil
+}
+
+type ZentaoPagination struct {
+	Page int `json:"page"`
+}
diff --git a/plugins/zentao/tasks/bug_collector.go b/plugins/zentao/tasks/bug_collector.go
new file mode 100644
index 000000000..843438583
--- /dev/null
+++ b/plugins/zentao/tasks/bug_collector.go
@@ -0,0 +1,82 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_BUG_TABLE = "zentao_api_bugs"
+
+var _ core.SubTaskEntryPoint = CollectBug
+
+func CollectBug(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "/products/{{ .Params.ProductId }}/bugs",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Bugs []json.RawMessage `json:"bugs"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector")
+			}
+			return data.Bugs, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectBugMeta = core.SubTaskMeta{
+	Name:             "CollectBug",
+	EntryPoint:       CollectBug,
+	EnabledByDefault: true,
+	Description:      "Collect Bug data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/bug_convertor.go b/plugins/zentao/tasks/bug_convertor.go
new file mode 100644
index 000000000..ed9735764
--- /dev/null
+++ b/plugins/zentao/tasks/bug_convertor.go
@@ -0,0 +1,116 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+	"strconv"
+)
+
+var _ core.SubTaskEntryPoint = ConvertBug
+
+var ConvertBugMeta = core.SubTaskMeta{
+	Name:             "convertBug",
+	EntryPoint:       ConvertBug,
+	EnabledByDefault: true,
+	Description:      "convert Zentao bug",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertBug(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	bugIdGen := didgen.NewDomainIdGenerator(&models.ZentaoBug{})
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{})
+	storyIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStory{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoBug{}),
+		dal.Where(`_tool_zentao_bugs.product = ? and
+			_tool_zentao_bugs.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoBug{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolEntity := inputRow.(*models.ZentaoBug)
+			domainEntity := &ticket.Issue{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: bugIdGen.Generate(toolEntity.ConnectionId, toolEntity.ID),
+				},
+				IssueKey:       strconv.FormatInt(toolEntity.ID, 10),
+				Title:          toolEntity.Title,
+				Type:           ticket.BUG,
+				OriginalStatus: toolEntity.Status,
+				ResolutionDate: toolEntity.ClosedDate.ToNullableTime(),
+				CreatedDate:    toolEntity.OpenedDate.ToNullableTime(),
+				UpdatedDate:    toolEntity.LastEditedDate.ToNullableTime(),
+				ParentIssueId:  storyIdGen.Generate(data.Options.ConnectionId, toolEntity.Story),
+				Priority:       string(rune(toolEntity.Pri)),
+				CreatorId:      strconv.FormatInt(toolEntity.OpenedById, 10),
+				CreatorName:    toolEntity.OpenedByName,
+				AssigneeId:     strconv.FormatInt(toolEntity.AssignedToId, 10),
+				AssigneeName:   toolEntity.AssignedToName,
+				Severity:       string(rune(toolEntity.Severity)),
+			}
+			switch toolEntity.Status {
+			case "resolved":
+				domainEntity.Status = ticket.DONE
+			default:
+				domainEntity.Status = ticket.IN_PROGRESS
+			}
+			if toolEntity.ClosedDate != nil {
+				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
+			}
+			domainBoardIssue := &ticket.BoardIssue{
+				BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ProductId),
+				IssueId: domainEntity.Id,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainEntity, domainBoardIssue)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/bug_extractor.go b/plugins/zentao/tasks/bug_extractor.go
new file mode 100644
index 000000000..7935c5a17
--- /dev/null
+++ b/plugins/zentao/tasks/bug_extractor.go
@@ -0,0 +1,138 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractBug
+
+var ExtractBugMeta = core.SubTaskMeta{
+	Name:             "extractBug",
+	EntryPoint:       ExtractBug,
+	EnabledByDefault: true,
+	Description:      "extract Zentao bug",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractBug(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_BUG_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			res := &models.ZentaoBugRes{}
+			err := json.Unmarshal(row.Data, res)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			bug := &models.ZentaoBug{
+				ConnectionId:   data.Options.ConnectionId,
+				ID:             res.ID,
+				Project:        res.Project,
+				Product:        res.Product,
+				Injection:      res.Injection,
+				Identify:       res.Identify,
+				Branch:         res.Branch,
+				Module:         res.Module,
+				Execution:      res.Execution,
+				Plan:           res.Plan,
+				Story:          res.Story,
+				StoryVersion:   res.StoryVersion,
+				Task:           res.Task,
+				ToTask:         res.ToTask,
+				ToStory:        res.ToStory,
+				Title:          res.Title,
+				Keywords:       res.Keywords,
+				Severity:       res.Severity,
+				Pri:            res.Pri,
+				Type:           res.Type,
+				Os:             res.Os,
+				Browser:        res.Browser,
+				Hardware:       res.Hardware,
+				Found:          res.Found,
+				Steps:          res.Steps,
+				Status:         res.Status,
+				SubStatus:      res.SubStatus,
+				Color:          res.Color,
+				Confirmed:      res.Confirmed,
+				ActivatedCount: res.ActivatedCount,
+				ActivatedDate:  res.ActivatedDate,
+				FeedbackBy:     res.FeedbackBy,
+				NotifyEmail:    res.NotifyEmail,
+				OpenedById:     getAccountId(res.OpenedBy),
+				OpenedByName:   getAccountName(res.OpenedBy),
+				OpenedDate:     res.OpenedDate,
+				OpenedBuild:    res.OpenedBuild,
+				AssignedToId:   getAccountId(res.AssignedTo),
+				AssignedToName: getAccountName(res.AssignedTo),
+				AssignedDate:   res.AssignedDate,
+				Deadline:       res.Deadline,
+				ResolvedById:   getAccountId(res.ResolvedBy),
+				Resolution:     res.Resolution,
+				ResolvedBuild:  res.ResolvedBuild,
+				ResolvedDate:   res.ResolvedDate,
+				ClosedById:     getAccountId(res.ClosedBy),
+				ClosedDate:     res.ClosedDate,
+				DuplicateBug:   res.DuplicateBug,
+				LinkBug:        res.LinkBug,
+				Feedback:       res.Feedback,
+				Result:         res.Result,
+				Repo:           res.Repo,
+				Mr:             res.Mr,
+				Entry:          res.Entry,
+				NumOfLine:      res.NumOfLine,
+				V1:             res.V1,
+				V2:             res.V2,
+				RepoType:       res.RepoType,
+				IssueKey:       res.IssueKey,
+				Testtask:       res.Testtask,
+				LastEditedById: getAccountId(res.LastEditedBy),
+				LastEditedDate: res.LastEditedDate,
+				Deleted:        res.Deleted,
+				PriOrder:       res.PriOrder,
+				SeverityOrder:  res.SeverityOrder,
+				Needconfirm:    res.Needconfirm,
+				StatusName:     res.StatusName,
+				ProductStatus:  res.ProductStatus,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, bug)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/department_collector.go b/plugins/zentao/tasks/department_collector.go
new file mode 100644
index 000000000..eacc2779e
--- /dev/null
+++ b/plugins/zentao/tasks/department_collector.go
@@ -0,0 +1,82 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_DEPARTMENT_TABLE = "zentao_api_departments"
+
+var _ core.SubTaskEntryPoint = CollectDepartment
+
+func CollectDepartment(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_DEPARTMENT_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "/users",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Users []json.RawMessage `json:"users"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector")
+			}
+			return data.Users, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectDepartmentMeta = core.SubTaskMeta{
+	Name:             "CollectDepartment",
+	EntryPoint:       CollectDepartment,
+	EnabledByDefault: true,
+	Description:      "Collect Department data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/department_convertor.go b/plugins/zentao/tasks/department_convertor.go
new file mode 100644
index 000000000..b892bc822
--- /dev/null
+++ b/plugins/zentao/tasks/department_convertor.go
@@ -0,0 +1,88 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/crossdomain"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertDepartment
+
+var ConvertDepartmentMeta = core.SubTaskMeta{
+	Name:             "convertDepartment",
+	EntryPoint:       ConvertDepartment,
+	EnabledByDefault: true,
+	Description:      "convert Zentao department",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertDepartment(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	departmentIdGen := didgen.NewDomainIdGenerator(&models.ZentaoDepartment{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoDepartment{}),
+		dal.Where(`_tool_zentao_departments.connection_id = ?`, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoDepartment{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_DEPARTMENT_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolEntity := inputRow.(*models.ZentaoDepartment)
+
+			domainEntity := &crossdomain.Team{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: departmentIdGen.Generate(toolEntity.ConnectionId, toolEntity.ID),
+				},
+				Name:     toolEntity.Name,
+				ParentId: departmentIdGen.Generate(toolEntity.ConnectionId, toolEntity.Parent),
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainEntity)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/department_extractor.go b/plugins/zentao/tasks/department_extractor.go
new file mode 100644
index 000000000..8ec1a6355
--- /dev/null
+++ b/plugins/zentao/tasks/department_extractor.go
@@ -0,0 +1,69 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractDepartment
+
+var ExtractDepartmentMeta = core.SubTaskMeta{
+	Name:             "extractDepartment",
+	EntryPoint:       ExtractDepartment,
+	EnabledByDefault: true,
+	Description:      "extract Zentao department",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractDepartment(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_DEPARTMENT_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			department := &models.ZentaoDepartment{}
+			err := json.Unmarshal(row.Data, department)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			department.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, department)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/execution_collector.go b/plugins/zentao/tasks/execution_collector.go
new file mode 100644
index 000000000..8f6d2c6ae
--- /dev/null
+++ b/plugins/zentao/tasks/execution_collector.go
@@ -0,0 +1,81 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"io"
+	"net/http"
+	"net/url"
+)
+
+const RAW_EXECUTION_TABLE = "zentao_api_executions"
+
+var _ core.SubTaskEntryPoint = CollectExecution
+
+func CollectExecution(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "executions/{{ .Params.ExecutionId }}",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			body, err := io.ReadAll(res.Body)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao execution collector")
+			}
+			res.Body.Close()
+			return []json.RawMessage{body}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectExecutionMeta = core.SubTaskMeta{
+	Name:             "CollectExecution",
+	EntryPoint:       CollectExecution,
+	EnabledByDefault: true,
+	Description:      "Collect Execution data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/execution_convertor.go b/plugins/zentao/tasks/execution_convertor.go
new file mode 100644
index 000000000..b663dfec0
--- /dev/null
+++ b/plugins/zentao/tasks/execution_convertor.go
@@ -0,0 +1,93 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertExecutions
+
+var ConvertExecutionMeta = core.SubTaskMeta{
+	Name:             "convertExecutions",
+	EntryPoint:       ConvertExecutions,
+	EnabledByDefault: true,
+	Description:      "convert Zentao executions",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertExecutions(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoExecution{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoExecution{}),
+		dal.Where(`_tool_zentao_executions.id = ? and 
+			_tool_zentao_executions.connection_id = ?`, data.Options.ExecutionId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoExecution{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolExecution := inputRow.(*models.ZentaoExecution)
+
+			domainBoard := &ticket.Board{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: boardIdGen.Generate(toolExecution.ConnectionId, toolExecution.Id),
+				},
+				Name:        toolExecution.Name,
+				Description: toolExecution.Description,
+				Url:         toolExecution.Path,
+				CreatedDate: toolExecution.OpenedDate.ToNullableTime(),
+				Type:        toolExecution.Type,
+			}
+
+			results := make([]interface{}, 0)
+			results = append(results, domainBoard)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/execution_extractor.go b/plugins/zentao/tasks/execution_extractor.go
new file mode 100644
index 000000000..6ed94b870
--- /dev/null
+++ b/plugins/zentao/tasks/execution_extractor.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 tasks
+
+import (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractExecutions
+
+var ExtractExecutionMeta = core.SubTaskMeta{
+	Name:             "extractExecutions",
+	EntryPoint:       ExtractExecutions,
+	EnabledByDefault: true,
+	Description:      "extract Zentao executions",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractExecutions(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_EXECUTION_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			res := &models.ZentaoExecutionRes{}
+			err := json.Unmarshal(row.Data, res)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			execution := &models.ZentaoExecution{
+				ConnectionId:   data.Options.ConnectionId,
+				Id:             res.ID,
+				Project:        res.Project,
+				Model:          res.Model,
+				Type:           res.Type,
+				Lifetime:       res.Lifetime,
+				Budget:         res.Budget,
+				BudgetUnit:     res.BudgetUnit,
+				Attribute:      res.Attribute,
+				Percent:        res.Percent,
+				Milestone:      res.Milestone,
+				Output:         res.Output,
+				Auth:           res.Auth,
+				Parent:         res.Parent,
+				Path:           res.Path,
+				Grade:          res.Grade,
+				Name:           res.Name,
+				Code:           res.Code,
+				PlanBegin:      res.PlanBegin,
+				PlanEnd:        res.PlanEnd,
+				RealBegan:      res.RealBegan,
+				RealEnd:        res.RealEnd,
+				Status:         res.Status,
+				SubStatus:      res.SubStatus,
+				Pri:            res.Pri,
+				Description:    res.Description,
+				Version:        res.Version,
+				ParentVersion:  res.ParentVersion,
+				PlanDuration:   res.PlanDuration,
+				RealDuration:   res.RealDuration,
+				OpenedById:     getAccountId(res.OpenedBy),
+				OpenedDate:     res.OpenedDate,
+				OpenedVersion:  res.OpenedVersion,
+				LastEditedById: getAccountId(res.LastEditedBy),
+				LastEditedDate: res.LastEditedDate,
+				ClosedById:     getAccountId(res.ClosedBy),
+				ClosedDate:     res.ClosedDate,
+				CanceledById:   getAccountId(res.CanceledBy),
+				CanceledDate:   res.CanceledDate,
+				SuspendedDate:  res.SuspendedDate,
+				POId:           getAccountId(res.PO),
+				PMId:           getAccountId(res.PM),
+				QDId:           getAccountId(res.QD),
+				RDId:           getAccountId(res.RD),
+				Team:           res.Team,
+				Acl:            res.Acl,
+				OrderIn:        res.OrderIn,
+				Vision:         res.Vision,
+				DisplayCards:   res.DisplayCards,
+				FluidBoard:     res.FluidBoard,
+				Deleted:        res.Deleted,
+				TotalHours:     res.TotalHours,
+				TotalEstimate:  res.TotalEstimate,
+				TotalConsumed:  res.TotalConsumed,
+				TotalLeft:      res.TotalLeft,
+				Progress:       res.Progress,
+				CaseReview:     res.CaseReview,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, execution)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/product_collector.go b/plugins/zentao/tasks/product_collector.go
new file mode 100644
index 000000000..cc1e9d8cb
--- /dev/null
+++ b/plugins/zentao/tasks/product_collector.go
@@ -0,0 +1,80 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"io"
+	"net/http"
+	"net/url"
+)
+
+const RAW_PRODUCT_TABLE = "zentao_api_products"
+
+var _ core.SubTaskEntryPoint = CollectProduct
+
+func CollectProduct(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProductId:    data.Options.ProductId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_PRODUCT_TABLE,
+		},
+		ApiClient: data.ApiClient,
+		PageSize:  100,
+		// TODO write which api would you want request
+		UrlTemplate: "products/{{ .Params.ProductId }}",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			body, err := io.ReadAll(res.Body)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao product collector")
+			}
+			res.Body.Close()
+			return []json.RawMessage{body}, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectProductMeta = core.SubTaskMeta{
+	Name:             "CollectProduct",
+	EntryPoint:       CollectProduct,
+	EnabledByDefault: true,
+	Description:      "Collect Product data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/product_convertor.go b/plugins/zentao/tasks/product_convertor.go
new file mode 100644
index 000000000..afbb94631
--- /dev/null
+++ b/plugins/zentao/tasks/product_convertor.go
@@ -0,0 +1,92 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+)
+
+var _ core.SubTaskEntryPoint = ConvertProducts
+
+var ConvertProductMeta = core.SubTaskMeta{
+	Name:             "convertProducts",
+	EntryPoint:       ConvertProducts,
+	EnabledByDefault: true,
+	Description:      "convert Zentao products",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertProducts(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoProduct{}),
+		dal.Where(`_tool_zentao_products.id = ? and 
+			_tool_zentao_products.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoProduct{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProductId:    data.Options.ProductId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_PRODUCT_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolProduct := inputRow.(*models.ZentaoProduct)
+
+			domainBoard := &ticket.Board{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: boardIdGen.Generate(toolProduct.ConnectionId, toolProduct.Id),
+				},
+				Name:        toolProduct.Name,
+				Description: toolProduct.Description,
+				CreatedDate: toolProduct.CreatedDate.ToNullableTime(),
+				Type:        toolProduct.Type,
+			}
+
+			results := make([]interface{}, 0)
+			results = append(results, domainBoard)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/product_extractor.go b/plugins/zentao/tasks/product_extractor.go
new file mode 100644
index 000000000..88428e37b
--- /dev/null
+++ b/plugins/zentao/tasks/product_extractor.go
@@ -0,0 +1,101 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractProducts
+
+var ExtractProductMeta = core.SubTaskMeta{
+	Name:             "extractProducts",
+	EntryPoint:       ExtractProducts,
+	EnabledByDefault: true,
+	Description:      "extract Zentao products",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractProducts(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProductId:    data.Options.ProductId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_PRODUCT_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			res := &models.ZentaoProductRes{}
+			err := json.Unmarshal(row.Data, res)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao product extractor")
+			}
+			product := &models.ZentaoProduct{
+				ConnectionId:   data.Options.ConnectionId,
+				Id:             int64(res.ID),
+				Program:        res.Program,
+				Name:           res.Name,
+				Code:           res.Code,
+				Bind:           res.Bind,
+				Line:           res.Line,
+				Type:           res.Type,
+				Status:         res.Status,
+				SubStatus:      res.SubStatus,
+				Description:    res.Description,
+				POId:           getAccountId(res.PO),
+				QDId:           getAccountId(res.QD),
+				RDId:           getAccountId(res.RD),
+				Acl:            res.Acl,
+				Reviewer:       res.Reviewer,
+				CreatedById:    getAccountId(res.CreatedBy),
+				CreatedDate:    res.CreatedDate,
+				CreatedVersion: res.CreatedVersion,
+				OrderIn:        res.OrderIn,
+				Deleted:        res.Deleted,
+				Plans:          res.Plans,
+				Releases:       res.Releases,
+				Builds:         res.Builds,
+				Cases:          res.Cases,
+				Projects:       res.Projects,
+				Executions:     res.Executions,
+				Bugs:           res.Bugs,
+				Docs:           res.Docs,
+				Progress:       res.Progress,
+				CaseReview:     res.CaseReview,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, product)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/project_collector.go b/plugins/zentao/tasks/project_collector.go
new file mode 100644
index 000000000..6d737a0b5
--- /dev/null
+++ b/plugins/zentao/tasks/project_collector.go
@@ -0,0 +1,79 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_PROJECT_TABLE = "zentao_api_projects"
+
+var _ core.SubTaskEntryPoint = CollectProject
+
+func CollectProject(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProductId:    data.Options.ProductId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_PROJECT_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "projects",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Projects []json.RawMessage `json:"projects"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			return data.Projects, err
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectProjectMeta = core.SubTaskMeta{
+	Name:             "CollectProject",
+	EntryPoint:       CollectProject,
+	EnabledByDefault: true,
+	Description:      "Collect Project data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/project_extractor.go b/plugins/zentao/tasks/project_extractor.go
new file mode 100644
index 000000000..ce1463471
--- /dev/null
+++ b/plugins/zentao/tasks/project_extractor.go
@@ -0,0 +1,69 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractProjects
+
+var ExtractProjectMeta = core.SubTaskMeta{
+	Name:             "extractProjects",
+	EntryPoint:       ExtractProjects,
+	EnabledByDefault: true,
+	Description:      "extract Zentao projects",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractProjects(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProductId:    data.Options.ProductId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_PROJECT_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			project := &models.ZentaoProject{}
+			err := json.Unmarshal(row.Data, project)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao project executor")
+			}
+			project.ConnectionId = data.Options.ConnectionId
+			results := make([]interface{}, 0)
+			results = append(results, project)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/shared.go b/plugins/zentao/tasks/shared.go
new file mode 100644
index 000000000..39fc86318
--- /dev/null
+++ b/plugins/zentao/tasks/shared.go
@@ -0,0 +1,50 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"net/http"
+
+	"github.com/apache/incubator-devlake/plugins/helper"
+)
+
+func GetTotalPagesFromResponse(res *http.Response, args *helper.ApiCollectorArgs) (int, errors.Error) {
+	body := &ZentaoPagination{}
+	err := helper.UnmarshalResponse(res, body)
+	if err != nil {
+		return 0, err
+	}
+	return body.Page, nil
+
+}
+
+func getAccountId(account *models.ZentaoAccount) int64 {
+	if account != nil {
+		return account.ID
+	}
+	return 0
+}
+
+func getAccountName(account *models.ZentaoAccount) string {
+	if account != nil {
+		return account.Realname
+	}
+	return ""
+}
diff --git a/plugins/zentao/tasks/story_collector.go b/plugins/zentao/tasks/story_collector.go
new file mode 100644
index 000000000..368e375d3
--- /dev/null
+++ b/plugins/zentao/tasks/story_collector.go
@@ -0,0 +1,83 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_STORY_TABLE = "zentao_api_stories"
+
+var _ core.SubTaskEntryPoint = CollectStory
+
+func CollectStory(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_STORY_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "/products/{{ .Params.ProductId }}/stories",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			query.Set("status", "allstory")
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Story []json.RawMessage `json:"stories"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector")
+			}
+			return data.Story, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectStoryMeta = core.SubTaskMeta{
+	Name:             "CollectStory",
+	EntryPoint:       CollectStory,
+	EnabledByDefault: true,
+	Description:      "Collect Story data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/story_convertor.go b/plugins/zentao/tasks/story_convertor.go
new file mode 100644
index 000000000..15726f0cb
--- /dev/null
+++ b/plugins/zentao/tasks/story_convertor.go
@@ -0,0 +1,117 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+	"strconv"
+)
+
+var _ core.SubTaskEntryPoint = ConvertStory
+
+var ConvertStoryMeta = core.SubTaskMeta{
+	Name:             "convertStory",
+	EntryPoint:       ConvertStory,
+	EnabledByDefault: true,
+	Description:      "convert Zentao story",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertStory(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	storyIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStory{})
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoStory{}),
+		dal.Where(`_tool_zentao_stories.product = ? and 
+			_tool_zentao_stories.connection_id = ?`, data.Options.ProductId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoStory{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_STORY_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolEntity := inputRow.(*models.ZentaoStory)
+
+			domainEntity := &ticket.Issue{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: storyIdGen.Generate(toolEntity.ConnectionId, toolEntity.ID),
+				},
+				IssueKey:       strconv.FormatInt(toolEntity.ID, 10),
+				Title:          toolEntity.Title,
+				Type:           ticket.REQUIREMENT,
+				OriginalStatus: toolEntity.Stage,
+				ResolutionDate: toolEntity.ClosedDate.ToNullableTime(),
+				CreatedDate:    toolEntity.OpenedDate.ToNullableTime(),
+				UpdatedDate:    toolEntity.LastEditedDate.ToNullableTime(),
+				ParentIssueId:  storyIdGen.Generate(data.Options.ConnectionId, toolEntity.Parent),
+				Priority:       string(rune(toolEntity.Pri)),
+				CreatorId:      strconv.FormatInt(toolEntity.OpenedById, 10),
+				CreatorName:    toolEntity.OpenedByName,
+				AssigneeId:     strconv.FormatInt(toolEntity.AssignedToId, 10),
+				AssigneeName:   toolEntity.AssignedToName,
+			}
+			switch toolEntity.Stage {
+			case "closed":
+				domainEntity.Status = ticket.DONE
+			case "wait":
+				domainEntity.Status = ticket.TODO
+			default:
+				domainEntity.Status = ticket.IN_PROGRESS
+			}
+			if toolEntity.ClosedDate != nil {
+				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
+			}
+			domainBoardIssue := &ticket.BoardIssue{
+				BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ProductId),
+				IssueId: domainEntity.Id,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainEntity, domainBoardIssue)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/story_extractor.go b/plugins/zentao/tasks/story_extractor.go
new file mode 100644
index 000000000..9495b9b63
--- /dev/null
+++ b/plugins/zentao/tasks/story_extractor.go
@@ -0,0 +1,125 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractStory
+
+var ExtractStoryMeta = core.SubTaskMeta{
+	Name:             "extractStory",
+	EntryPoint:       ExtractStory,
+	EnabledByDefault: true,
+	Description:      "extract Zentao story",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractStory(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_STORY_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			res := &models.ZentaoStoryRes{}
+			err := json.Unmarshal(row.Data, res)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			story := &models.ZentaoStory{
+				ConnectionId:     data.Options.ConnectionId,
+				ID:               res.ID,
+				Product:          res.Product,
+				Branch:           res.Branch,
+				Version:          res.Version,
+				OrderIn:          0,
+				Vision:           res.Vision,
+				Parent:           res.Parent,
+				Module:           res.Module,
+				Plan:             res.Plan,
+				Source:           res.Source,
+				SourceNote:       res.SourceNote,
+				FromBug:          res.FromBug,
+				Feedback:         res.Feedback,
+				Title:            res.Title,
+				Keywords:         res.Keywords,
+				Type:             res.Type,
+				Category:         res.Category,
+				Pri:              res.Pri,
+				Estimate:         res.Estimate,
+				Status:           res.Status,
+				SubStatus:        res.SubStatus,
+				Color:            res.Color,
+				Stage:            res.Stage,
+				Lib:              res.Lib,
+				FromStory:        res.FromStory,
+				FromVersion:      res.FromVersion,
+				OpenedById:       getAccountId(res.OpenedBy),
+				OpenedByName:     getAccountName(res.OpenedBy),
+				OpenedDate:       res.OpenedDate,
+				AssignedToId:     getAccountId(res.AssignedTo),
+				AssignedToName:   getAccountName(res.AssignedTo),
+				AssignedDate:     res.AssignedDate,
+				ApprovedDate:     res.ApprovedDate,
+				LastEditedId:     getAccountId(res.LastEditedBy),
+				LastEditedDate:   res.LastEditedDate,
+				ChangedDate:      res.ChangedDate,
+				ReviewedById:     getAccountId(res.ReviewedBy),
+				ReviewedDate:     res.ReviewedDate,
+				ClosedId:         getAccountId(res.ClosedBy),
+				ClosedDate:       res.ClosedDate,
+				ClosedReason:     res.ClosedReason,
+				ActivatedDate:    res.ActivatedDate,
+				ToBug:            res.ToBug,
+				ChildStories:     res.ChildStories,
+				LinkStories:      res.LinkStories,
+				LinkRequirements: res.LinkRequirements,
+				DuplicateStory:   res.DuplicateStory,
+				StoryChanged:     res.StoryChanged,
+				FeedbackBy:       res.FeedbackBy,
+				NotifyEmail:      res.NotifyEmail,
+				URChanged:        res.URChanged,
+				Deleted:          res.Deleted,
+				PriOrder:         res.PriOrder,
+				PlanTitle:        res.PlanTitle,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, story)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/tasks/task_collector.go b/plugins/zentao/tasks/task_collector.go
new file mode 100644
index 000000000..8094870cf
--- /dev/null
+++ b/plugins/zentao/tasks/task_collector.go
@@ -0,0 +1,82 @@
+/*
+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 (
+	"encoding/json"
+	"fmt"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"net/http"
+	"net/url"
+)
+
+const RAW_TASK_TABLE = "zentao_api_tasks"
+
+var _ core.SubTaskEntryPoint = CollectTask
+
+func CollectTask(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	collector, err := helper.NewApiCollector(helper.ApiCollectorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_TASK_TABLE,
+		},
+		ApiClient: data.ApiClient,
+
+		PageSize: 100,
+		// TODO write which api would you want request
+		UrlTemplate: "/executions/{{ .Params.ExecutionId }}/tasks",
+		Query: func(reqData *helper.RequestData) (url.Values, errors.Error) {
+			query := url.Values{}
+			query.Set("page", fmt.Sprintf("%v", reqData.Pager.Page))
+			query.Set("limit", fmt.Sprintf("%v", reqData.Pager.Size))
+			return query, nil
+		},
+		GetTotalPages: GetTotalPagesFromResponse,
+		ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
+			var data struct {
+				Task []json.RawMessage `json:"tasks"`
+			}
+			err := helper.UnmarshalResponse(res, &data)
+			if err != nil {
+				return nil, errors.Default.Wrap(err, "error reading endpoint response by Zentao bug collector")
+			}
+			return data.Task, nil
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	return collector.Execute()
+}
+
+var CollectTaskMeta = core.SubTaskMeta{
+	Name:             "CollectTask",
+	EntryPoint:       CollectTask,
+	EnabledByDefault: true,
+	Description:      "Collect Task data from Zentao api",
+}
diff --git a/plugins/zentao/tasks/task_convertor.go b/plugins/zentao/tasks/task_convertor.go
new file mode 100644
index 000000000..cccedaa16
--- /dev/null
+++ b/plugins/zentao/tasks/task_convertor.go
@@ -0,0 +1,119 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package tasks
+
+import (
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/models/domainlayer"
+	"github.com/apache/incubator-devlake/models/domainlayer/didgen"
+	"github.com/apache/incubator-devlake/models/domainlayer/ticket"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/core/dal"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+	"reflect"
+	"strconv"
+)
+
+var _ core.SubTaskEntryPoint = ConvertTask
+
+var ConvertTaskMeta = core.SubTaskMeta{
+	Name:             "convertTask",
+	EntryPoint:       ConvertTask,
+	EnabledByDefault: true,
+	Description:      "convert Zentao task",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ConvertTask(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	db := taskCtx.GetDal()
+	storyIdGen := didgen.NewDomainIdGenerator(&models.ZentaoStory{})
+	boardIdGen := didgen.NewDomainIdGenerator(&models.ZentaoProduct{})
+	taskIdGen := didgen.NewDomainIdGenerator(&models.ZentaoTask{})
+	cursor, err := db.Cursor(
+		dal.From(&models.ZentaoTask{}),
+		dal.Where(`_tool_zentao_tasks.execution = ? and 
+			_tool_zentao_tasks.connection_id = ?`, data.Options.ExecutionId, data.Options.ConnectionId),
+	)
+	if err != nil {
+		return err
+	}
+	defer cursor.Close()
+	convertor, err := helper.NewDataConverter(helper.DataConverterArgs{
+		InputRowType: reflect.TypeOf(models.ZentaoTask{}),
+		Input:        cursor,
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_TASK_TABLE,
+		},
+		Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
+			toolEntity := inputRow.(*models.ZentaoTask)
+
+			domainEntity := &ticket.Issue{
+				DomainEntity: domainlayer.DomainEntity{
+					Id: taskIdGen.Generate(toolEntity.ConnectionId, toolEntity.ID),
+				},
+				IssueKey:       strconv.FormatInt(toolEntity.ID, 10),
+				Title:          toolEntity.Name,
+				Description:    toolEntity.Description,
+				Type:           "TASK",
+				OriginalStatus: toolEntity.Status,
+				ResolutionDate: toolEntity.ClosedDate.ToNullableTime(),
+				CreatedDate:    toolEntity.OpenedDate.ToNullableTime(),
+				UpdatedDate:    toolEntity.LastEditedDate.ToNullableTime(),
+				ParentIssueId:  storyIdGen.Generate(data.Options.ConnectionId, toolEntity.Parent),
+				Priority:       string(rune(toolEntity.Pri)),
+				CreatorId:      strconv.FormatInt(toolEntity.OpenedById, 10),
+				CreatorName:    toolEntity.OpenedByName,
+				AssigneeId:     strconv.FormatInt(toolEntity.AssignedToId, 10),
+				AssigneeName:   toolEntity.AssignedToName,
+			}
+			switch toolEntity.Status {
+			case "done", "closed", "cancel":
+				domainEntity.Status = ticket.DONE
+			case "wait":
+				domainEntity.Status = ticket.TODO
+			default:
+				domainEntity.Status = ticket.IN_PROGRESS
+			}
+			if toolEntity.ClosedDate != nil {
+				domainEntity.LeadTimeMinutes = int64(toolEntity.ClosedDate.ToNullableTime().Sub(toolEntity.OpenedDate.ToTime()).Minutes())
+			}
+			domainBoardIssue := &ticket.BoardIssue{
+				BoardId: boardIdGen.Generate(data.Options.ConnectionId, data.Options.ProductId),
+				IssueId: domainEntity.Id,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, domainEntity, domainBoardIssue)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return convertor.Execute()
+}
diff --git a/plugins/zentao/tasks/task_data.go b/plugins/zentao/tasks/task_data.go
new file mode 100644
index 000000000..aa75c84f4
--- /dev/null
+++ b/plugins/zentao/tasks/task_data.go
@@ -0,0 +1,62 @@
+/*
+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"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/mitchellh/mapstructure"
+)
+
+type ZentaoApiParams struct {
+	ConnectionId uint64
+	ProductId    int64
+	ExecutionId  int64
+	ProjectId    int64
+}
+
+type ZentaoOptions struct {
+	// TODO add some custom options here if necessary
+	// options means some custom params required by plugin running.
+	// Such As How many rows do your want
+	// You can use it in sub tasks and you need pass it in main.go and pipelines.
+	ConnectionId uint64 `json:"connectionId"`
+	ProductId    int64
+	ExecutionId  int64
+	ProjectId    int64
+	Tasks        []string `json:"tasks,omitempty"`
+	Since        string
+}
+
+type ZentaoTaskData struct {
+	Options   *ZentaoOptions
+	ApiClient *helper.ApiAsyncClient
+}
+
+func DecodeAndValidateTaskOptions(options map[string]interface{}) (*ZentaoOptions, error) {
+	var op ZentaoOptions
+	err := mapstructure.Decode(options, &op)
+	if err != nil {
+		return nil, err
+	}
+
+	if op.ConnectionId == 0 {
+		return nil, fmt.Errorf("connectionId is invalid")
+	}
+	return &op, nil
+}
diff --git a/plugins/zentao/tasks/task_extractor.go b/plugins/zentao/tasks/task_extractor.go
new file mode 100644
index 000000000..df1318950
--- /dev/null
+++ b/plugins/zentao/tasks/task_extractor.go
@@ -0,0 +1,138 @@
+/*
+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 (
+	"encoding/json"
+	"github.com/apache/incubator-devlake/errors"
+	"github.com/apache/incubator-devlake/plugins/core"
+	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/zentao/models"
+)
+
+var _ core.SubTaskEntryPoint = ExtractTask
+
+var ExtractTaskMeta = core.SubTaskMeta{
+	Name:             "extractTask",
+	EntryPoint:       ExtractTask,
+	EnabledByDefault: true,
+	Description:      "extract Zentao task",
+	DomainTypes:      []string{core.DOMAIN_TYPE_TICKET},
+}
+
+func ExtractTask(taskCtx core.SubTaskContext) errors.Error {
+	data := taskCtx.GetData().(*ZentaoTaskData)
+	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
+		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
+			Ctx: taskCtx,
+			Params: ZentaoApiParams{
+				ConnectionId: data.Options.ConnectionId,
+				ProductId:    data.Options.ProductId,
+				ExecutionId:  data.Options.ExecutionId,
+				ProjectId:    data.Options.ProjectId,
+			},
+			Table: RAW_TASK_TABLE,
+		},
+		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
+			res := &models.ZentaoTaskRes{}
+			err := json.Unmarshal(row.Data, res)
+			if err != nil {
+				return nil, errors.Default.WrapRaw(err)
+			}
+			task := &models.ZentaoTask{
+				ConnectionId:       data.Options.ConnectionId,
+				ExecutionId:        data.Options.ExecutionId,
+				ID:                 res.Id,
+				Project:            res.Project,
+				Parent:             res.Parent,
+				Execution:          res.Execution,
+				Module:             res.Module,
+				Design:             res.Design,
+				Story:              res.Story,
+				StoryVersion:       res.StoryVersion,
+				DesignVersion:      res.DesignVersion,
+				FromBug:            res.FromBug,
+				Feedback:           res.Feedback,
+				FromIssue:          res.FromIssue,
+				Name:               res.Name,
+				Type:               res.Type,
+				Mode:               res.Mode,
+				Pri:                res.Pri,
+				Estimate:           res.Estimate,
+				Consumed:           res.Consumed,
+				Deadline:           res.Deadline,
+				Status:             res.Status,
+				SubStatus:          res.SubStatus,
+				Color:              res.Color,
+				Description:        res.Description,
+				Version:            res.Version,
+				OpenedById:         getAccountId(res.OpenedBy),
+				OpenedByName:       getAccountName(res.OpenedBy),
+				OpenedDate:         res.OpenedDate,
+				AssignedToId:       getAccountId(res.AssignedTo),
+				AssignedToName:     getAccountName(res.AssignedTo),
+				AssignedDate:       res.AssignedDate,
+				EstStarted:         res.EstStarted,
+				RealStarted:        res.RealStarted,
+				FinishedId:         getAccountId(res.FinishedBy),
+				FinishedDate:       res.FinishedDate,
+				FinishedList:       res.FinishedList,
+				CanceledId:         getAccountId(res.CanceledBy),
+				CanceledDate:       res.CanceledDate,
+				ClosedById:         getAccountId(res.ClosedBy),
+				ClosedDate:         res.ClosedDate,
+				PlanDuration:       res.PlanDuration,
+				RealDuration:       res.RealDuration,
+				ClosedReason:       res.ClosedReason,
+				LastEditedId:       getAccountId(res.LastEditedBy),
+				LastEditedDate:     res.LastEditedDate,
+				ActivatedDate:      res.ActivatedDate,
+				OrderIn:            res.OrderIn,
+				Repo:               res.Repo,
+				Mr:                 res.Mr,
+				Entry:              res.Entry,
+				NumOfLine:          res.NumOfLine,
+				V1:                 res.V1,
+				V2:                 res.V2,
+				Deleted:            res.Deleted,
+				Vision:             res.Vision,
+				StoryID:            res.Story,
+				StoryTitle:         res.StoryTitle,
+				LatestStoryVersion: 0,
+				//Product:            getAccountId(res.Product),
+				//Branch:             res.Branch,
+				//LatestStoryVersion: res.LatestStoryVersion,
+				//StoryStatus:        res.StoryStatus,
+				AssignedToRealName: res.AssignedToRealName,
+				PriOrder:           res.PriOrder,
+				NeedConfirm:        res.NeedConfirm,
+				//ProductType:        res.ProductType,
+				Progress: res.Progress,
+			}
+			results := make([]interface{}, 0)
+			results = append(results, task)
+			return results, nil
+		},
+	})
+
+	if err != nil {
+		return err
+	}
+
+	return extractor.Execute()
+}
diff --git a/plugins/zentao/zentao.go b/plugins/zentao/zentao.go
new file mode 100644
index 000000000..11d73050a
--- /dev/null
+++ b/plugins/zentao/zentao.go
@@ -0,0 +1,47 @@
+/*
+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 main
+
+import (
+	"github.com/apache/incubator-devlake/plugins/zentao/impl"
+	"github.com/apache/incubator-devlake/runner"
+	"github.com/spf13/cobra"
+)
+
+// PluginEntry Export a variable named PluginEntry for Framework to search and load
+var PluginEntry impl.Zentao //nolint
+
+// standalone mode for debugging
+func main() {
+	cmd := &cobra.Command{Use: "zentao"}
+
+	connectionId := cmd.Flags().Uint64P("connectionId", "c", 0, "zentao connection id")
+	executionId := cmd.Flags().IntP("executionId", "e", 8, "execution id")
+	productId := cmd.Flags().IntP("productId", "o", 8, "product id")
+	projectId := cmd.Flags().IntP("projectId", "p", 8, "project id")
+
+	cmd.Run = func(cmd *cobra.Command, args []string) {
+		runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
+			"connectionId": *connectionId,
+			"executionId":  *executionId,
+			"productId":    *productId,
+			"projectId":    *projectId,
+		})
+	}
+	runner.RunCmd(cmd)
+}