You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by wa...@apache.org on 2022/11/22 06:23:37 UTC
[incubator-devlake] branch release-v0.14 updated: feat(zentao): add zentao (#3777)
This is an automated email from the ASF dual-hosted git repository.
warren pushed a commit to branch release-v0.14
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git
The following commit(s) were added to refs/heads/release-v0.14 by this push:
new ae5bbfc16 feat(zentao): add zentao (#3777)
ae5bbfc16 is described below
commit ae5bbfc16bcac61e9b3ea714ad3a1a20464afc7a
Author: Warren Chen <yi...@merico.dev>
AuthorDate: Tue Nov 22 14:23:33 2022 +0800
feat(zentao): add zentao (#3777)
---
.../src/components/Sidebar/MenuConfiguration.jsx | 10 +
.../src/components/blueprints/ConnectionDialog.jsx | 6 +
.../blueprints/ProviderTransformationSettings.jsx | 13 ++
.../create-workflow/DataTransformations.jsx | 2 +-
.../src/components/menus/PipelineConfigsMenu.jsx | 10 +
.../src/components/pipelines/StageTaskName.jsx | 1 +
config-ui/src/data/Providers.js | 28 +++
config-ui/src/data/availablePlugins.js | 2 +-
config-ui/src/data/integrations.jsx | 22 ++
.../zentao.js} | 15 +-
config-ui/src/hooks/useConnectionManager.jsx | 3 +
config-ui/src/hooks/useDataScopesManager.jsx | 27 ++-
config-ui/src/hooks/usePipelineManager.jsx | 3 +-
config-ui/src/hooks/usePipelineValidation.jsx | 3 +-
config-ui/src/images/integrations/zentao.svg | 4 +
.../src/pages/blueprints/blueprint-settings.jsx | 3 +
.../pages/configure/connections/AddConnection.jsx | 4 +-
.../configure/connections/ConfigureConnection.jsx | 6 +-
.../pages/configure/connections/ConnectionForm.jsx | 3 +-
config-ui/src/pages/configure/settings/zentao.jsx | 73 +++++++
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 | 11 +
.../zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv | 5 +
.../e2e/raw_tables/_raw_zentao_api_departments.csv | 11 +
.../e2e/raw_tables/_raw_zentao_api_executions.csv | 3 +
.../raw_tables/_raw_zentao_api_executions_real.csv | 2 +
.../e2e/raw_tables/_raw_zentao_api_products.csv | 2 +
.../e2e/raw_tables/_raw_zentao_api_stories.csv | 8 +
.../e2e/raw_tables/_raw_zentao_api_tasks.csv | 4 +
.../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 | 1 +
.../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 | 1 +
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 | 143 +++++++++++++
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 | 67 ++++++
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 +++++
105 files changed, 5559 insertions(+), 11 deletions(-)
diff --git a/config-ui/src/components/Sidebar/MenuConfiguration.jsx b/config-ui/src/components/Sidebar/MenuConfiguration.jsx
index 7b3528a41..01bf66b52 100644
--- a/config-ui/src/components/Sidebar/MenuConfiguration.jsx
+++ b/config-ui/src/components/Sidebar/MenuConfiguration.jsx
@@ -89,6 +89,16 @@ const MenuConfiguration = (activeRoute) => {
activeRoute.url.endsWith('/webhook'),
icon: 'layers',
classNames: []
+ },
+ {
+ id: 6,
+ label: `${ProviderLabels.ZENTAO} (beta)`,
+ route: '/integrations/zentao',
+ active:
+ activeRoute.url.endsWith('/integrations/zentao') ||
+ activeRoute.url.endsWith('/zentao'),
+ icon: 'layers',
+ classNames: []
}
]
},
diff --git a/config-ui/src/components/blueprints/ConnectionDialog.jsx b/config-ui/src/components/blueprints/ConnectionDialog.jsx
index 43ddd8c5f..c7acc1c93 100644
--- a/config-ui/src/components/blueprints/ConnectionDialog.jsx
+++ b/config-ui/src/components/blueprints/ConnectionDialog.jsx
@@ -80,6 +80,12 @@ const DATA_SOURCES_LIST = [
name: Providers.TAPD,
title: ProviderLabels[Providers.TAPD.toUpperCase()],
value: Providers.TAPD
+ },
+ {
+ id: 6,
+ name: Providers.ZENTAO,
+ title: ProviderLabels[Providers.ZENTAO.toUpperCase()],
+ value: Providers.ZENTAO
}
]
diff --git a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
index 5ecf56bea..4d6c81fd4 100644
--- a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
+++ b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
@@ -28,6 +28,7 @@ import JiraSettings from '@/pages/configure/settings/jira'
import GitlabSettings from '@/pages/configure/settings/gitlab'
import JenkinsSettings from '@/pages/configure/settings/jenkins'
import TapdSettings from '@/pages/configure/settings/tapd'
+import ZentaoSettings from '@/pages/configure/settings/zentao'
import GithubSettings from '@/pages/configure/settings/github'
const ProviderTransformationSettings = (props) => {
@@ -135,6 +136,18 @@ const ProviderTransformationSettings = (props) => {
isSavingConnection={isSavingConnection}
/>
)}
+ {provider?.id === Providers.ZENTAO && (
+ <ZentaoSettings
+ provider={provider}
+ connection={connection}
+ transformation={transformation}
+ entityIdKey={entityIdKey}
+ onSettingsChange={onSettingsChange}
+ entities={entities[connection?.id]}
+ isSaving={isSaving}
+ isSavingConnection={isSavingConnection}
+ />
+ )}
</div>
)
}
diff --git a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
index 145813681..e98b23824 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -115,7 +115,7 @@ const DataTransformations = (props) => {
const noTransformationsAvailable = useMemo(
() =>
- [Providers.TAPD].includes(configuredConnection?.provider) ||
+ [Providers.TAPD].includes(configuredConnection?.provider) || [Providers.ZENTAO].includes(configuredConnection?.provider) ||
([Providers.GITLAB].includes(configuredConnection?.provider) &&
dataEntities[configuredConnection?.id].every(
(e) => e.value !== DataEntityTypes.DEVOPS
diff --git a/config-ui/src/components/menus/PipelineConfigsMenu.jsx b/config-ui/src/components/menus/PipelineConfigsMenu.jsx
index d45a4312e..7ab45d2c0 100644
--- a/config-ui/src/components/menus/PipelineConfigsMenu.jsx
+++ b/config-ui/src/components/menus/PipelineConfigsMenu.jsx
@@ -24,6 +24,7 @@ import { githubConfig as sampleGithubPipelineConfig } from '@/data/pipeline-conf
import { gitlabConfig as sampleGitlabPipelineConfig } from '@/data/pipeline-config-samples/gitlab'
import { jiraConfig as sampleJiraPipelineConfig } from '@/data/pipeline-config-samples/jira'
import { jenkinsConfig as sampleJenkinsPipelineConfig } from '@/data/pipeline-config-samples/jenkins'
+import { zentaoConfig as sampleZentaoPipelineConfig } from '@/data/pipeline-config-samples/zentao'
import { feishuConfig as sampleFeishuPipelineConfig } from '@/data/pipeline-config-samples/feishu'
import { dbtConfig as sampleDbtPipelineConfig } from '@/data/pipeline-config-samples/dbt'
import { starRocksConfig as sampleStarRocksConfigPipelineConfig } from '@/data/pipeline-config-samples/starrocks'
@@ -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 8ad33d189..4ff31a528 100644
--- a/config-ui/src/components/pipelines/StageTaskName.jsx
+++ b/config-ui/src/components/pipelines/StageTaskName.jsx
@@ -117,6 +117,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 9c4f4d7e6..3d9248fac 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 GitExtractorIcon from '@/images/git.png'
// import RefDiffIcon from '@/images/git-diff.png'
import FeishuIcon from '@/images/feishu.png'
@@ -41,6 +42,7 @@ const Providers = {
DBT: 'dbt',
STARROCKS: 'starrocks',
TAPD: 'tapd',
+ ZENTAO: 'zentao',
DORA: 'dora'
}
@@ -63,6 +65,7 @@ const ProviderLabels = {
DBT: 'Data Build Tool (DBT)',
STARROCKS: 'StarRocks',
TAPD: 'TAPD',
+ ZENTAO: 'ZENTAO',
DORA: 'DORA'
}
@@ -125,6 +128,19 @@ const ProviderFormLabels = {
</>
)
},
+ zentao: {
+ name: 'Connection Name',
+ endpoint: 'Endpoint URL',
+ proxy: 'Proxy URL',
+ token: 'Basic Auth Token',
+ username: 'Username',
+ password: 'Password',
+ rateLimitPerHour: (
+ <>
+ Rate Limit <sup>(per hour)</sup>
+ </>
+ )
+ },
jira: {
name: 'Connection Name',
endpoint: 'Endpoint URL',
@@ -242,6 +258,15 @@ const ProviderFormPlaceholders = {
password: 'eg. ************',
rateLimitPerHour: '1000'
},
+ zentao: {
+ name: 'eg. Zentao',
+ endpoint: 'URL eg. http://domain:port/api.php/v1/',
+ proxy: 'eg. http://proxy.localhost:8080',
+ token: 'eg. 6b057ffe68464c93a057',
+ username: 'eg. admin',
+ password: 'eg. ************',
+ rateLimitPerHour: '1000'
+ },
jira: {
name: 'eg. JIRA',
endpoint: 'eg. https://your-domain.atlassian.net/rest/',
@@ -272,6 +297,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 78abcbd1a..9e6807a03 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 GitExtractorProvider from '@/images/git.png'
// import RefDiffProvider from '@/images/git-diff.png'
// import { ReactComponent as NullProvider } from '@/images/integrations/null.svg'
@@ -98,6 +99,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 81%
copy from config-ui/src/data/availablePlugins.js
copy to config-ui/src/data/pipeline-config-samples/zentao.js
index 4433f79cb..b1f78545a 100644
--- a/config-ui/src/data/availablePlugins.js
+++ b/config-ui/src/data/pipeline-config-samples/zentao.js
@@ -15,6 +15,17 @@
* limitations under the License.
*
*/
-const AVAILABLE_PLUGINS = ['gitlab', 'jira', 'jenkins', 'github', 'tapd']
+const zentaoConfig = [
+ [
+ {
+ plugin: 'zentao',
+ options: {
+ connectionId: 1,
+ productId: 1,
+ executionId: 1
+ }
+ }
+ ]
+]
-module.exports = AVAILABLE_PLUGINS
+export { zentaoConfig }
diff --git a/config-ui/src/hooks/useConnectionManager.jsx b/config-ui/src/hooks/useConnectionManager.jsx
index 99fd64f51..7b1450ab4 100644
--- a/config-ui/src/hooks/useConnectionManager.jsx
+++ b/config-ui/src/hooks/useConnectionManager.jsx
@@ -364,6 +364,9 @@ function useConnectionManager(
),
request.get(
`${DEVLAKE_ENDPOINT}/plugins/${Providers.TAPD}/connections`
+ ),
+ request.get(
+ `${DEVLAKE_ENDPOINT}/plugins/${Providers.ZENTAO}/connections`
)
])
const builtConnections = aC.map((providerResponse) =>
diff --git a/config-ui/src/hooks/useDataScopesManager.jsx b/config-ui/src/hooks/useDataScopesManager.jsx
index d96cb2d0c..ec4a1b31d 100644
--- a/config-ui/src/hooks/useDataScopesManager.jsx
+++ b/config-ui/src/hooks/useDataScopesManager.jsx
@@ -190,6 +190,18 @@ function useDataScopesManager({
// testingPattern: ''
}
break
+ case Providers.ZENTAO:
+ // @todo: complete tapd transforms #2673
+ transforms = {
+ issueTypeRequirement: '',
+ issueTypeBug: '',
+ issueTypeIncident: '',
+ productionPattern: '',
+ deploymentPattern: ''
+ // stagingPattern: '',
+ // testingPattern: ''
+ }
+ break
}
console.log(
'>>>>> DATA SCOPES MANAGER: Getting Default Transformation Values for PROVIDER Type ',
@@ -280,6 +292,14 @@ function useDataScopesManager({
// transformation: {},
}
break
+ case Providers.ZENTAO:
+ newScope = {
+ ...newScope
+ // options: {
+ // },
+ // transformation: {},
+ }
+ break
}
return Array.isArray(newScope) ? newScope.flat() : [newScope]
},
@@ -491,7 +511,10 @@ function useDataScopesManager({
entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
break
case Providers.TAPD:
- entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
+ entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'issue-tracking')
+ break
+ case Providers.ZENTAO:
+ entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'issue-tracking')
break
}
return entities
@@ -721,6 +744,8 @@ function useDataScopesManager({
break
case Providers.TAPD:
break
+ case Providers.ZENTAO:
+ break
}
}, [provider])
diff --git a/config-ui/src/hooks/usePipelineManager.jsx b/config-ui/src/hooks/usePipelineManager.jsx
index aa1a60f89..703b2c87a 100644
--- a/config-ui/src/hooks/usePipelineManager.jsx
+++ b/config-ui/src/hooks/usePipelineManager.jsx
@@ -57,7 +57,8 @@ function usePipelineManager(
Providers.FEISHU,
Providers.AE,
Providers.DBT,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
])
const PIPELINES_ENDPOINT = useMemo(() => `${DEVLAKE_ENDPOINT}/pipelines`, [])
diff --git a/config-ui/src/hooks/usePipelineValidation.jsx b/config-ui/src/hooks/usePipelineValidation.jsx
index f1cd789b7..b6102dd11 100644
--- a/config-ui/src/hooks/usePipelineValidation.jsx
+++ b/config-ui/src/hooks/usePipelineValidation.jsx
@@ -57,7 +57,8 @@ function usePipelineValidation({
Providers.AE,
Providers.DBT,
Providers.STARROCKS,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
])
const clear = () => {
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 cb1c9355c..b1f862d5d 100644
--- a/config-ui/src/pages/blueprints/blueprint-settings.jsx
+++ b/config-ui/src/pages/blueprints/blueprint-settings.jsx
@@ -549,6 +549,9 @@ const BlueprintSettings = (props) => {
case Providers.TAPD:
isValid = entities[configuredConnection?.id]?.length > 0
break
+ case Providers.ZENTAO:
+ isValid = entities[configuredConnection?.id]?.length > 0
+ break
default:
isValid = true
}
diff --git a/config-ui/src/pages/configure/connections/AddConnection.jsx b/config-ui/src/pages/configure/connections/AddConnection.jsx
index bf1df872f..173d71e06 100644
--- a/config-ui/src/pages/configure/connections/AddConnection.jsx
+++ b/config-ui/src/pages/configure/connections/AddConnection.jsx
@@ -118,6 +118,7 @@ export default function AddConnection() {
case Providers.GITLAB:
case Providers.JIRA:
case Providers.TAPD:
+ case Providers.ZENTAO:
default:
setName('')
break
@@ -212,7 +213,8 @@ export default function AddConnection() {
[
Providers.JENKINS,
Providers.JIRA,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
].includes(activeProvider.id)
? 'plain'
: 'token'
diff --git a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
index 9c0c2ce9a..6a37bca03 100644
--- a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
+++ b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
@@ -262,7 +262,8 @@ export default function ConfigureConnection() {
{[
Providers.GITLAB,
Providers.JIRA,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
].includes(activeProvider.id) && (
<h2 style={{ margin: 0 }}>
#{activeConnection?.id} {activeConnection.name}
@@ -341,7 +342,8 @@ export default function ConfigureConnection() {
[
Providers.JENKINS,
Providers.JIRA,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
].includes(activeProvider.id)
? 'plain'
: 'token'
diff --git a/config-ui/src/pages/configure/connections/ConnectionForm.jsx b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
index f4349ead0..2c10892b8 100644
--- a/config-ui/src/pages/configure/connections/ConnectionForm.jsx
+++ b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
@@ -745,7 +745,8 @@ export default function ConnectionForm(props) {
Providers.GITLAB,
Providers.JIRA,
Providers.JENKINS,
- Providers.TAPD
+ Providers.TAPD,
+ Providers.ZENTAO
].includes(activeProvider?.id) && (
<>
<div className='formContainer'>
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..a76ea7dae
--- /dev/null
+++ b/config-ui/src/pages/configure/settings/zentao.jsx
@@ -0,0 +1,73 @@
+/*
+ * 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 { DataEntityTypes } from '@/data/DataEntities'
+
+import '@/styles/integration.scss'
+import '@/styles/connections.scss'
+
+export default function ZentaoSettings(props) {
+ const {
+ provider,
+ connection,
+ entities = [],
+ onSettingsChange = () => {}
+ } = props
+ const history = useHistory()
+ const { providerId, connectionId } = useParams()
+
+ // eslint-disable-next-line max-len
+ const [errors, setErrors] = useState([])
+
+ const cancel = () => {
+ history.push(`/integrations/${provider.id}`)
+ }
+
+ // useEffect(() => {
+ // setErrors(['This integration doesn’t require any configuration.'])
+ // }, [])
+
+ useEffect(() => {
+ onSettingsChange({
+ errors,
+ providerId,
+ connectionId
+ })
+ }, [errors, onSettingsChange, connectionId, providerId])
+
+ 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/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..0ea9a752f
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_accounts.csv
@@ -0,0 +1,11 @@
+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
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..8ff72a6bc
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_bugs.csv
@@ -0,0 +1,5 @@
+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\ [...]
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..a4faf38ae
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_departments.csv
@@ -0,0 +1,11 @@
+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
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..7df7b5849
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_executions.csv
@@ -0,0 +1,3 @@
+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" [...]
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..ccfdbc5d5
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_products.csv
@@ -0,0 +1,2 @@
+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"":"" [...]
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..b346e0450
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_stories.csv
@@ -0,0 +1,8 @@
+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" [...]
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..3d9e27181
--- /dev/null
+++ b/plugins/zentao/e2e/raw_tables/_raw_zentao_api_tasks.csv
@@ -0,0 +1,4 @@
+id,params,data,url,input,created_at
+1,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":1,""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"":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"":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 [...]
+4,"{""ConnectionId"":1,""ProductId"":3,""ExecutionId"":1,""ProjectId"":1}","{""id"":3,""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"":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..c2c174cef
--- /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,9,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,9,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,0,9,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..5500305c8
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/board_issues_task.csv
@@ -0,0 +1 @@
+board_id,issue_id
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..06752d1b5
--- /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,首页设计和开发,,,story,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,新闻中心的设计和开发。,,,story,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,成果展示的设计和开发,,,story,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,售后服务的设计和开发,,,story,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,诚聘英才的设计和开发,,,story,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,合作洽谈的设计和开发,,,story,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,关于我们的设计和开发,,,story,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..37eaa7ab1
--- /dev/null
+++ b/plugins/zentao/e2e/snapshot_tables/issues_task.csv
@@ -0,0 +1 @@
+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
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..a4ca7a9c1
--- /dev/null
+++ b/plugins/zentao/impl/impl.go
@@ -0,0 +1,143 @@
+/*
+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/migration"
+ "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() []migration.Script {
+ 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..98fb5cf6c
--- /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 uint64 `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 uint64 `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..87c2a009c
--- /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 uint64 `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 uint64 `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..364d319f6
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `json:"project"`
+ Product uint64 `json:"product"`
+ Injection int `json:"injection"`
+ Identify int `json:"identify"`
+ Branch int `json:"branch"`
+ Module int `json:"module"`
+ Execution uint64 `json:"execution"`
+ Plan int `json:"plan"`
+ Story uint64 `json:"story"`
+ StoryVersion int `json:"storyVersion"`
+ Task int `json:"task"`
+ ToTask int `json:"toTask"`
+ ToStory uint64 `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 uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ OpenedBuild string `json:"openedBuild"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ Deadline string `json:"deadline"`
+ ResolvedById uint64
+ Resolution string `json:"resolution"`
+ ResolvedBuild string `json:"resolvedBuild"`
+ ResolvedDate *helper.Iso8601Time `json:"resolvedDate"`
+ ClosedById uint64
+ 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 uint64
+ 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..5a89da666
--- /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 uint64 `json:"id"`
+ ZentaoConnection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+ Id uint64
+ 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..70beab771
--- /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 uint64 `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..7eb8c6a0a
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `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 uint64 `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 string `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 uint64
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ OpenedVersion string `json:"openedVersion"`
+ LastEditedById uint64
+ LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+ ClosedById uint64
+ ClosedDate *helper.Iso8601Time `json:"closedDate"`
+ CanceledById uint64
+ CanceledDate *helper.Iso8601Time `json:"canceledDate"`
+ SuspendedDate *helper.Iso8601Time `json:"suspendedDate"`
+ POId uint64
+ PMId uint64
+ QDId uint64
+ RDId uint64
+ 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 uint64
+ 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..36d4e6655
--- /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 uint64 `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 uint64
+ QDId uint64
+ RDId uint64
+ Acl string `json:"acl"`
+ Reviewer string `json:"reviewer"`
+ CreatedById uint64
+ 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..fcd2314b0
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `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 uint64 `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 string `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 uint64 `json:"id"`
+ PmAccount string `json:"account"`
+ PmAvatar string `json:"avatar"`
+ PmRealname string `json:"realname"`
+}
+type Whitelist []struct {
+ WhitelistID uint64 `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..65fc7b950
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL" `
+ Project uint64 `json:"project"`
+ Product uint64 `json:"product"`
+ Branch int `json:"branch"`
+ Version int `json:"version"`
+ OrderIn int `json:"order"`
+ Vision string `json:"vision"`
+ Parent uint64 `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 uint64 `json:"stagedBy"`
+ //Mailto []interface{} `json:"mailto"`
+ Lib int `json:"lib"`
+ FromStory uint64 `json:"fromStory"`
+ FromVersion int `json:"fromVersion"`
+ OpenedById uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ ApprovedDate *helper.Iso8601Time `json:"approvedDate"`
+ LastEditedId uint64
+ LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+ ChangedDate *helper.Iso8601Time `json:"changedDate"`
+ ReviewedById uint64 `json:"reviewedBy"`
+ ReviewedDate *helper.Iso8601Time `json:"reviewedDate"`
+ ClosedId uint64
+ 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 uint64 `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..9942d575a
--- /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 uint64 `json:"execution_id"`
+ ID uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `json:"project"`
+ Parent uint64 `json:"parent"`
+ Execution uint64 `json:"execution"`
+ Module int `json:"module"`
+ Design int `json:"design"`
+ Story uint64 `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 uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ EstStarted string `json:"estStarted"`
+ RealStarted *helper.Iso8601Time `json:"realStarted"`
+ FinishedId uint64
+ FinishedDate *helper.Iso8601Time `json:"finishedDate"`
+ FinishedList string `json:"finishedList"`
+ CanceledId uint64
+ CanceledDate *helper.Iso8601Time `json:"canceledDate"`
+ ClosedById uint64
+ ClosedDate *helper.Iso8601Time `json:"closedDate"`
+ PlanDuration int `json:"planDuration"`
+ RealDuration int `json:"realDuration"`
+ ClosedReason string `json:"closedReason"`
+ LastEditedId uint64
+ 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 uint64 `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..da39ee41f
--- /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 uint64 `json:"id"`
+ Project uint64 `json:"project"`
+ Product uint64 `json:"product"`
+ Injection int `json:"injection"`
+ Identify int `json:"identify"`
+ Branch int `json:"branch"`
+ Module int `json:"module"`
+ Execution uint64 `json:"execution"`
+ Plan int `json:"plan"`
+ Story uint64 `json:"story"`
+ StoryVersion int `json:"storyVersion"`
+ Task int `json:"task"`
+ ToTask int `json:"toTask"`
+ ToStory uint64 `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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `json:"project"`
+ Product uint64 `json:"product"`
+ Injection int `json:"injection"`
+ Identify int `json:"identify"`
+ Branch int `json:"branch"`
+ Module int `json:"module"`
+ Execution uint64 `json:"execution"`
+ Plan int `json:"plan"`
+ Story uint64 `json:"story"`
+ StoryVersion int `json:"storyVersion"`
+ Task int `json:"task"`
+ ToTask int `json:"toTask"`
+ ToStory uint64 `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 uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ OpenedBuild string `json:"openedBuild"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ Deadline string `json:"deadline"`
+ ResolvedById uint64
+ Resolution string `json:"resolution"`
+ ResolvedBuild string `json:"resolvedBuild"`
+ ResolvedDate *helper.Iso8601Time `json:"resolvedDate"`
+ ClosedById uint64
+ 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 uint64
+ 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..e25f0c3d5
--- /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 uint64 `json:"id"`
+ ZentaoConnection
+}
+
+// Using User because it requires authentication.
+type ApiUserResponse struct {
+ Id uint64
+ 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..036e4289e
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL" `
+ Name string `json:"name" gorm:"type:varchar(100);index"`
+ Parent uint64 `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..52db4d498
--- /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 uint64 `json:"id"`
+ Project uint64 `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 uint64 `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 uint64 `json:"id"`
+ Project uint64 `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 uint64 `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 string `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 uint64 `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 uint64 `json:"userID"`
+ Realname string `json:"realname"`
+ } `json:"teamMembers"`
+ Products []struct {
+ ID uint64 `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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `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 uint64 `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 uint64
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ OpenedVersion string `json:"openedVersion"`
+ LastEditedById uint64
+ LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+ ClosedById uint64
+ ClosedDate *helper.Iso8601Time `json:"closedDate"`
+ CanceledById uint64
+ CanceledDate *helper.Iso8601Time `json:"canceledDate"`
+ SuspendedDate *helper.Iso8601Time `json:"suspendedDate"`
+ POId uint64
+ PMId uint64
+ QDId uint64
+ RDId uint64
+ 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 uint64
+ 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..112a3ea17
--- /dev/null
+++ b/plugins/zentao/models/migrationscripts/20221121_add_init_tables.go
@@ -0,0 +1,67 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements. See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package migrationscripts
+
+import (
+ "context"
+ "github.com/apache/incubator-devlake/errors"
+ "github.com/apache/incubator-devlake/plugins/zentao/models/archived"
+ "gorm.io/gorm"
+)
+
+type addInitTables struct{}
+
+func (*addInitTables) Up(ctx context.Context, db *gorm.DB) errors.Error {
+ err := db.Migrator().DropTable(
+ &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 errors.Convert(err)
+ }
+
+ return errors.Convert(db.Migrator().AutoMigrate(
+ &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..c1365f7d4
--- /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/migration"
+)
+
+// All return all the migration scripts
+func All() []migration.Script {
+ return []migration.Script{
+ new(addInitTables),
+ }
+}
diff --git a/plugins/zentao/models/product.go b/plugins/zentao/models/product.go
new file mode 100644
index 000000000..85c57b2bd
--- /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 uint64 `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 uint64 `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 uint64
+ QDId uint64
+ RDId uint64
+ Acl string `json:"acl"`
+ Reviewer string `json:"reviewer"`
+ CreatedById uint64
+ 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..ef854d0a6
--- /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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `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 uint64 `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 string `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 uint64 `json:"id"`
+ PmAccount string `json:"account"`
+ PmAvatar string `json:"avatar"`
+ PmRealname string `json:"realname"`
+}
+type Whitelist []struct {
+ WhitelistID uint64 `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..8df779f0a
--- /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 uint64 `json:"id"`
+ Vision string `json:"vision"`
+ Parent uint64 `json:"parent"`
+ Product uint64 `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 uint64 `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 uint64 `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 uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL" `
+ Product uint64 `json:"product"`
+ Branch int `json:"branch"`
+ Version int `json:"version"`
+ OrderIn int `json:"order"`
+ Vision string `json:"vision"`
+ Parent uint64 `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 uint64 `json:"fromStory"`
+ FromVersion int `json:"fromVersion"`
+ OpenedById uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ ApprovedDate *helper.Iso8601Time `json:"approvedDate"`
+ LastEditedId uint64
+ LastEditedDate *helper.Iso8601Time `json:"lastEditedDate"`
+ ChangedDate *helper.Iso8601Time `json:"changedDate"`
+ ReviewedById uint64 `json:"reviewedBy"`
+ ReviewedDate *helper.Iso8601Time `json:"reviewedDate"`
+ ClosedId uint64
+ 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 uint64 `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..4f9be832f
--- /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 uint64 `json:"id"`
+ Project uint64 `json:"project"`
+ Parent uint64 `json:"parent"`
+ Execution uint64 `json:"execution"`
+ Module int `json:"module"`
+ Design int `json:"design"`
+ Story uint64 `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 uint64 `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 uint64 `json:"execution_id"`
+ ID uint64 `json:"id" gorm:"primaryKey;type:BIGINT NOT NULL"`
+ Project uint64 `json:"project"`
+ Parent uint64 `json:"parent"`
+ Execution uint64 `json:"execution"`
+ Module int `json:"module"`
+ Design int `json:"design"`
+ Story uint64 `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 uint64
+ OpenedByName string
+ OpenedDate *helper.Iso8601Time `json:"openedDate"`
+ AssignedToId uint64
+ AssignedToName string
+ AssignedDate *helper.Iso8601Time `json:"assignedDate"`
+ EstStarted string `json:"estStarted"`
+ RealStarted *helper.Iso8601Time `json:"realStarted"`
+ FinishedId uint64
+ FinishedDate *helper.Iso8601Time `json:"finishedDate"`
+ FinishedList string `json:"finishedList"`
+ CanceledId uint64
+ CanceledDate *helper.Iso8601Time `json:"canceledDate"`
+ ClosedById uint64
+ ClosedDate *helper.Iso8601Time `json:"closedDate"`
+ PlanDuration int `json:"planDuration"`
+ RealDuration int `json:"realDuration"`
+ ClosedReason string `json:"closedReason"`
+ LastEditedId uint64
+ 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 uint64 `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..882a1fe2a
--- /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.FormatUint(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.FormatUint(toolEntity.OpenedById, 10),
+ CreatorName: toolEntity.OpenedByName,
+ AssigneeId: strconv.FormatUint(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..d5d38582d
--- /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: uint64(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..eb6afafe2
--- /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) uint64 {
+ 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..391346b92
--- /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.FormatUint(toolEntity.ID, 10),
+ Title: toolEntity.Title,
+ Type: toolEntity.Type,
+ 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.FormatUint(toolEntity.OpenedById, 10),
+ CreatorName: toolEntity.OpenedByName,
+ AssigneeId: strconv.FormatUint(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..d719c36bc
--- /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.FormatUint(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.FormatUint(toolEntity.OpenedById, 10),
+ CreatorName: toolEntity.OpenedByName,
+ AssigneeId: strconv.FormatUint(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..3a3d8ac74
--- /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 uint64
+ ExecutionId uint64
+ ProjectId uint64
+}
+
+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 uint64
+ ExecutionId uint64
+ ProjectId uint64
+ 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)
+}