feat(frontend): add tapd config-ui

Signed-off-by: Yingchu Chen <yingchu.chen@merico.dev>
diff --git a/config-ui/src/components/Sidebar/MenuConfiguration.jsx b/config-ui/src/components/Sidebar/MenuConfiguration.jsx
index c6fad4d..a291c47 100644
--- a/config-ui/src/components/Sidebar/MenuConfiguration.jsx
+++ b/config-ui/src/components/Sidebar/MenuConfiguration.jsx
@@ -59,6 +59,14 @@
           active: activeRoute.url.endsWith('/integrations/jenkins') || activeRoute.url.endsWith('/jenkins'),
           icon: 'layers',
           classNames: [],
+        },
+        {
+          id: 4,
+          label: ProviderLabels.TAPD,
+          route: '/integrations/tapd',
+          active: activeRoute.url.endsWith('/integrations/tapd') || activeRoute.url.endsWith('/tapd'),
+          icon: 'layers',
+          classNames: [],
         }
       ]
     },
diff --git a/config-ui/src/components/blueprints/ConnectionDialog.jsx b/config-ui/src/components/blueprints/ConnectionDialog.jsx
index 05079dc..0424562 100644
--- a/config-ui/src/components/blueprints/ConnectionDialog.jsx
+++ b/config-ui/src/components/blueprints/ConnectionDialog.jsx
@@ -75,6 +75,12 @@
     title: ProviderLabels[Providers.JENKINS.toUpperCase()],
     value: Providers.JENKINS,
   },
+  {
+    id: 5,
+    name: Providers.TAPD,
+    title: ProviderLabels[Providers.TAPD.toUpperCase()],
+    value: Providers.TAPD,
+  },
 ]
 
 const ConnectionDialog = (props) => {
diff --git a/config-ui/src/components/pipelines/StageTaskName.jsx b/config-ui/src/components/pipelines/StageTaskName.jsx
index 8061039..cb84f88 100644
--- a/config-ui/src/components/pipelines/StageTaskName.jsx
+++ b/config-ui/src/components/pipelines/StageTaskName.jsx
@@ -92,6 +92,7 @@
                   {task.plugin === Providers.GITEXTRACTOR && (<>{ProviderLabels.GITEXTRACTOR}</>)}
                   {task.plugin === Providers.FEISHU && (<>{ProviderLabels.FEISHU}</>)}
                   {task.plugin === Providers.JENKINS && (<>{ProviderLabels.JENKINS}</>)}
+                  {task.plugin === Providers.TAPD && (<>{ProviderLabels.TAPD}</>)}
                   {task.plugin === Providers.JIRA && (<>Board ID {task.options.boardId}</>)}
                   {task.plugin === Providers.GITLAB && (<>Project ID {task.options.projectId}</>)}
                   {task.plugin === Providers.GITHUB && task.plugin !== Providers.JENKINS && (<>@{task.options.owner}/{task.options.repo}</>)}
diff --git a/config-ui/src/data/Providers.js b/config-ui/src/data/Providers.js
index e7db313..b5c0f41 100644
--- a/config-ui/src/data/Providers.js
+++ b/config-ui/src/data/Providers.js
@@ -21,6 +21,7 @@
 import { ReactComponent as JenkinsProviderIcon } from '@/images/integrations/jenkins.svg'
 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 GitExtractorIcon from '@/images/git.png'
 // import RefDiffIcon from '@/images/git-diff.png'
 import FeishuIcon from '@/images/feishu.png'
@@ -39,6 +40,7 @@
   AE: 'ae',
   DBT: 'dbt',
   STARROCKS: 'starrocks',
+  TAPD: 'tapd',
 }
 
 const ProviderTypes = {
@@ -59,6 +61,7 @@
   AE: 'Analysis Engine (AE)',
   DBT: 'Data Build Tool (DBT)',
   STARROCKS: 'StarRocks',
+  TAPD: 'Tapd',
 }
 
 const ProviderConnectionLimits = {
@@ -96,11 +99,18 @@
     username: 'Username',
     password: 'Password'
   },
-  jira: {
+  tapd: {
     name: 'Connection Name',
     endpoint: 'Endpoint URL',
     proxy: 'Proxy URL',
     token: 'Basic Auth Token',
+    username: 'Username',
+    password: 'Password',
+  },
+  jira: {
+    name: 'Connection Name',
+    endpoint: 'Endpoint URL',
+    token: 'Basic Auth Token',
     username: 'Username / E-mail',
     // password; 'Password',
     password: (
@@ -181,6 +191,14 @@
     username: 'eg. admin',
     password: 'eg. ************'
   },
+  tapd: {
+    name: 'eg. Tapd',
+    endpoint: 'URL eg. https://api.tapd.cn/',
+    proxy: 'eg. http://proxy.localhost:8080',
+    token: 'eg. 6b057ffe68464c93a057',
+    username: 'eg. admin',
+    password: 'eg. ************'
+  },
   jira: {
     name: 'eg. JIRA',
     endpoint: 'eg. https://your-domain.atlassian.net/rest/',
@@ -202,6 +220,7 @@
 const ProviderIcons = {
   [Providers.GITLAB]: (w, h) => <GitlabProviderIcon width={w || 24} height={h || 24} />,
   [Providers.JENKINS]: (w, h) => <JenkinsProviderIcon width={w || 24} height={h || 24} />,
+  [Providers.TAPD]: (w, h) => <TapdProviderIcon width={w || 24} height={h || 24} />,
   [Providers.JIRA]: (w, h) => <JiraProviderIcon width={w || 24} height={h || 24} />,
   [Providers.GITHUB]: (w, h) => <GitHubProviderIcon width={w || 24} height={h || 24} />,
   [Providers.REFDIFF]: (w, h) => <Icon icon='box' size={w || 24} />,
diff --git a/config-ui/src/data/availablePlugins.js b/config-ui/src/data/availablePlugins.js
index 6827c54..4433f79 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']
+const AVAILABLE_PLUGINS = ['gitlab', 'jira', 'jenkins', 'github', 'tapd']
 
 module.exports = AVAILABLE_PLUGINS
diff --git a/config-ui/src/data/integrations.jsx b/config-ui/src/data/integrations.jsx
index a63a0ae..10134c7 100644
--- a/config-ui/src/data/integrations.jsx
+++ b/config-ui/src/data/integrations.jsx
@@ -28,6 +28,7 @@
 import { ReactComponent as JenkinsProvider } from '@/images/integrations/jenkins.svg'
 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 GitExtractorProvider from '@/images/git.png'
 // import RefDiffProvider from '@/images/git-diff.png'
 // import { ReactComponent as NullProvider } from '@/images/integrations/null.svg'
@@ -70,6 +71,24 @@
     )
   },
   {
+    id: Providers.TAPD,
+    type: ProviderTypes.INTEGRATION,
+    enabled: true,
+    multiConnection: true,
+    name: ProviderLabels.TAPD,
+    icon: <TapdProvider className='providerIconSvg' width='30' height='30' style={{ float: 'left', marginTop: '5px' }} />,
+    iconDashboard: <TapdProvider className='providerIconSvg' width='40' height='40' />,
+    settings: ({ activeProvider, activeConnection, isSaving, isSavingConnection, setSettings }) => (
+      <JenkinsSettings
+        provider={activeProvider}
+        connection={activeConnection}
+        isSaving={isSaving}
+        isSavingConnection={isSavingConnection}
+        onSettingsChange={setSettings}
+      />
+    )
+  },
+  {
     id: Providers.JIRA,
     type: ProviderTypes.INTEGRATION,
     enabled: true,
diff --git a/config-ui/src/hooks/useConnectionManager.jsx b/config-ui/src/hooks/useConnectionManager.jsx
index aa762d6..37e2c03 100644
--- a/config-ui/src/hooks/useConnectionManager.jsx
+++ b/config-ui/src/hooks/useConnectionManager.jsx
@@ -149,6 +149,16 @@
           ...connectionPayload,
         }
         break
+      case Providers.TAPD:
+        connectionPayload = {
+          name: name,
+          endpoint: endpointUrl,
+          username: username,
+          password: password,
+          proxy: proxy,
+          ...connectionPayload,
+        }
+        break
       case Providers.GITHUB:
         connectionPayload = {
           name: name,
diff --git a/config-ui/src/hooks/usePipelineManager.jsx b/config-ui/src/hooks/usePipelineManager.jsx
index b2d7f8e..5f29e41 100644
--- a/config-ui/src/hooks/usePipelineManager.jsx
+++ b/config-ui/src/hooks/usePipelineManager.jsx
@@ -53,7 +53,8 @@
     Providers.GITEXTRACTOR,
     Providers.FEISHU,
     Providers.AE,
-    Providers.DBT
+    Providers.DBT,
+    Providers.TAPD
   ])
 
   const PIPELINES_ENDPOINT = useMemo(() => `${DEVLAKE_ENDPOINT}/pipelines`, [DEVLAKE_ENDPOINT])
diff --git a/config-ui/src/hooks/usePipelineValidation.jsx b/config-ui/src/hooks/usePipelineValidation.jsx
index 43e8609..a8e10d7 100644
--- a/config-ui/src/hooks/usePipelineValidation.jsx
+++ b/config-ui/src/hooks/usePipelineValidation.jsx
@@ -56,7 +56,8 @@
     Providers.FEISHU,
     Providers.AE,
     Providers.DBT,
-    Providers.STARROCKS
+    Providers.STARROCKS,
+    Providers.TAPD
   ])
 
   const clear = () => {
diff --git a/config-ui/src/images/integrations/tapd.svg b/config-ui/src/images/integrations/tapd.svg
new file mode 100644
index 0000000..3481500
--- /dev/null
+++ b/config-ui/src/images/integrations/tapd.svg
@@ -0,0 +1,6 @@
+<svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M30.1151 10.3491L27.9087 3.35706L4.95635 10.3491L6.96429 17.3253L15.0198 15.0396L19.4008 29.492L26.5992 27.246L22.1389 12.8491L30.1151 10.3491Z" fill="#7497F7"/>
+<path d="M2.25 33.6666H15.2659L2.25 12.0317V33.6666Z" fill="#7497F7"/>
+<path d="M33.0119 2.33325L27.2103 33.6666H33.75V2.33325H33.0119Z" fill="#7497F7"/>
+<path d="M2.25 2.33325V8.6904L23.6944 2.33325H2.25Z" fill="#7497F7"/>
+</svg>
diff --git a/config-ui/src/pages/blueprints/create-blueprint.jsx b/config-ui/src/pages/blueprints/create-blueprint.jsx
index df0aad2..560da91 100644
--- a/config-ui/src/pages/blueprints/create-blueprint.jsx
+++ b/config-ui/src/pages/blueprints/create-blueprint.jsx
@@ -494,6 +494,7 @@
     switch (configuredConnection.provider) {
       case Providers.GITLAB:
       case Providers.JIRA:
+      case Providers.TAPD:
       case Providers.GITHUB:
         items = dataEntitiesList.filter((d) => d.name !== 'ci-cd')
         break
@@ -550,6 +551,11 @@
             ...newScope,
           }
           break
+        case Providers.TAPD:
+          newScope = {
+            ...newScope,
+          }
+          break
         case Providers.GITHUB:
           newScope = projects[connection.id]?.map((p) => ({
             ...newScope,
@@ -905,6 +911,9 @@
         case Providers.JENKINS:
           // No Transform Settings...
           break
+        case Providers.TAPD:
+          // No Transform Settings...
+          break
         case Providers.GITLAB:
           // No Transform Settings...
           break
diff --git a/config-ui/src/pages/configure/connections/AddConnection.jsx b/config-ui/src/pages/configure/connections/AddConnection.jsx
index 7013609..4ad749b 100644
--- a/config-ui/src/pages/configure/connections/AddConnection.jsx
+++ b/config-ui/src/pages/configure/connections/AddConnection.jsx
@@ -112,6 +112,7 @@
         case Providers.GITHUB:
         case Providers.GITLAB:
         case Providers.JIRA:
+        case Providers.TAPD:
         default:
           setName('')
           break
@@ -184,7 +185,7 @@
                   allTestResponses={allTestResponses}
                   errors={errors}
                   showError={showError}
-                  authType={[Providers.JENKINS, Providers.JIRA].includes(activeProvider.id) ? 'plain' : 'token'}
+                  authType={[Providers.JENKINS, Providers.JIRA, Providers.TAPD].includes(activeProvider.id) ? 'plain' : 'token'}
                   sourceLimits={ProviderConnectionLimits}
                   labels={ProviderFormLabels[activeProvider.id]}
                   placeholders={ProviderFormPlaceholders[activeProvider.id]}
diff --git a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
index 5234f5c..bbc60ba 100644
--- a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
+++ b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
@@ -216,7 +216,7 @@
                     </div>
                     {activeConnection && (
                       <>
-                        {[Providers.GITLAB, Providers.JIRA].includes(activeProvider.id) &&
+                        {[Providers.GITLAB, Providers.JIRA, Providers.TAPD].includes(activeProvider.id) &&
                         (<h2 style={{ margin: 0 }}>#{activeConnection.ID} {activeConnection.name}</h2>)}
                         <p className='page-description'>Manage settings and options for this connection.</p>
                       </>
@@ -255,7 +255,7 @@
                             username={username}
                             password={password}
                             // JIRA and GITLAB are multi-connection plugins, for now we intentially won't include additional settings during save...
-                            onSave={() => saveConnection(![Providers.GITLAB, Providers.JIRA].includes(activeProvider.id) ? settings : {})}
+                            onSave={() => saveConnection(![Providers.GITLAB, Providers.JIRA,Providers.TAPD].includes(activeProvider.id) ? settings : {})}
                             onTest={testConnection}
                             onCancel={cancel}
                             onValidate={validate}
@@ -272,7 +272,7 @@
                             allTestResponses={allTestResponses}
                             errors={errors}
                             showError={showConnectionError}
-                            authType={[Providers.JENKINS, Providers.JIRA].includes(activeProvider.id) ? 'plain' : 'token'}
+                            authType={[Providers.JENKINS, Providers.JIRA, Providers.TAPD].includes(activeProvider.id) ? 'plain' : 'token'}
                             showLimitWarning={false}
                             sourceLimits={ProviderConnectionLimits}
                             labels={ProviderFormLabels[activeProvider.id]}
diff --git a/config-ui/src/pages/configure/connections/ConnectionForm.jsx b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
index dc9cf9a..c929fa5 100644
--- a/config-ui/src/pages/configure/connections/ConnectionForm.jsx
+++ b/config-ui/src/pages/configure/connections/ConnectionForm.jsx
@@ -702,7 +702,7 @@
             </div>
           </>
         )}
-        {[Providers.GITHUB, Providers.GITLAB, Providers.JIRA].includes(
+        {[Providers.GITHUB, Providers.GITLAB, Providers.JIRA, Providers.TAPD].includes(
           activeProvider.id
         ) && (
           <div className='formContainer'>
diff --git a/config-ui/src/pages/configure/connections/EditConnection.jsx b/config-ui/src/pages/configure/connections/EditConnection.jsx
index 2631170..d1bfc37 100644
--- a/config-ui/src/pages/configure/connections/EditConnection.jsx
+++ b/config-ui/src/pages/configure/connections/EditConnection.jsx
@@ -76,6 +76,7 @@
     setEndpointUrl(activeConnection.endpoint)
     switch (activeProvider.id) {
       case Providers.JENKINS:
+      case Providers.TAPD:
       case Providers.JIRA:
         setUsername(activeConnection.username)
         setPassword(activeConnection.password)
@@ -155,7 +156,7 @@
                   testStatus={testStatus}
                   errors={errors}
                   showError={showError}
-                  authType={[Providers.JENKINS, Providers.JIRA].includes(activeProvider.id) ? 'plain' : 'token'}
+                  authType={[Providers.JENKINS, Providers.JIRA, Providers.TAPD].includes(activeProvider.id) ? 'plain' : 'token'}
                   sourceLimits={ProviderConnectionLimits}
                   labels={ProviderFormLabels[activeProvider.id]}
                   placeholders={ProviderFormPlaceholders[activeProvider.id]}
diff --git a/config-ui/src/pages/pipelines/activity.jsx b/config-ui/src/pages/pipelines/activity.jsx
index c127e94..015244f 100644
--- a/config-ui/src/pages/pipelines/activity.jsx
+++ b/config-ui/src/pages/pipelines/activity.jsx
@@ -44,6 +44,7 @@
 import CodeInspector from '@/components/pipelines/CodeInspector'
 import { ReactComponent as GitlabProviderIcon } from '@/images/integrations/gitlab.svg'
 import { ReactComponent as JenkinsProviderIcon } from '@/images/integrations/jenkins.svg'
+import { ReactComponent as TapdProviderIcon } from '@/images/integrations/tapd.svg'
 import { ReactComponent as JiraProviderIcon } from '@/images/integrations/jira.svg'
 import { ReactComponent as GitHubProviderIcon } from '@/images/integrations/github.svg'
 import { ReactComponent as HelpIcon } from '@/images/help.svg'
@@ -507,6 +508,24 @@
                         </div>
                       </div>
                     )}
+                    {pipelineHasProvider(Providers.TAPD) && (
+                      <div className='jenkins-settings' style={{ display: 'flex' }}>
+                        <div style={{ display: 'flex', padding: '2px 6px' }}>
+                          <TapdProviderIcon width={24} height={24} />
+                        </div>
+                        <div>
+                          <label style={{ lineHeight: '100%', display: 'block', fontSize: '10px', marginTop: '2px', marginBottom: '10px' }}>
+                            <strong style={{
+                              fontSize: '16px',
+                              fontWeight: 800
+                            }}
+                            >{ProviderLabels.TAPD}
+                            </strong><br />Auto-configured
+                          </label>
+                          <span style={{ color: Colors.GRAY3 }}>(No Settings)</span>
+                        </div>
+                      </div>
+                    )}
                     {pipelineHasProvider(Providers.JIRA) && (
                       <div className='jira-settings' style={{ display: 'flex', paddingLeft: '20px' }}>
                         <div style={{ display: 'flex', padding: '2px 6px' }}>
diff --git a/plugins/tapd/api/connection.go b/plugins/tapd/api/connection.go
index ec1767c..138f3ce 100644
--- a/plugins/tapd/api/connection.go
+++ b/plugins/tapd/api/connection.go
@@ -36,38 +36,37 @@
 */
 func TestConnection(input *core.ApiResourceInput) (*core.ApiResourceOutput, error) {
 	// process input
-	var params models.TestConnectionRequest
-	err := mapstructure.Decode(input.Body, &params)
+	var connection models.TestConnectionRequest
+	err := mapstructure.Decode(input.Body, &connection)
 	if err != nil {
 		return nil, err
 	}
-	err = vld.Struct(params)
+	err = vld.Struct(connection)
 	if err != nil {
 		return nil, err
 	}
 
 	// verify multiple token in parallel
 	// PLEASE NOTE: This works because GitHub API Client rotates tokens on each request
-	token := params.Auth
 	apiClient, err := helper.NewApiClient(
 		context.TODO(),
-		params.Endpoint,
+		connection.Endpoint,
 		map[string]string{
-			"Authorization": fmt.Sprintf("Basic %s", token),
+			"Authorization": fmt.Sprintf("Basic %s", connection.GetEncodedToken()),
 		},
 		3*time.Second,
-		params.Proxy,
+		connection.Proxy,
 		basicRes,
 	)
 	if err != nil {
-		return nil, fmt.Errorf("verify token failed for %s %w", token, err)
+		return nil, fmt.Errorf("verify token failed for %s %w", connection.Username, err)
 	}
 	res, err := apiClient.Get("/quickstart/testauth", nil, nil)
 	if err != nil {
 		return nil, err
 	}
 	if res.StatusCode == http.StatusUnauthorized {
-		return nil, fmt.Errorf("verify token failed for %s", token)
+		return nil, fmt.Errorf("verify token failed for %s", connection.Username)
 	}
 	if res.StatusCode != http.StatusOK {
 		return nil, fmt.Errorf("unexpected status code: %d", res.StatusCode)
diff --git a/plugins/tapd/models/connection.go b/plugins/tapd/models/connection.go
index 5073988..c10f116 100644
--- a/plugins/tapd/models/connection.go
+++ b/plugins/tapd/models/connection.go
@@ -25,9 +25,9 @@
 )
 
 type TestConnectionRequest struct {
-	Endpoint string `json:"endpoint" validate:"required,url"`
-	Auth     string `json:"auth" validate:"required"`
-	Proxy    string `json:"proxy" gorm:"type:varchar(255)"`
+	Endpoint         string `json:"endpoint"`
+	Proxy            string `json:"proxy"`
+	helper.BasicAuth `mapstructure:",squash"`
 }
 
 type WorkspaceResponse struct {
diff --git a/plugins/tapd/tasks/company_extractor.go b/plugins/tapd/tasks/company_extractor.go
index 97bdc67..be7b6fb 100644
--- a/plugins/tapd/tasks/company_extractor.go
+++ b/plugins/tapd/tasks/company_extractor.go
@@ -36,7 +36,7 @@
 }
 
 func ExtractCompanies(taskCtx core.SubTaskContext) error {
-	rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, RAW_COMPANY_TABLE, false)
+	rawDataSubTaskArgs, data := CreateRawDataSubTaskArgs(taskCtx, RAW_COMPANY_TABLE, true)
 	extractor, err := helper.NewApiExtractor(helper.ApiExtractorArgs{
 		RawDataSubTaskArgs: *rawDataSubTaskArgs,
 		Extract: func(row *helper.RawData) ([]interface{}, error) {