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
+ <a href='#' style={{ textDecoration: 'underline' }} onClick={cancel}>
+ add other data connections
+ </a>
+ 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, ¶ms)
+ 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>[结果]出现乱码 </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>[步骤]进入成果展示 </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)
+}