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

[incubator-devlake] branch main updated: feat: add jenkins job select in create-blueprint page (#3356)

This is an automated email from the ASF dual-hosted git repository.

likyh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 24b674a6 feat: add jenkins job select in create-blueprint page (#3356)
24b674a6 is described below

commit 24b674a65d662256e19e638d68d38931a2056ae7
Author: Likyh <l...@likyh.com>
AuthorDate: Tue Oct 11 15:03:19 2022 +0800

    feat: add jenkins job select in create-blueprint page (#3356)
    
    * feat: add jenkins job select in create-blueprint page
    
    * feat: add jenkins selector in edit page
    
    * feat: add jobName in backend for jenkins scope
    
    * feat: save jenkins models in different scope by jobName
    
    * fix: fix for review
    
    * fix: fix for lint
    
    * fix: fix for review
    
    Co-authored-by: linyh <ya...@meri.co>
---
 .../blueprints/BlueprintDataScopesDialog.jsx       |  20 ++-
 .../components/blueprints/JenkinsJobsSelector.jsx  | 151 ++++++++++++++++++++
 .../blueprints/ProviderTransformationSettings.jsx  |   3 +-
 .../blueprints/create-workflow/DataScopes.jsx      |  25 ++++
 .../create-workflow/DataTransformations.jsx        |  33 ++---
 config-ui/src/config/jenkinsApiProxy.js            |  23 +++
 config-ui/src/hooks/useBlueprintValidation.jsx     |  16 +--
 config-ui/src/hooks/useDataScopesManager.jsx       | 154 +++++++++------------
 config-ui/src/hooks/useJenkins.jsx                 |  91 ++++++++++++
 config-ui/src/models/JenkinsJob.js                 |  56 ++++++++
 .../src/pages/blueprints/blueprint-settings.jsx    |  28 +++-
 .../src/pages/blueprints/create-blueprint.jsx      |  23 +++
 .../src/pages/configure/integration/manage.jsx     |   4 +-
 config-ui/src/pages/configure/settings/gitlab.jsx  |   1 -
 config-ui/src/pages/configure/settings/jenkins.jsx |   9 +-
 config-ui/src/store/UIContext.jsx                  |  34 ++---
 config-ui/src/styles/libraries/blueprint.scss      |   7 +
 plugins/gitlab/api/proxy.go                        |   2 +-
 plugins/{gitlab => jenkins}/api/proxy.go           |  10 +-
 plugins/jenkins/e2e/builds_test.go                 |   1 +
 plugins/jenkins/e2e/jobs_test.go                   |   1 +
 plugins/jenkins/e2e/stages_test.go                 |   1 +
 plugins/jenkins/impl/impl.go                       |   3 +
 plugins/jenkins/jenkins.go                         |   2 +
 plugins/jenkins/tasks/build_cicd_convertor.go      |   1 +
 plugins/jenkins/tasks/build_collector.go           |   3 +-
 plugins/jenkins/tasks/build_commit_convertor.go    |   1 +
 plugins/jenkins/tasks/build_extractor.go           |  10 +-
 plugins/jenkins/tasks/job_collector.go             |   9 +-
 plugins/jenkins/tasks/job_extractor.go             |   9 +-
 plugins/jenkins/tasks/stage_collector.go           |   5 +-
 plugins/jenkins/tasks/stage_convertor.go           |   1 +
 plugins/jenkins/tasks/stage_extractor.go           |   1 +
 plugins/jenkins/tasks/task_data.go                 |   2 +
 34 files changed, 551 insertions(+), 189 deletions(-)

diff --git a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
index 941e8e6f..44e5e733 100644
--- a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
+++ b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
@@ -77,6 +77,8 @@ const BlueprintDataScopesDialog = (props) => {
     setBoardSearch = () => {},
     gitlabProjects = [],
     fetchGitlabProjects = () => [],
+    fetchJenkinsJobs = () => [],
+    jenkinsJobs = [],
     entities = {},
     projects = {},
     mode = Modes.EDIT,
@@ -112,6 +114,8 @@ const BlueprintDataScopesDialog = (props) => {
     jiraProxyError,
     isFetchingGitlab,
     gitlabProxyError,
+    isFetchingJenkins,
+    jenkinsProxyError,
     errors = [],
     content = null,
     backButtonProps = {
@@ -119,28 +123,32 @@ const BlueprintDataScopesDialog = (props) => {
       intent: Intent.PRIMARY,
       text: 'Previous Step',
       outlined: true,
-      loading: isFetchingJIRA || isFetchingGitlab || isSaving
+      loading:
+        isFetchingJIRA || isFetchingGitlab || isFetchingJenkins || isSaving
     },
     nextButtonProps = {
       disabled: !isValid,
       intent: Intent.PRIMARY,
       text: 'Next Step',
       outlined: true,
-      loading: isFetchingJIRA || isFetchingGitlab || isSaving
+      loading:
+        isFetchingJIRA || isFetchingGitlab || isFetchingJenkins || isSaving
     },
     finalButtonProps = {
       disabled: !isValid,
       intent: Intent.PRIMARY,
       onClick: onSave,
       text: 'Save Changes',
-      loading: isFetchingJIRA || isFetchingGitlab || isSaving
+      loading:
+        isFetchingJIRA || isFetchingGitlab || isFetchingJenkins || isSaving
     },
     closeButtonProps = {
       // disabled:
       intent: Intent.PRIMARY,
       text: 'Cancel',
       outlined: true,
-      loading: isFetchingJIRA || isFetchingGitlab || isSaving
+      loading:
+        isFetchingJIRA || isFetchingGitlab || isFetchingJenkins || isSaving
     }
   } = props
 
@@ -184,6 +192,10 @@ const BlueprintDataScopesDialog = (props) => {
                 gitlabProjects={gitlabProjects}
                 isFetchingGitlab={isFetchingGitlab}
                 gitlabProxyError={gitlabProxyError}
+                fetchJenkinsJobs={fetchJenkinsJobs}
+                jenkinsJobs={jenkinsJobs}
+                isFetchingJenkins={isFetchingJenkins}
+                jenkinsProxyError={jenkinsProxyError}
                 dataEntities={entities}
                 projects={projects}
                 configuredConnection={configuredConnection}
diff --git a/config-ui/src/components/blueprints/JenkinsJobsSelector.jsx b/config-ui/src/components/blueprints/JenkinsJobsSelector.jsx
new file mode 100644
index 00000000..00dfe675
--- /dev/null
+++ b/config-ui/src/components/blueprints/JenkinsJobsSelector.jsx
@@ -0,0 +1,151 @@
+/*
+ * 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 { Intent, MenuItem, Position, Tooltip } from '@blueprintjs/core'
+import { MultiSelect } from '@blueprintjs/select'
+import JenkinsJob from '@/models/JenkinsJob'
+
+const JenkinsJobsSelector = (props) => {
+  const {
+    onFetch = () => [],
+    isFetching = false,
+    configuredConnection,
+    placeholder = 'Select Jobs',
+    items = [],
+    selectedItems = [],
+    activeItem = null,
+    disabled = false,
+    isLoading = false,
+    isSaving = false,
+    onItemSelect = () => {},
+    onRemove = () => {},
+    onClear = () => {},
+    itemRenderer = (item, { handleClick, modifiers }) => (
+      <MenuItem
+        active={modifiers.active}
+        disabled={selectedItems.find((i) => i?.id === item?.id)}
+        key={item.value}
+        onClick={handleClick}
+        text={
+          selectedItems.find((i) => i?.id === item?.id) ? (
+            <>
+              <input type='checkbox' checked readOnly /> {item?.title}
+            </>
+          ) : (
+            <span style={{ fontWeight: 700 }}>
+              <input type='checkbox' readOnly /> {item?.title}
+            </span>
+          )
+        }
+        style={{
+          marginBottom: '2px',
+          fontWeight: items.includes(item) ? 700 : 'normal'
+        }}
+      />
+    ),
+    // eslint-disable-next-line max-len
+    tagRenderer = (item) => (
+      <Tooltip
+        intent={Intent.PRIMARY}
+        content={item?.title}
+        position={Position.TOP}
+      >
+        {item.title}
+      </Tooltip>
+    )
+  } = props
+
+  const [query, setQuery] = useState('')
+  const [filtedItems, setFiltedItems] = useState([])
+
+  useEffect(() => {
+    onFetch()
+  }, [onFetch, configuredConnection?.id])
+
+  useEffect(() => {
+    setFiltedItems(items.filter((v) => v.title.includes(query)))
+  }, [items, query])
+
+  return (
+    <div
+      className='jenkins-jobs-multiselect'
+      style={{ display: 'flex', marginBottom: '10px' }}
+    >
+      <div
+        className='jenkins-jobs-multiselect-selector'
+        style={{ minWidth: '200px', width: '100%' }}
+      >
+        <MultiSelect
+          disabled={disabled || isSaving || isLoading}
+          // openOnKeyDown={true}
+          resetOnSelect={true}
+          placeholder={placeholder}
+          popoverProps={{ usePortal: false, minimal: true }}
+          className='multiselector-jobs'
+          inline={true}
+          fill={true}
+          items={filtedItems}
+          selectedItems={selectedItems}
+          activeItem={activeItem}
+          onQueryChange={(query) => setQuery(query)}
+          itemRenderer={itemRenderer}
+          tagRenderer={tagRenderer}
+          tagInputProps={{
+            tagProps: {
+              intent: Intent.PRIMARY,
+              minimal: true
+            }
+          }}
+          noResults={
+            isFetching ? (
+              <MenuItem disabled={true} text='Fetching...' />
+            ) : (
+              <MenuItem disabled={true} text='No Jobs Available.' />
+            )
+          }
+          onRemove={(item) => {
+            onRemove((rT) => {
+              return {
+                ...rT,
+                [configuredConnection.id]: rT[configuredConnection.id].filter(
+                  (t) => t?.id !== item.id
+                )
+              }
+            })
+          }}
+          onItemSelect={(item) => {
+            onItemSelect((rT) => {
+              return !rT[configuredConnection.id].includes(item)
+                ? {
+                    ...rT,
+                    [configuredConnection.id]: [
+                      ...rT[configuredConnection.id],
+                      new JenkinsJob(item)
+                    ]
+                  }
+                : { ...rT }
+            })
+          }}
+          style={{ borderRight: 0 }}
+        />
+      </div>
+    </div>
+  )
+}
+
+export default JenkinsJobsSelector
diff --git a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
index 5ecf56be..a211e2c9 100644
--- a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
+++ b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
@@ -65,7 +65,6 @@ const ProviderTransformationSettings = (props) => {
           provider={provider}
           connection={connection}
           configuredProject={configuredProject}
-          projects={projects}
           transformation={transformation}
           entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
@@ -80,7 +79,6 @@ const ProviderTransformationSettings = (props) => {
           provider={provider}
           connection={connection}
           configuredProject={configuredProject}
-          projects={projects}
           transformation={transformation}
           entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
@@ -115,6 +113,7 @@ const ProviderTransformationSettings = (props) => {
         <JenkinsSettings
           provider={provider}
           connection={connection}
+          configuredProject={configuredProject}
           transformation={transformation}
           entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
diff --git a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
index 2091dfab..65cb5be4 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
@@ -31,6 +31,7 @@ import DataEntitiesSelector from '@/components/blueprints/DataEntitiesSelector'
 import NoData from '@/components/NoData'
 import GitlabProjectsSelector from '@/components/blueprints/GitlabProjectsSelector'
 import GitHubProject from '@/models/GithubProject'
+import JenkinsJobsSelector from '@/components/blueprints/JenkinsJobsSelector'
 
 const DataScopes = (props) => {
   const {
@@ -42,6 +43,9 @@ const DataScopes = (props) => {
     fetchGitlabProjects = () => [],
     isFetchingGitlab = false,
     gitlabProjects = [],
+    fetchJenkinsJobs = () => [],
+    isFetchingJenkins = false,
+    jenkinsJobs = [],
     dataEntities = [],
     projects = [],
     boards = [],
@@ -232,6 +236,27 @@ const DataScopes = (props) => {
                     </>
                   )}
 
+                  {[Providers.JENKINS].includes(
+                    configuredConnection.provider
+                  ) && (
+                    <>
+                      <h4>Jobs *</h4>
+                      <p>Select the job you would like to sync.</p>
+                      <JenkinsJobsSelector
+                        onFetch={fetchJenkinsJobs}
+                        isFetching={isFetchingJenkins}
+                        items={jenkinsJobs}
+                        selectedItems={selectedProjects}
+                        onItemSelect={setProjects}
+                        onClear={setProjects}
+                        onRemove={setProjects}
+                        disabled={isSaving}
+                        configuredConnection={configuredConnection}
+                        isLoading={isFetching}
+                      />
+                    </>
+                  )}
+
                   <h4>Data Entities</h4>
                   <p>
                     Select the data entities you wish to collect for the
diff --git a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
index 14581368..f929cb3c 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -53,7 +53,6 @@ import ConnectionTabs from '@/components/blueprints/ConnectionTabs'
 import NoData from '@/components/NoData'
 import StandardStackedList from '@/components/blueprints/StandardStackedList'
 import ProviderTransformationSettings from '@/components/blueprints/ProviderTransformationSettings'
-import GithubSettings from '@/pages/configure/settings/github'
 
 const DataTransformations = (props) => {
   const {
@@ -104,15 +103,6 @@ const DataTransformations = (props) => {
     setInitializeTransformations(transformations)
   }, [])
 
-  const isTransformationSupported = useMemo(
-    () =>
-      configuredProject ||
-      configuredBoard ||
-      (configuredConnection?.provider === Providers.JENKINS &&
-        configuredConnection),
-    [configuredProject, configuredBoard, configuredConnection]
-  )
-
   const noTransformationsAvailable = useMemo(
     () =>
       [Providers.TAPD].includes(configuredConnection?.provider) ||
@@ -150,9 +140,6 @@ const DataTransformations = (props) => {
     (item) => {
       const initializeTransform = initializeTransformations[item?.id]
       const storedTransform = transformations[item?.id]
-
-      console.log(initializeTransform)
-      console.log(storedTransform)
       return (
         initializeTransform &&
         storedTransform &&
@@ -265,7 +252,8 @@ const DataTransformations = (props) => {
                     [
                       Providers.JIRA,
                       Providers.GITHUB,
-                      Providers.GITLAB
+                      Providers.GITLAB,
+                      Providers.JENKINS
                     ].includes(configuredConnection.provider) && (
                       <div
                         className='project-or-board-select'
@@ -277,9 +265,6 @@ const DataTransformations = (props) => {
                             : 'Project'}
                         </h4>
                         <Select
-                          disabled={
-                            configuredConnection.provider === Providers.JENKINS
-                          }
                           popoverProps={{ usePortal: false }}
                           className='selector-entity'
                           id='selector-entity'
@@ -313,10 +298,6 @@ const DataTransformations = (props) => {
                           }}
                         >
                           <Button
-                            disabled={
-                              configuredConnection.provider ===
-                              Providers.JENKINS
-                            }
                             className='btn-select-entity'
                             intent={Intent.PRIMARY}
                             outlined
@@ -339,9 +320,11 @@ const DataTransformations = (props) => {
                       </div>
                     )}
 
-                  {[Providers.GITLAB, Providers.GITHUB].includes(
-                    configuredConnection.provider
-                  ) &&
+                  {[
+                    Providers.GITLAB,
+                    Providers.GITHUB,
+                    Providers.JENKINS
+                  ].includes(configuredConnection.provider) &&
                     !useDropdownSelector &&
                     !configuredProject && (
                       <>
@@ -391,7 +374,7 @@ const DataTransformations = (props) => {
                       </>
                     )}
 
-                  {isTransformationSupported && (
+                  {(configuredProject || configuredBoard) && (
                     <div>
                       {!useDropdownSelector &&
                         (configuredProject || configuredBoard) && (
diff --git a/config-ui/src/config/jenkinsApiProxy.js b/config-ui/src/config/jenkinsApiProxy.js
new file mode 100644
index 00000000..4497a081
--- /dev/null
+++ b/config-ui/src/config/jenkinsApiProxy.js
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ *
+ */
+// @todo: add string replacer for [:connectionId] or refactor this const
+const JENKINS_API_PROXY_ENDPOINT =
+  '/api/plugins/jenkins/connections/[:connectionId:]/proxy/rest'
+const JENKINS_JOBS_ENDPOINT = `${JENKINS_API_PROXY_ENDPOINT}/api/json?tree=jobs[name]{0,10000}`
+
+export { JENKINS_API_PROXY_ENDPOINT, JENKINS_JOBS_ENDPOINT }
diff --git a/config-ui/src/hooks/useBlueprintValidation.jsx b/config-ui/src/hooks/useBlueprintValidation.jsx
index 9bcb9a69..a409e7d1 100644
--- a/config-ui/src/hooks/useBlueprintValidation.jsx
+++ b/config-ui/src/hooks/useBlueprintValidation.jsx
@@ -161,10 +161,16 @@ function useBlueprintValidation({
             errs.push('Boards: No Boards selected.')
           }
           if (
-            activeProvider?.id === Providers.GITHUB &&
+            [Providers.GITHUB, Providers.GITLAB, Providers.JENKINS].includes(
+              activeProvider?.id
+            ) &&
             projects[activeConnection?.id]?.length === 0
           ) {
-            errs.push('Projects: No Project Repsitories entered.')
+            if (activeProvider?.id === Providers.JENKINS) {
+              errs.push('Jobs: No Job entered.')
+            } else {
+              errs.push('Projects: No Project Repsitories entered.')
+            }
           }
           if (
             activeProvider?.id === Providers.GITHUB &&
@@ -191,12 +197,6 @@ function useBlueprintValidation({
           if (entities[activeConnection?.id]?.length === 0) {
             errs.push('Data Entities: No Data Entities selected.')
           }
-          if (
-            activeProvider?.id === Providers.GITLAB &&
-            projects[activeConnection?.id]?.length === 0
-          ) {
-            errs.push('Projects: No Project IDs entered.')
-          }
           if (
             activeProvider?.id === Providers.GITLAB &&
             !validateUniqueObjectSet(projects[activeConnection?.id])
diff --git a/config-ui/src/hooks/useDataScopesManager.jsx b/config-ui/src/hooks/useDataScopesManager.jsx
index f100f8d3..71577c51 100644
--- a/config-ui/src/hooks/useDataScopesManager.jsx
+++ b/config-ui/src/hooks/useDataScopesManager.jsx
@@ -15,19 +15,17 @@
  * limitations under the License.
  *
  */
-import { useCallback, useEffect, useState, useMemo } from 'react'
-import { ToastNotification } from '@/components/Toast'
-import { DEVLAKE_ENDPOINT } from '@/utils/config'
-import request from '@/utils/request'
-import { NullBlueprint, BlueprintMode } from '@/data/NullBlueprint'
+import { useCallback, useEffect, useMemo, useState } from 'react'
+import { BlueprintMode } from '@/data/NullBlueprint'
 import { DEFAULT_DATA_ENTITIES } from '@/data/BlueprintWorkflow'
 import { integrationsData } from '@/data/integrations'
 import TransformationSettings from '@/models/TransformationSettings'
 import JiraBoard from '@/models/JiraBoard'
 import GitHubProject from '@/models/GithubProject'
 import GitlabProject from '@/models/GitlabProject'
-import { Providers, ProviderLabels, ProviderIcons } from '@/data/Providers'
+import { ProviderIcons, ProviderLabels, Providers } from '@/data/Providers'
 import { DataScopeModes } from '@/data/DataScopes'
+import JenkinsJob from '@/models/JenkinsJob'
 
 function useDataScopesManager({
   mode = DataScopeModes.CREATE,
@@ -62,12 +60,12 @@ function useDataScopesManager({
     switch (connection?.providerId) {
       case Providers.GITHUB:
       case Providers.GITLAB:
+      case Providers.JENKINS:
         key = configuredProject?.id
         break
       case Providers.JIRA:
         key = configuredBoard?.id
         break
-      case Providers.JENKINS:
       case 'default':
         key = `C#${connection?.id}`
         break
@@ -254,13 +252,13 @@ function useDataScopesManager({
           }))
           break
         case Providers.JENKINS:
-          newScope = {
+          newScope = projects[connection.id]?.map((p) => ({
             ...newScope,
-            // options: {
-            // },
-            // NOTE: Jenkins has no concept of projects/boards. Transformations Key'ed by Conn *INDEX* ID!
-            transformation: { ...transformations[`C#${connection?.id}`] }
-          }
+            options: {
+              jobName: p.value
+            },
+            transformation: { ...transformations[p?.id] }
+          }))
           break
         case Providers.GITHUB:
           newScope = projects[connection.id]?.map((p) => ({
@@ -365,38 +363,64 @@ function useDataScopesManager({
 
   const getGithubProjects = useCallback(
     (c) =>
-      [Providers.GITHUB].includes(c.plugin)
-        ? c.scope.map(
-            (s) =>
-              new GitHubProject({
-                id: `${s.options?.owner}/${s.options?.repo}`,
-                key: `${s.options?.owner}/${s.options?.repo}`,
-                owner: s.options?.owner,
-                repo: s.options?.repo,
-                value: `${s.options?.owner}/${s.options?.repo}`,
-                title: `${s.options?.owner}/${s.options?.repo}`
-              })
-          )
-        : [],
+      c.scope.map(
+        (s) =>
+          new GitHubProject({
+            id: `${s.options?.owner}/${s.options?.repo}`,
+            key: `${s.options?.owner}/${s.options?.repo}`,
+            owner: s.options?.owner,
+            repo: s.options?.repo,
+            value: `${s.options?.owner}/${s.options?.repo}`,
+            title: `${s.options?.owner}/${s.options?.repo}`
+          })
+      ),
     []
   )
 
   const getGitlabProjects = useCallback(
     (c) =>
-      [Providers.GITLAB].includes(c.plugin)
-        ? c.scope.map(
-            (s) =>
-              new GitlabProject({
-                id: s.options?.projectId,
-                key: s.options?.projectId,
-                value: s.options?.projectId,
-                title: s.options?.title || `Project ${s.options?.projectId}`
-              })
-          )
-        : [],
+      c.scope.map(
+        (s) =>
+          new GitlabProject({
+            id: s.options?.projectId,
+            key: s.options?.projectId,
+            value: s.options?.projectId,
+            title: s.options?.title || `Project ${s.options?.projectId}`
+          })
+      ),
+    []
+  )
+
+  const getJenkinsProjects = useCallback(
+    (c) =>
+      c.scope.map(
+        (s) =>
+          new JenkinsJob({
+            id: s.options?.jobName,
+            key: s.options?.jobName,
+            value: s.options?.jobName,
+            title: s.options?.jobName
+          })
+      ),
     []
   )
 
+  const getProjects = useCallback(
+    (c) => {
+      switch (c.plugin) {
+        case Providers.GITHUB:
+          return getGithubProjects(c)
+        case Providers.GITLAB:
+          return getGitlabProjects(c)
+        case Providers.JENKINS:
+          return getJenkinsProjects(c)
+        default:
+          return []
+      }
+    },
+    [getGithubProjects, getGitlabProjects, getJenkinsProjects]
+  )
+
   const getAdvancedGithubProjects = useCallback(
     (t, providerId) =>
       [Providers.GITHUB].includes(providerId)
@@ -531,9 +555,7 @@ function useDataScopesManager({
       entityList: c.scope[0]?.entities?.map((e) =>
         DEFAULT_DATA_ENTITIES.find((de) => de.value === e)
       ),
-      projects: [Providers.GITLAB].includes(c.plugin)
-        ? getGitlabProjects(c)
-        : getGithubProjects(c),
+      projects: getProjects(c),
       boards: [Providers.JIRA].includes(c.plugin)
         ? c.scope.map((s) => `Board ${s.options?.boardId}`)
         : [],
@@ -562,7 +584,7 @@ function useDataScopesManager({
       stage: 1,
       totalStages: 1
     }),
-    [getGithubProjects, getGitlabProjects]
+    [getProjects]
   )
 
   const createAdvancedConnection = useCallback(
@@ -655,6 +677,7 @@ function useDataScopesManager({
     switch (connection?.provider?.id) {
       case Providers.GITHUB:
       case Providers.GITLAB:
+      case Providers.JENKINS:
         setProjects((p) => ({
           ...p,
           [connection?.id]: connection?.projects || []
@@ -683,16 +706,6 @@ function useDataScopesManager({
           setTransformationSettings(connection.transformations[bIdx], bId)
         )
         break
-      case Providers.JENKINS:
-        setEntities((e) => ({
-          ...e,
-          [connection?.id]: connection?.entityList || []
-        }))
-        setTransformationSettings(
-          connection.transformations[0],
-          `C#${connection?.id}`
-        )
-        break
     }
   }, [connection, setTransformationSettings])
 
@@ -708,45 +721,6 @@ function useDataScopesManager({
     modifyConnectionSettings
   ])
 
-  useEffect(() => {
-    console.log('>>>>> DATA SCOPES MANAGER: PROVIDER...', provider)
-    switch (provider?.id) {
-      case Providers.GITHUB:
-        break
-      case Providers.GITLAB:
-        break
-      case Providers.JIRA:
-        break
-      case Providers.JENKINS:
-        break
-      case Providers.TAPD:
-        break
-    }
-  }, [provider])
-
-  useEffect(() => {
-    console.log(
-      '>>>>> DATA SCOPES MANAGER: INITIALIZE NEW CONNECTION TRANSFORMATIONS...',
-      newConnections
-    )
-    // @note: jenkins has no "project/board" entity associated!
-    // transformations are based on the main connection scope...
-    const jenkinsTransformations = newConnections
-      .filter((c) => c.plugin === Providers.JENKINS)
-      .map((c) => `C#${c?.id}`)
-    console.log(
-      '>>>>> DATA SCOPES MANAGER: JENKINS TRANSFORMATIONS SCOPES...',
-      jenkinsTransformations
-    )
-    if (Array.isArray(jenkinsTransformations)) {
-      setTransformations((cT) => ({
-        ...jenkinsTransformations.reduce(initializeTransformations, {}),
-        // Spread Current/Existing Transformations Settings
-        ...cT
-      }))
-    }
-  }, [newConnections, initializeTransformations])
-
   useEffect(() => {
     console.log('>>>>> DATA SCOPES MANAGER: INITIALIZE BOARDS...', boards)
     const boardTransformations = boards[connection?.id]
diff --git a/config-ui/src/hooks/useJenkins.jsx b/config-ui/src/hooks/useJenkins.jsx
new file mode 100644
index 00000000..81ec872b
--- /dev/null
+++ b/config-ui/src/hooks/useJenkins.jsx
@@ -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.
+ *
+ */
+import { useEffect, useState, useCallback } from 'react'
+import request from '@/utils/request'
+import { ToastNotification } from '@/components/Toast'
+
+const useJenkins = (
+  { apiProxyPath, jobsEndpoint },
+  activeConnection = null
+) => {
+  const [isFetching, setIsFetching] = useState(false)
+  const [jobs, setJobs] = useState([])
+  const [error, setError] = useState()
+
+  const fetchJobs = useCallback(async () => {
+    try {
+      if (apiProxyPath.includes('null')) {
+        throw new Error('Connection ID is Null')
+      }
+      setError(null)
+      setIsFetching(true)
+      // only search when type more than 2 chars
+      const endpoint = jobsEndpoint.replace(
+        '[:connectionId:]',
+        activeConnection?.connectionId
+      )
+      const jobsResponse = await request.get(endpoint)
+      if (
+        jobsResponse &&
+        jobsResponse.status === 200 &&
+        jobsResponse.data &&
+        jobsResponse.data.jobs
+      ) {
+        setJobs(createListData(jobsResponse.data?.jobs))
+      } else {
+        throw new Error('request jobs fail')
+      }
+    } catch (e) {
+      setError(e)
+      ToastNotification.show({
+        message: e.message,
+        intent: 'danger',
+        icon: 'error'
+      })
+    } finally {
+      setIsFetching(false)
+    }
+  }, [jobsEndpoint, activeConnection, apiProxyPath])
+
+  const createListData = (
+    data = [],
+    titleProperty = 'name',
+    valueProperty = 'name'
+  ) => {
+    return data.map((d, dIdx) => ({
+      id: d[valueProperty],
+      key: d[valueProperty],
+      title: d[titleProperty],
+      value: d[valueProperty],
+      type: 'string'
+    }))
+  }
+
+  useEffect(() => {
+    console.log('>>> Jenkins API PROXY: FIELD SELECTOR JOBS DATA', jobs)
+  }, [jobs])
+
+  return {
+    isFetching,
+    fetchJobs,
+    jobs,
+    error
+  }
+}
+
+export default useJenkins
diff --git a/config-ui/src/models/JenkinsJob.js b/config-ui/src/models/JenkinsJob.js
new file mode 100644
index 00000000..f43d29bf
--- /dev/null
+++ b/config-ui/src/models/JenkinsJob.js
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ *
+ */
+
+/**
+ * @typedef {object} JenkinsJob
+ * @property {number?} id
+ * @property {number?} key
+ * @property {number?} projectId
+ * @property {string|number?} name
+ * @property {string|number?} value
+ * @property {string|number?} title
+ * @property {boolean?} useApi
+ * @property {project|board|job?} variant
+ */
+class JenkinsJob {
+  constructor(data = {}) {
+    this.id = data?.id || null
+    this.key = data?.key || this.id || null
+    this.name = data?.name || null
+    this.value = data?.value || this.name || this.id || null
+    this.title = data?.title || this.name || this.id || null
+
+    this.useApi = data?.useApi || true
+    this.variant = data?.variant || 'job'
+  }
+
+  get(property) {
+    return this[property]
+  }
+
+  set(property, value) {
+    this[property] = value
+    return this.property
+  }
+
+  getConfiguredEntityId() {
+    return this.name.toString() || this.id
+  }
+}
+
+export default JenkinsJob
diff --git a/config-ui/src/pages/blueprints/blueprint-settings.jsx b/config-ui/src/pages/blueprints/blueprint-settings.jsx
index cb1c9355..5095047a 100644
--- a/config-ui/src/pages/blueprints/blueprint-settings.jsx
+++ b/config-ui/src/pages/blueprints/blueprint-settings.jsx
@@ -78,6 +78,11 @@ import {
   GITLAB_API_PROXY_ENDPOINT,
   PROJECTS_ENDPOINT
 } from '@/config/gitlabApiProxy'
+import useJenkins from '@/hooks/useJenkins'
+import {
+  JENKINS_API_PROXY_ENDPOINT,
+  JENKINS_JOBS_ENDPOINT
+} from '@/config/jenkinsApiProxy'
 
 const BlueprintSettings = (props) => {
   // eslint-disable-next-line no-unused-vars
@@ -326,6 +331,19 @@ const BlueprintSettings = (props) => {
     configuredConnection
   )
 
+  const {
+    fetchJobs: fetchJenkinsJobs,
+    jobs: jenkinsJobs,
+    isFetching: isFetchingJenkins,
+    error: jenkinsProxyError
+  } = useJenkins(
+    {
+      apiProxyPath: JENKINS_API_PROXY_ENDPOINT,
+      jobsEndpoint: JENKINS_JOBS_ENDPOINT
+    },
+    configuredConnection
+  )
+
   const handleBlueprintActivation = useCallback(
     (blueprint) => {
       if (blueprint.enable) {
@@ -1281,7 +1299,8 @@ const BlueprintSettings = (props) => {
                         loading={
                           isFetchingBlueprint ||
                           isFetchingJIRA ||
-                          isFetchingGitlab
+                          isFetchingGitlab ||
+                          isFetchingJenkins
                         }
                       />
                     </div>
@@ -1322,7 +1341,8 @@ const BlueprintSettings = (props) => {
                       loading={
                         isFetchingBlueprint ||
                         isFetchingJIRA ||
-                        isFetchingGitlab
+                        isFetchingGitlab ||
+                        isFetchingJenkins
                       }
                     />
                   </div>
@@ -1455,6 +1475,10 @@ const BlueprintSettings = (props) => {
         gitlabProjects={gitlabProjects}
         isFetchingGitlab={isFetchingGitlab}
         gitlabProxyError={gitlabProxyError}
+        fetchJenkinsJobs={fetchJenkinsJobs}
+        jenkinsJobs={jenkinsJobs}
+        isFetchingJenkins={isFetchingJenkins}
+        jenkinsProxyError={jenkinsProxyError}
         setConfiguredProject={setConfiguredProject}
         setConfiguredBoard={setConfiguredBoard}
         setBoards={setBoards}
diff --git a/config-ui/src/pages/blueprints/create-blueprint.jsx b/config-ui/src/pages/blueprints/create-blueprint.jsx
index 2c2bd83c..3aa214f7 100644
--- a/config-ui/src/pages/blueprints/create-blueprint.jsx
+++ b/config-ui/src/pages/blueprints/create-blueprint.jsx
@@ -79,6 +79,11 @@ import {
   GITLAB_API_PROXY_ENDPOINT,
   PROJECTS_ENDPOINT
 } from '@/config/gitlabApiProxy'
+import useJenkins from '@/hooks/useJenkins'
+import {
+  JENKINS_API_PROXY_ENDPOINT,
+  JENKINS_JOBS_ENDPOINT
+} from '@/config/jenkinsApiProxy'
 
 // import ConnectionTabs from '@/components/blueprints/ConnectionTabs'
 
@@ -287,6 +292,19 @@ const CreateBlueprint = (props) => {
     configuredConnection
   )
 
+  const {
+    fetchJobs: fetchJenkinsJobs,
+    jobs: jenkinsJobs,
+    isFetching: isFetchingJenkins,
+    error: jenkinsProxyError
+  } = useJenkins(
+    {
+      apiProxyPath: JENKINS_API_PROXY_ENDPOINT,
+      jobsEndpoint: JENKINS_JOBS_ENDPOINT
+    },
+    configuredConnection
+  )
+
   const {
     testConnection,
     // eslint-disable-next-line no-unused-vars
@@ -1137,6 +1155,9 @@ const CreateBlueprint = (props) => {
                       fetchGitlabProjects={fetchGitlabProjects}
                       isFetchingGitlab={isFetchingGitlab}
                       gitlabProjects={gitlabProjects}
+                      fetchJenkinsJobs={fetchJenkinsJobs}
+                      isFetchingJenkins={isFetchingJenkins}
+                      jenkinsJobs={jenkinsJobs}
                       boards={boards}
                       dataEntities={dataEntities}
                       projects={projects}
@@ -1156,6 +1177,7 @@ const CreateBlueprint = (props) => {
                       isFetching={
                         isFetchingJIRA ||
                         isFetchingGitlab ||
+                        isFetchingJenkins ||
                         isFetchingConnection
                       }
                     />
@@ -1234,6 +1256,7 @@ const CreateBlueprint = (props) => {
                 isSaving ||
                 isFetchingJIRA ||
                 isFetchingGitlab ||
+                isFetchingJenkins ||
                 isFetchingConnection ||
                 isTestingConnection
               }
diff --git a/config-ui/src/pages/configure/integration/manage.jsx b/config-ui/src/pages/configure/integration/manage.jsx
index 1f964fb4..aca63b6d 100644
--- a/config-ui/src/pages/configure/integration/manage.jsx
+++ b/config-ui/src/pages/configure/integration/manage.jsx
@@ -208,9 +208,7 @@ export default function ManageIntegration() {
                       </>
                     )}
                   </h1>
-                  <p className='page-description'>
-                    Manage connections.
-                  </p>
+                  <p className='page-description'>Manage connections.</p>
                 </div>
               </div>
             </div>
diff --git a/config-ui/src/pages/configure/settings/gitlab.jsx b/config-ui/src/pages/configure/settings/gitlab.jsx
index fa0c8d02..1af832fc 100644
--- a/config-ui/src/pages/configure/settings/gitlab.jsx
+++ b/config-ui/src/pages/configure/settings/gitlab.jsx
@@ -30,7 +30,6 @@ export default function GitlabSettings(props) {
     transformation = {},
     entityIdKey,
     provider,
-    projects,
     configuredProject,
     isSaving = false,
     isSavingConnection = false,
diff --git a/config-ui/src/pages/configure/settings/jenkins.jsx b/config-ui/src/pages/configure/settings/jenkins.jsx
index ceb983ac..9d937571 100644
--- a/config-ui/src/pages/configure/settings/jenkins.jsx
+++ b/config-ui/src/pages/configure/settings/jenkins.jsx
@@ -40,13 +40,14 @@ import '@/styles/connections.scss'
 export default function JenkinsSettings(props) {
   const {
     provider,
-    transformation,
-    entityIdKey,
     connection,
     entities = [],
-    onSettingsChange = () => {},
+    transformation = {},
+    entityIdKey,
     isSaving = false,
-    isSavingConnection = false
+    isSavingConnection = false,
+    onSettingsChange = () => {},
+    configuredProject
   } = props
   const history = useHistory()
   const { providerId, connectionId } = useParams()
diff --git a/config-ui/src/store/UIContext.jsx b/config-ui/src/store/UIContext.jsx
index 6e1e22fb..d4774c48 100644
--- a/config-ui/src/store/UIContext.jsx
+++ b/config-ui/src/store/UIContext.jsx
@@ -1,20 +1,20 @@
 /*
-  * 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.
-  *
-  */
+ * 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, { useState } from 'react'
 
 const UIContext = React.createContext({
@@ -39,4 +39,4 @@ export const UIContextProvider = (props) => {
   )
 }
 
-export default UIContext
\ No newline at end of file
+export default UIContext
diff --git a/config-ui/src/styles/libraries/blueprint.scss b/config-ui/src/styles/libraries/blueprint.scss
index 993de363..f00d85bf 100644
--- a/config-ui/src/styles/libraries/blueprint.scss
+++ b/config-ui/src/styles/libraries/blueprint.scss
@@ -155,6 +155,13 @@ button, .bp3-button {
   }
 }
 
+.bp3-multi-select-popover {
+  .bp3-menu {
+    max-height: calc(10 * 32px);
+    overflow: auto;
+  }
+}
+
 .bp3-multi-select-popover {
   max-width: 600px;
   overflow: auto;
diff --git a/plugins/gitlab/api/proxy.go b/plugins/gitlab/api/proxy.go
index 39717f45..999cb9bf 100644
--- a/plugins/gitlab/api/proxy.go
+++ b/plugins/gitlab/api/proxy.go
@@ -46,7 +46,7 @@ func Proxy(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error)
 		map[string]string{
 			"Authorization": fmt.Sprintf("Bearer %v", connection.Token),
 		},
-		30*time.Second,
+		TimeOut,
 		connection.Proxy,
 		BasicRes,
 	)
diff --git a/plugins/gitlab/api/proxy.go b/plugins/jenkins/api/proxy.go
similarity index 90%
copy from plugins/gitlab/api/proxy.go
copy to plugins/jenkins/api/proxy.go
index 39717f45..cc56a1db 100644
--- a/plugins/gitlab/api/proxy.go
+++ b/plugins/jenkins/api/proxy.go
@@ -21,13 +21,13 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
-	"github.com/apache/incubator-devlake/errors"
 	"io"
 	"time"
 
+	"github.com/apache/incubator-devlake/errors"
 	"github.com/apache/incubator-devlake/plugins/core"
-	"github.com/apache/incubator-devlake/plugins/gitlab/models"
 	"github.com/apache/incubator-devlake/plugins/helper"
+	"github.com/apache/incubator-devlake/plugins/jenkins/models"
 )
 
 const (
@@ -35,7 +35,7 @@ const (
 )
 
 func Proxy(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error) {
-	connection := &models.GitlabConnection{}
+	connection := &models.JenkinsConnection{}
 	err := connectionHelper.First(connection, input.Params)
 	if err != nil {
 		return nil, err
@@ -44,9 +44,9 @@ func Proxy(input *core.ApiResourceInput) (*core.ApiResourceOutput, errors.Error)
 		context.TODO(),
 		connection.Endpoint,
 		map[string]string{
-			"Authorization": fmt.Sprintf("Bearer %v", connection.Token),
+			"Authorization": fmt.Sprintf("Basic %v", connection.GetEncodedToken()),
 		},
-		30*time.Second,
+		TimeOut,
 		connection.Proxy,
 		BasicRes,
 	)
diff --git a/plugins/jenkins/e2e/builds_test.go b/plugins/jenkins/e2e/builds_test.go
index bd6fb662..e5d5b8e3 100644
--- a/plugins/jenkins/e2e/builds_test.go
+++ b/plugins/jenkins/e2e/builds_test.go
@@ -35,6 +35,7 @@ func TestJenkinsBuildsDataFlow(t *testing.T) {
 	taskData := &tasks.JenkinsTaskData{
 		Options: &tasks.JenkinsOptions{
 			ConnectionId: 1,
+			JobName:      `devlake`,
 		},
 	}
 
diff --git a/plugins/jenkins/e2e/jobs_test.go b/plugins/jenkins/e2e/jobs_test.go
index 85da78d4..b55ad3ea 100644
--- a/plugins/jenkins/e2e/jobs_test.go
+++ b/plugins/jenkins/e2e/jobs_test.go
@@ -34,6 +34,7 @@ func TestJenkinsJobsDataFlow(t *testing.T) {
 	taskData := &tasks.JenkinsTaskData{
 		Options: &tasks.JenkinsOptions{
 			ConnectionId: 1,
+			JobName:      `devlake`,
 		},
 	}
 
diff --git a/plugins/jenkins/e2e/stages_test.go b/plugins/jenkins/e2e/stages_test.go
index 61eb87a0..eff00a6e 100644
--- a/plugins/jenkins/e2e/stages_test.go
+++ b/plugins/jenkins/e2e/stages_test.go
@@ -34,6 +34,7 @@ func TestJenkinsStagesDataFlow(t *testing.T) {
 	taskData := &tasks.JenkinsTaskData{
 		Options: &tasks.JenkinsOptions{
 			ConnectionId: 1,
+			JobName:      `devlake`,
 		},
 	}
 
diff --git a/plugins/jenkins/impl/impl.go b/plugins/jenkins/impl/impl.go
index 9a94dbe2..11b281a9 100644
--- a/plugins/jenkins/impl/impl.go
+++ b/plugins/jenkins/impl/impl.go
@@ -137,6 +137,9 @@ func (plugin Jenkins) ApiResources() map[string]map[string]core.ApiResourceHandl
 			"DELETE": api.DeleteConnection,
 			"GET":    api.GetConnection,
 		},
+		"connections/:connectionId/proxy/rest/*path": {
+			"GET": api.Proxy,
+		},
 	}
 }
 
diff --git a/plugins/jenkins/jenkins.go b/plugins/jenkins/jenkins.go
index f5c1c12e..941d7798 100644
--- a/plugins/jenkins/jenkins.go
+++ b/plugins/jenkins/jenkins.go
@@ -28,11 +28,13 @@ var PluginEntry impl.Jenkins
 func main() {
 	jenkinsCmd := &cobra.Command{Use: "jenkins"}
 	connectionId := jenkinsCmd.Flags().Uint64P("connection", "c", 1, "jenkins connection id")
+	jobName := jenkinsCmd.Flags().StringP("jobName", "j", "", "jenkins job name")
 	deployTagPattern := jenkinsCmd.Flags().String("deployTagPattern", "(?i)deploy", "deploy tag name")
 
 	jenkinsCmd.Run = func(cmd *cobra.Command, args []string) {
 		runner.DirectRun(cmd, args, PluginEntry, map[string]interface{}{
 			"connectionId":     *connectionId,
+			"jobName":          jobName,
 			"deployTagPattern": *deployTagPattern,
 		})
 	}
diff --git a/plugins/jenkins/tasks/build_cicd_convertor.go b/plugins/jenkins/tasks/build_cicd_convertor.go
index 5719dbd4..92772030 100644
--- a/plugins/jenkins/tasks/build_cicd_convertor.go
+++ b/plugins/jenkins/tasks/build_cicd_convertor.go
@@ -69,6 +69,7 @@ func ConvertBuildsToCICD(taskCtx core.SubTaskContext) (err errors.Error) {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_BUILD_TABLE,
diff --git a/plugins/jenkins/tasks/build_collector.go b/plugins/jenkins/tasks/build_collector.go
index 1d19f1ae..5f9546c1 100644
--- a/plugins/jenkins/tasks/build_collector.go
+++ b/plugins/jenkins/tasks/build_collector.go
@@ -51,7 +51,7 @@ func CollectApiBuilds(taskCtx core.SubTaskContext) errors.Error {
 	clauses := []dal.Clause{
 		dal.Select("tjj.name,tjj.path"),
 		dal.From("_tool_jenkins_jobs tjj"),
-		dal.Where(`tjj.connection_id = ?`, data.Options.ConnectionId),
+		dal.Where(`tjj.connection_id = ? and tjj.name = ?`, data.Options.ConnectionId, data.Options.JobName),
 	}
 
 	cursor, err := db.Cursor(clauses...)
@@ -69,6 +69,7 @@ func CollectApiBuilds(taskCtx core.SubTaskContext) errors.Error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_BUILD_TABLE,
diff --git a/plugins/jenkins/tasks/build_commit_convertor.go b/plugins/jenkins/tasks/build_commit_convertor.go
index 8af205d3..95589ca8 100644
--- a/plugins/jenkins/tasks/build_commit_convertor.go
+++ b/plugins/jenkins/tasks/build_commit_convertor.go
@@ -59,6 +59,7 @@ func ConvertBuildRepos(taskCtx core.SubTaskContext) errors.Error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_BUILD_TABLE,
diff --git a/plugins/jenkins/tasks/build_extractor.go b/plugins/jenkins/tasks/build_extractor.go
index ee3e517c..0a01de9a 100644
--- a/plugins/jenkins/tasks/build_extractor.go
+++ b/plugins/jenkins/tasks/build_extractor.go
@@ -45,15 +45,9 @@ func ExtractApiBuilds(taskCtx core.SubTaskContext) errors.Error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
-			Ctx: taskCtx,
-			/*
-				This struct will be JSONEncoded and stored into database along with raw data itself, to identity minimal
-				set of data to be process, for example, we process JiraIssues by Board
-			*/
-			/*
-				Table store raw data
-			*/
+			Ctx:   taskCtx,
 			Table: RAW_BUILD_TABLE,
 		},
 		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
diff --git a/plugins/jenkins/tasks/job_collector.go b/plugins/jenkins/tasks/job_collector.go
index db4325a2..e94db4bd 100644
--- a/plugins/jenkins/tasks/job_collector.go
+++ b/plugins/jenkins/tasks/job_collector.go
@@ -49,14 +49,7 @@ func CollectApiJobs(taskCtx core.SubTaskContext) errors.Error {
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
 			},
-			Ctx: taskCtx,
-			/*
-				This struct will be JSONEncoded and stored into database along with raw data itself, to identity minimal
-				set of data to be process, for example, we process JiraIssues by Board
-			*/
-			/*
-				Table store raw data
-			*/
+			Ctx:   taskCtx,
 			Table: RAW_JOB_TABLE,
 		},
 		ApiClient:   data.ApiClient,
diff --git a/plugins/jenkins/tasks/job_extractor.go b/plugins/jenkins/tasks/job_extractor.go
index fd93354b..63a553cb 100644
--- a/plugins/jenkins/tasks/job_extractor.go
+++ b/plugins/jenkins/tasks/job_extractor.go
@@ -42,14 +42,7 @@ func ExtractApiJobs(taskCtx core.SubTaskContext) errors.Error {
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
 			},
-			Ctx: taskCtx,
-			/*
-				This struct will be JSONEncoded and stored into database along with raw data itself, to identity minimal
-				set of data to be process, for example, we process JiraIssues by Board
-			*/
-			/*
-				Table store raw data
-			*/
+			Ctx:   taskCtx,
 			Table: RAW_JOB_TABLE,
 		},
 		Extract: func(row *helper.RawData) ([]interface{}, errors.Error) {
diff --git a/plugins/jenkins/tasks/stage_collector.go b/plugins/jenkins/tasks/stage_collector.go
index 8411ac01..7ffc1250 100644
--- a/plugins/jenkins/tasks/stage_collector.go
+++ b/plugins/jenkins/tasks/stage_collector.go
@@ -53,8 +53,8 @@ func CollectApiStages(taskCtx core.SubTaskContext) errors.Error {
 	clauses := []dal.Clause{
 		dal.Select("tjj.path,tjb.job_name,tjb.number,tjb.full_display_name"),
 		dal.From("_tool_jenkins_builds as tjb,_tool_jenkins_jobs as tjj"),
-		dal.Where(`tjb.connection_id = ? and tjb.class = ? and tjb.job_name = tjj.name`,
-			data.Options.ConnectionId, "WorkflowRun"),
+		dal.Where(`tjb.connection_id = ? and tjj.name = ? and tjb.class = ? and tjb.job_name = tjj.name`,
+			data.Options.ConnectionId, data.Options.JobName, "WorkflowRun"),
 	}
 
 	cursor, err := db.Cursor(clauses...)
@@ -72,6 +72,7 @@ func CollectApiStages(taskCtx core.SubTaskContext) errors.Error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_STAGE_TABLE,
diff --git a/plugins/jenkins/tasks/stage_convertor.go b/plugins/jenkins/tasks/stage_convertor.go
index 75461f49..42dbd670 100644
--- a/plugins/jenkins/tasks/stage_convertor.go
+++ b/plugins/jenkins/tasks/stage_convertor.go
@@ -97,6 +97,7 @@ func ConvertStages(taskCtx core.SubTaskContext) (err errors.Error) {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_STAGE_TABLE,
diff --git a/plugins/jenkins/tasks/stage_extractor.go b/plugins/jenkins/tasks/stage_extractor.go
index d010c792..e4158c6f 100644
--- a/plugins/jenkins/tasks/stage_extractor.go
+++ b/plugins/jenkins/tasks/stage_extractor.go
@@ -42,6 +42,7 @@ func ExtractApiStages(taskCtx core.SubTaskContext) errors.Error {
 		RawDataSubTaskArgs: helper.RawDataSubTaskArgs{
 			Params: JenkinsApiParams{
 				ConnectionId: data.Options.ConnectionId,
+				JobName:      data.Options.JobName,
 			},
 			Ctx:   taskCtx,
 			Table: RAW_STAGE_TABLE,
diff --git a/plugins/jenkins/tasks/task_data.go b/plugins/jenkins/tasks/task_data.go
index 1a775706..0fa4a298 100644
--- a/plugins/jenkins/tasks/task_data.go
+++ b/plugins/jenkins/tasks/task_data.go
@@ -27,10 +27,12 @@ import (
 
 type JenkinsApiParams struct {
 	ConnectionId uint64
+	JobName      string
 }
 
 type JenkinsOptions struct {
 	ConnectionId               uint64 `json:"connectionId"`
+	JobName                    string `json:"JobName"`
 	Since                      string
 	Tasks                      []string `json:"tasks,omitempty"`
 	models.TransformationRules `mapstructure:"transformationRules" json:"transformationRules"`