You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@devlake.apache.org by e2...@apache.org on 2022/09/08 12:18:00 UTC

[incubator-devlake] branch main updated: feat: add projects selector fetching by api for gitlab when adding blueprint (#2926)

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

e2corporation 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 76611327 feat: add projects selector fetching by api for gitlab when adding blueprint (#2926)
76611327 is described below

commit 76611327c8692d48799789ecb4835e963a1f7278
Author: likyh <l...@likyh.com>
AuthorDate: Thu Sep 8 20:17:55 2022 +0800

    feat: add projects selector fetching by api for gitlab when adding blueprint (#2926)
    
    * feat: add projects selector fetching by api for gitlab when adding blueprint
    
    * feat: change the project from id to object
    
    * fix: fix some small bug
    
    * feat: update project from id to object on setting page
    
    * fix: fix some bug on advance mode
    
    * fix: fix for review
    
    * fix: fix for review
    
    * fix: fix for review. (revert boards)
    
    * feat: add icon for jira and gitlab
    
    * fix: fix for lint
    
    Co-authored-by: linyh <ya...@meri.co>
---
 .../blueprints/BlueprintDataScopesDialog.jsx       |  16 ++-
 .../src/components/blueprints/DataScopesGrid.jsx   |   4 +-
 .../blueprints/GitlabProjectsSelector.jsx          | 149 +++++++++++++++++++++
 .../components/blueprints/StandardStackedList.jsx  |  22 ++-
 .../blueprints/create-workflow/DataScopes.jsx      |  71 ++++++----
 .../create-workflow/DataTransformations.jsx        |   6 +-
 .../config/{jiraApiProxy.js => gitlabApiProxy.js}  |  14 +-
 config-ui/src/config/jiraApiProxy.js               |  14 +-
 config-ui/src/hooks/useBlueprintValidation.jsx     |  10 +-
 config-ui/src/hooks/useDataScopesManager.jsx       |  10 +-
 config-ui/src/hooks/useGitlab.jsx                  |  86 ++++++++++++
 config-ui/src/hooks/useJIRA.jsx                    |  10 +-
 .../src/pages/blueprints/blueprint-settings.jsx    |  90 +++++++++----
 .../src/pages/blueprints/create-blueprint.jsx      |  36 +++--
 config-ui/src/pages/configure/settings/github.jsx  |  28 ++--
 15 files changed, 442 insertions(+), 124 deletions(-)

diff --git a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
index 2ad70849..b27fcc79 100644
--- a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
+++ b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
@@ -72,6 +72,8 @@ const BlueprintDataScopesDialog = (props) => {
     issueTypesList = [],
     fieldsList = [],
     boards = {},
+    gitlabProjects = [],
+    fetchGitlabProjects = () => [],
     entities = {},
     projects = {},
     mode = Modes.EDIT,
@@ -105,6 +107,8 @@ const BlueprintDataScopesDialog = (props) => {
     isTesting = false,
     isFetchingJIRA = false,
     jiraProxyError,
+    isFetchingGitlab,
+    gitlabProxyError,
     errors = [],
     content = null,
     backButtonProps = {
@@ -112,28 +116,28 @@ const BlueprintDataScopesDialog = (props) => {
       intent: Intent.PRIMARY,
       text: 'Previous Step',
       outlined: true,
-      loading: isFetchingJIRA || isSaving
+      loading: isFetchingJIRA || isFetchingGitlab || isSaving
     },
     nextButtonProps = {
       disabled: !isValid,
       intent: Intent.PRIMARY,
       text: 'Next Step',
       outlined: true,
-      loading: isFetchingJIRA || isSaving,
+      loading: isFetchingJIRA || isFetchingGitlab || isSaving,
     },
     finalButtonProps = {
       disabled: !isValid,
       intent: Intent.PRIMARY,
       onClick: onSave,
       text: 'Save Changes',
-      loading: isFetchingJIRA || isSaving
+      loading: isFetchingJIRA || isFetchingGitlab || isSaving
     },
     closeButtonProps = {
       // disabled:
       intent: Intent.PRIMARY,
       text: 'Cancel',
       outlined: true,
-      loading: isFetchingJIRA || isSaving
+      loading: isFetchingJIRA || isFetchingGitlab || isSaving
     }
   } = props
 
@@ -180,6 +184,10 @@ const BlueprintDataScopesDialog = (props) => {
                 dataEntitiesList={dataEntitiesList}
                 boardsList={boardsList}
                 boards={boards}
+                fetchGitlabProjects={fetchGitlabProjects}
+                gitlabProjects={gitlabProjects}
+                isFetchingGitlab={isFetchingGitlab}
+                gitlabProxyError={gitlabProxyError}
                 dataEntities={entities}
                 projects={projects}
                 configuredConnection={configuredConnection}
diff --git a/config-ui/src/components/blueprints/DataScopesGrid.jsx b/config-ui/src/components/blueprints/DataScopesGrid.jsx
index c1ccd540..52930371 100644
--- a/config-ui/src/components/blueprints/DataScopesGrid.jsx
+++ b/config-ui/src/components/blueprints/DataScopesGrid.jsx
@@ -155,8 +155,8 @@ const DataScopesGrid = (props) => {
                   }}
                 >
                   {c.projects.map((project, pIdx) => (
-                    <li key={`list-item-key-${pIdx}`}>
-                      {project}
+                    <li key={`list-item-key-${pIdx}`} style={{ whiteSpace: 'break-spaces' }}>
+                      {project.title}
                     </li>
                   ))}
                 </ul>
diff --git a/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx b/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx
new file mode 100644
index 00000000..ffeb0656
--- /dev/null
+++ b/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx
@@ -0,0 +1,149 @@
+/*
+ * 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 { Checkbox, Intent, MenuItem, } from '@blueprintjs/core'
+import { MultiSelect } from '@blueprintjs/select'
+
+const GitlabProjectsSelector = (props) => {
+  const {
+    onFetch = () => [],
+    isFetching = false,
+    configuredConnection,
+    placeholder = 'Select Projects',
+    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',
+        }}
+      />
+    ),
+    tagRenderer = (item) => item.shortTitle || item.title
+  } = props
+
+  const [query, setQuery] = useState('')
+  const [onlyQueryMemberRepo, setOnlyQueryMemberRepo] = useState(true)
+
+  useEffect(() => {
+    // prevent request too frequently
+    const timer = setTimeout(() => {
+      onFetch(query, onlyQueryMemberRepo)
+    }, 200)
+    return () => clearTimeout(timer)
+  }, [onFetch, query, onlyQueryMemberRepo])
+
+  return (
+    <div
+      className='gitlab-projects-multiselect'
+      style={{ display: 'flex', marginBottom: '10px' }}
+    >
+      <div
+        className='gitlab-projects-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-projects'
+          inline={true}
+          fill={true}
+          items={items}
+          selectedItems={selectedItems}
+          activeItem={activeItem}
+          onQueryChange={query => setQuery(query)}
+          itemRenderer={itemRenderer}
+          tagRenderer={tagRenderer}
+          tagInputProps={{
+            tagProps: {
+              intent: Intent.PRIMARY,
+              minimal: true
+            },
+          }}
+          noResults={
+            (query.length <= 2 && <MenuItem disabled={true} text='Please type more than 2 characters to search.' />) ||
+            (isFetching && <MenuItem disabled={true} text='Fetching...' />) ||
+            <MenuItem disabled={true} text='No Projects 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],
+                      item,
+                    ],
+                  }
+                : { ...rT }
+            })
+          }}
+          style={{ borderRight: 0 }}
+        />
+
+        <Checkbox
+          label='Only search my repositories' checked={onlyQueryMemberRepo}
+          onChange={e => setOnlyQueryMemberRepo(!onlyQueryMemberRepo)}
+          style={{ margin: '10px 0 0 6px' }}
+        />
+      </div>
+    </div>
+  )
+}
+
+export default GitlabProjectsSelector
diff --git a/config-ui/src/components/blueprints/StandardStackedList.jsx b/config-ui/src/components/blueprints/StandardStackedList.jsx
index cbd72a40..ad175062 100644
--- a/config-ui/src/components/blueprints/StandardStackedList.jsx
+++ b/config-ui/src/components/blueprints/StandardStackedList.jsx
@@ -15,8 +15,7 @@
  * limitations under the License.
  *
  */
-import React, { Fragment, useEffect, useState, useCallback } from 'react'
-import { CSSTransition } from 'react-transition-group'
+import React from 'react'
 import {
   Button,
   Icon,
@@ -24,6 +23,8 @@ import {
   Elevation,
   Card,
   Colors,
+  Popover,
+  PopoverInteractionKind,
 } from '@blueprintjs/core'
 
 const StandardStackedList = (props) => {
@@ -68,7 +69,22 @@ const StandardStackedList = (props) => {
                     onClick={() => onAdd(item)}
                     style={{ cursor: 'pointer' }}
                   >
-                    {item?.name || item}
+                    {item.shortTitle || item.icon
+                      ? (
+                        <Popover
+                          className='docs-popover-portal-example-popover'
+                          interactionKind={PopoverInteractionKind.HOVER_TARGET_ONLY}
+                          content={
+                            <div style={{ padding: '5px', 'justify-content': 'center', display: 'flex' }}>
+                              {item.icon && <img src={item.icon} style={{ maxWidth: '100%', overflow: 'hidden', width: '14px', height: '14px', borderRadius: '50%', marginRight: '2px' }}/>}
+                              {item.title}
+                            </div>
+                          }
+                        >
+                          {item.shortTitle || item.title}
+                        </Popover>
+                        )
+                      : item.title}
                   </label>
                 </div>
               </div>
diff --git a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
index de0d204d..a550bc79 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
@@ -15,29 +15,14 @@
  * limitations under the License.
  *
  */
-import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react'
-import {
-  Button,
-  Icon,
-  Intent,
-  TagInput,
-  Divider,
-  Elevation,
-  Card,
-  Colors,
-} from '@blueprintjs/core'
-import {
-  Providers,
-  ProviderTypes,
-  ProviderIcons,
-  ConnectionStatus,
-  ConnectionStatusLabels,
-} from '@/data/Providers'
-
+import React, { useEffect, useMemo } from 'react'
+import { Button, Card, Divider, Elevation, Intent, TagInput, } from '@blueprintjs/core'
+import { ProviderIcons, Providers, } from '@/data/Providers'
 import ConnectionTabs from '@/components/blueprints/ConnectionTabs'
 import BoardsSelector from '@/components/blueprints/BoardsSelector'
 import DataEntitiesSelector from '@/components/blueprints/DataEntitiesSelector'
 import NoData from '@/components/NoData'
+import GitlabProjectsSelector from '@/components/blueprints/GitlabProjectsSelector'
 
 const DataScopes = (props) => {
   const {
@@ -46,6 +31,9 @@ const DataScopes = (props) => {
     blueprintConnections = [],
     dataEntitiesList = [],
     boardsList = [],
+    fetchGitlabProjects = () => [],
+    isFetchingGitlab = false,
+    gitlabProjects = [],
     dataEntities = [],
     projects = [],
     boards = [],
@@ -67,11 +55,16 @@ const DataScopes = (props) => {
   } = props
 
   const selectedBoards = useMemo(() => boards[configuredConnection.id], [boards, configuredConnection?.id])
+  const selectedProjects = useMemo(() => projects[configuredConnection.id], [projects, configuredConnection?.id])
 
   useEffect(() => {
     console.log('>> OVER HERE!!!', selectedBoards)
   }, [selectedBoards])
 
+  useEffect(() => {
+    console.log('>> OVER HERE FOR Projects!!!', selectedProjects)
+  }, [selectedProjects])
+
   return (
     <div className='workflow-step workflow-step-data-scope' data-step={activeStep?.id}>
       {blueprintConnections.length > 0 && (
@@ -120,27 +113,28 @@ const DataScopes = (props) => {
                   </h3>
                   <Divider className='section-divider' />
 
-                  {[Providers.GITLAB, Providers.GITHUB].includes(
+                  {[Providers.GITHUB].includes(
                     configuredConnection.provider
                   ) && (
                     <>
                       <h4>Projects *</h4>
-                      {configuredConnection.provider === Providers.GITHUB && (<p>Enter the project names you would like to sync.</p>)}
-                      {configuredConnection.provider === Providers.GITLAB && (<p>Enter the project ids you would like to sync.</p>)}
+                      <p>Enter the project names you would like to sync.</p>
                       <TagInput
                         id='project-id'
                         disabled={isRunning}
-                        placeholder={
-                          configuredConnection.provider === Providers.GITHUB
-                            ? 'username/repo, username/another-repo'
-                            : '1000000, 200000'
-                        }
-                        values={projects[configuredConnection.id] || []}
+                        placeholder='username/repo, username/another-repo'
+                        values={projects[configuredConnection.id]?.map(p => p.value) || []}
                         fill={true}
                         onChange={(values) =>
                           setProjects((p) => ({
                             ...p,
-                            [configuredConnection.id]: [...new Set(values)],
+                            [configuredConnection.id]: [...values.map((v, vIdx) => ({
+                              id: v,
+                              key: v,
+                              title: v,
+                              value: v,
+                              type: 'string'
+                            }))],
                           }))}
                         addOnPaste={true}
                         addOnBlur={true}
@@ -184,6 +178,25 @@ const DataScopes = (props) => {
                     </>
                   )}
 
+                  {[Providers.GITLAB].includes(configuredConnection.provider) && (
+                    <>
+                      <h4>Projects *</h4>
+                      <p>Select the project you would like to sync.</p>
+                      <GitlabProjectsSelector
+                        onFetch={fetchGitlabProjects}
+                        isFetching={isFetchingGitlab}
+                        items={gitlabProjects}
+                        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 bba7e3cc..ffb3b45d 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -101,8 +101,8 @@ const DataTransformations = (props) => {
 
   const [entityList, setEntityList] = useState(boardsAndProjects?.map((e, eIdx) => ({
     id: eIdx,
-    value: e?.value || e,
-    title: e?.title || e,
+    value: e?.value,
+    title: e?.title,
     entity: e,
     type: typeof e === 'object' ? 'board' : 'project'
   })))
@@ -308,7 +308,7 @@ const DataTransformations = (props) => {
                       {!useDropdownSelector && (
                         <>
                           <h4>Project</h4>
-                          <p style={{ color: '#292B3F' }}>{configuredProject || configuredBoard?.name || '< select a project >'}</p>
+                          <p style={{ color: '#292B3F' }}>{configuredProject?.title || configuredBoard?.title || '< select a project >'}</p>
                         </>
                       )}
                       <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
diff --git a/config-ui/src/config/jiraApiProxy.js b/config-ui/src/config/gitlabApiProxy.js
similarity index 66%
copy from config-ui/src/config/jiraApiProxy.js
copy to config-ui/src/config/gitlabApiProxy.js
index cf349cdd..edcfbe27 100644
--- a/config-ui/src/config/jiraApiProxy.js
+++ b/config-ui/src/config/gitlabApiProxy.js
@@ -15,17 +15,11 @@
  * limitations under the License.
  *
  */
-const API_VERSION = 2
 // @todo: add string replacer for [:connectionId] or refactor this const
-const API_PROXY_ENDPOINT = '/api/plugins/jira/connections/[:connectionId:]/proxy/rest'
-const ISSUE_TYPES_ENDPOINT = `${API_PROXY_ENDPOINT}/api/${API_VERSION}/issuetype`
-const ISSUE_FIELDS_ENDPOINT = `${API_PROXY_ENDPOINT}/api/${API_VERSION}/field`
-const BOARDS_ENDPOINT = `${API_PROXY_ENDPOINT}/agile/1.0/board`
+const GITLAB_API_PROXY_ENDPOINT = '/api/plugins/gitlab/connections/[:connectionId:]/proxy/rest'
+const PROJECTS_ENDPOINT = `${GITLAB_API_PROXY_ENDPOINT}/projects?search=[:search:]&membership=[:membership:]`
 
 export {
-  API_VERSION,
-  API_PROXY_ENDPOINT,
-  ISSUE_TYPES_ENDPOINT,
-  ISSUE_FIELDS_ENDPOINT,
-  BOARDS_ENDPOINT,
+  GITLAB_API_PROXY_ENDPOINT,
+  PROJECTS_ENDPOINT,
 }
diff --git a/config-ui/src/config/jiraApiProxy.js b/config-ui/src/config/jiraApiProxy.js
index cf349cdd..3c0ac201 100644
--- a/config-ui/src/config/jiraApiProxy.js
+++ b/config-ui/src/config/jiraApiProxy.js
@@ -15,16 +15,16 @@
  * limitations under the License.
  *
  */
-const API_VERSION = 2
+const JIRA_API_VERSION = 2
 // @todo: add string replacer for [:connectionId] or refactor this const
-const API_PROXY_ENDPOINT = '/api/plugins/jira/connections/[:connectionId:]/proxy/rest'
-const ISSUE_TYPES_ENDPOINT = `${API_PROXY_ENDPOINT}/api/${API_VERSION}/issuetype`
-const ISSUE_FIELDS_ENDPOINT = `${API_PROXY_ENDPOINT}/api/${API_VERSION}/field`
-const BOARDS_ENDPOINT = `${API_PROXY_ENDPOINT}/agile/1.0/board`
+const JIRA_API_PROXY_ENDPOINT = '/api/plugins/jira/connections/[:connectionId:]/proxy/rest'
+const ISSUE_TYPES_ENDPOINT = `${JIRA_API_PROXY_ENDPOINT}/api/${JIRA_API_VERSION}/issuetype`
+const ISSUE_FIELDS_ENDPOINT = `${JIRA_API_PROXY_ENDPOINT}/api/${JIRA_API_VERSION}/field`
+const BOARDS_ENDPOINT = `${JIRA_API_PROXY_ENDPOINT}/agile/1.0/board`
 
 export {
-  API_VERSION,
-  API_PROXY_ENDPOINT,
+  JIRA_API_VERSION,
+  JIRA_API_PROXY_ENDPOINT,
   ISSUE_TYPES_ENDPOINT,
   ISSUE_FIELDS_ENDPOINT,
   BOARDS_ENDPOINT,
diff --git a/config-ui/src/hooks/useBlueprintValidation.jsx b/config-ui/src/hooks/useBlueprintValidation.jsx
index 9ff6f66c..ef66ff6b 100644
--- a/config-ui/src/hooks/useBlueprintValidation.jsx
+++ b/config-ui/src/hooks/useBlueprintValidation.jsx
@@ -84,9 +84,9 @@ function useBlueprintValidation ({
     return Array.isArray(set) ? set.every(i => !isNaN(i)) : false
   }, [])
 
-  const validateRepositoryName = useCallback((set = []) => {
+  const validateRepositoryName = useCallback((projects = []) => {
     const repoRegExp = /([a-z0-9_-]){2,}\/([a-z0-9_-]){2,}$/gi
-    return set.every(i => i.match(repoRegExp))
+    return projects.every(p => p.value.match(repoRegExp))
   }, [])
 
   const valiateNonEmptySet = useCallback((set = []) => {
@@ -151,9 +151,6 @@ function useBlueprintValidation ({
           if (activeProvider?.id === Providers.GITLAB && projects[activeConnection?.id]?.length === 0) {
             errs.push('Projects: No Project IDs entered.')
           }
-          if (activeProvider?.id === Providers.GITLAB && !validateNumericSet(projects[activeConnection?.id])) {
-            errs.push('Projects: Only Numeric Project IDs are supported.')
-          }
 
           connections.forEach(c => {
             if (c.provider === Providers.JIRA && boards[c?.id]?.length === 0) {
@@ -168,9 +165,6 @@ function useBlueprintValidation ({
             if (c.provider === Providers.GITLAB && projects[c?.id]?.length === 0) {
               errs.push(`${c.name} requires Project IDs`)
             }
-            if (c.provider === Providers.GITLAB && !validateNumericSet(projects[c?.id])) {
-              errs.push(`${c.name} has invalid Project ID`)
-            }
             if (entities[c?.id]?.length === 0) {
               errs.push(`${c.name} is missing Data Entities`)
             }
diff --git a/config-ui/src/hooks/useDataScopesManager.jsx b/config-ui/src/hooks/useDataScopesManager.jsx
index 28060fdf..c1cdbeea 100644
--- a/config-ui/src/hooks/useDataScopesManager.jsx
+++ b/config-ui/src/hooks/useDataScopesManager.jsx
@@ -126,7 +126,8 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           newScope = boards[connection.id]?.map((b) => ({
             ...newScope,
             options: {
-              boardId: Number(b?.id),
+              boardId: Number(b?.value),
+              title: b.title
               // @todo: verify initial value of since date for jira provider
               // since: new Date(),
             },
@@ -137,7 +138,8 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           newScope = projects[connection.id]?.map((p) => ({
             ...newScope,
             options: {
-              projectId: Number(p),
+              projectId: Number(p.value),
+              title: p.title
             },
             transformation: {},
           }))
@@ -154,8 +156,8 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           newScope = projects[connection.id]?.map((p) => ({
             ...newScope,
             options: {
-              owner: p.split('/')[0],
-              repo: p.split('/')[1],
+              owner: p.value.split('/')[0],
+              repo: p.value.split('/')[1],
             },
             transformation: { ...transformations[p] },
           }))
diff --git a/config-ui/src/hooks/useGitlab.jsx b/config-ui/src/hooks/useGitlab.jsx
new file mode 100644
index 00000000..0eca9a2e
--- /dev/null
+++ b/config-ui/src/hooks/useGitlab.jsx
@@ -0,0 +1,86 @@
+/*
+ * 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 useGitlab = ({ apiProxyPath, projectsEndpoint }, activeConnection = null) => {
+  const [isFetching, setIsFetching] = useState(false)
+  const [projects, setProjects] = useState([])
+  const [error, setError] = useState()
+
+  const fetchProjects = useCallback(async (search = '', onlyQueryMemberRepo = true) => {
+    try {
+      if (apiProxyPath.includes('null')) {
+        throw new Error('Connection ID is Null')
+      }
+      setError(null)
+      setIsFetching(true)
+      if (search.length > 2) {
+        // only search when type more than 2 chars
+        const endpoint = projectsEndpoint
+          .replace('[:connectionId:]', activeConnection?.connectionId)
+          .replace('[:search:]', search)
+          .replace('[:membership:]', onlyQueryMemberRepo ? 1 : 0)
+        const projectsResponse = await request.get(endpoint)
+        if (projectsResponse && projectsResponse.status === 200 && projectsResponse.data) {
+          setProjects(createListData(projectsResponse.data))
+        } else {
+          throw new Error('request projects fail')
+        }
+      } else {
+        setProjects([])
+      }
+    } catch (e) {
+      setError(e)
+      ToastNotification.show({ message: e.message, intent: 'danger', icon: 'error' })
+    } finally {
+      setIsFetching(false)
+    }
+  }, [projectsEndpoint, activeConnection, apiProxyPath])
+
+  const createListData = (
+    data = [],
+    titleProperty = 'name_with_namespace',
+    valueProperty = 'id',
+    iconProperty = 'avatar_url',
+  ) => {
+    return data.map((d, dIdx) => ({
+      id: d[valueProperty],
+      key: d[valueProperty],
+      title: d[titleProperty],
+      shortTitle: d.name,
+      value: d[valueProperty],
+      icon: d[iconProperty],
+      type: 'string'
+    }))
+  }
+
+  useEffect(() => {
+    console.log('>>> GITLAB API PROXY: FIELD SELECTOR PROJECTS DATA', projects)
+  }, [projects])
+
+  return {
+    isFetching,
+    fetchProjects,
+    projects,
+    error
+  }
+}
+
+export default useGitlab
diff --git a/config-ui/src/hooks/useJIRA.jsx b/config-ui/src/hooks/useJIRA.jsx
index 09428c09..dcc9e14b 100644
--- a/config-ui/src/hooks/useJIRA.jsx
+++ b/config-ui/src/hooks/useJIRA.jsx
@@ -153,13 +153,13 @@ const useJIRA = ({ apiProxyPath, issuesEndpoint, fieldsEndpoint, boardsEndpoint
     activeConnection,
     apiProxyPath])
 
-  const createListData = (data = [], titleProperty = 'name', valueProperty = 'name') => {
+  const createListData = (data = [], titleProperty = 'name', valueProperty = 'id') => {
     return data.map((d, dIdx) => ({
-      ...d,
-      id: d.id || dIdx,
-      key: d.key ? d.key : dIdx,
+      id: d[valueProperty],
+      key: d[valueProperty],
       title: d[titleProperty],
       value: d[valueProperty],
+      icon: d?.location?.avatarURI,
       type: d.schema?.type || 'string'
     }))
   }
@@ -175,7 +175,7 @@ const useJIRA = ({ apiProxyPath, issuesEndpoint, fieldsEndpoint, boardsEndpoint
   }, [fieldsResponse])
 
   useEffect(() => {
-    setBoards(boardsResponse ? createListData(boardsResponse, 'name', 'name') : [])
+    setBoards(boardsResponse ? createListData(boardsResponse, 'name', 'id') : [])
   }, [boardsResponse])
 
   useEffect(() => {
diff --git a/config-ui/src/pages/blueprints/blueprint-settings.jsx b/config-ui/src/pages/blueprints/blueprint-settings.jsx
index 5b7c636e..fcc77747 100644
--- a/config-ui/src/pages/blueprints/blueprint-settings.jsx
+++ b/config-ui/src/pages/blueprints/blueprint-settings.jsx
@@ -20,7 +20,7 @@ import { useParams, useHistory } from 'react-router-dom'
 import { ENVIRONMENT } from '@/config/environment'
 import dayjs from '@/utils/time'
 import {
-  API_PROXY_ENDPOINT,
+  JIRA_API_PROXY_ENDPOINT,
   ISSUE_TYPES_ENDPOINT,
   ISSUE_FIELDS_ENDPOINT,
   BOARDS_ENDPOINT,
@@ -72,6 +72,8 @@ import BlueprintDataScopesDialog from '@/components/blueprints/BlueprintDataScop
 import BlueprintNavigationLinks from '@/components/blueprints/BlueprintNavigationLinks'
 import DataScopesGrid from '@/components/blueprints/DataScopesGrid'
 import AdvancedJSON from '@/components/blueprints/create-workflow/AdvancedJSON'
+import useGitlab from '@/hooks/useGitlab'
+import { GITLAB_API_PROXY_ENDPOINT, PROJECTS_ENDPOINT } from '@/config/gitlabApiProxy'
 
 const BlueprintSettings = (props) => {
   // eslint-disable-next-line no-unused-vars
@@ -288,7 +290,7 @@ const BlueprintSettings = (props) => {
     error: jiraProxyError,
   } = useJIRA(
     {
-      apiProxyPath: API_PROXY_ENDPOINT,
+      apiProxyPath: JIRA_API_PROXY_ENDPOINT,
       issuesEndpoint: ISSUE_TYPES_ENDPOINT,
       fieldsEndpoint: ISSUE_FIELDS_ENDPOINT,
       boardsEndpoint: BOARDS_ENDPOINT,
@@ -296,6 +298,19 @@ const BlueprintSettings = (props) => {
     configuredConnection
   )
 
+  const {
+    fetchProjects: fetchGitlabProjects,
+    projects: gitlabProjects,
+    isFetching: isFetchingGitlab,
+    error: gitlabProxyError,
+  } = useGitlab(
+    {
+      apiProxyPath: GITLAB_API_PROXY_ENDPOINT,
+      projectsEndpoint: PROJECTS_ENDPOINT,
+    },
+    configuredConnection
+  )
+
   const handleBlueprintActivation = useCallback(
     (blueprint) => {
       if (blueprint.enable) {
@@ -454,7 +469,7 @@ const BlueprintSettings = (props) => {
               break
             case Providers.GITLAB:
               isValid = Array.isArray(projects[configuredConnection?.id]) &&
-              validateNumericSet(projects[configuredConnection?.id]) &&
+                projects[configuredConnection?.id]?.length > 0 &&
               entities[configuredConnection?.id]?.length > 0
               break
             case Providers.JIRA:
@@ -529,17 +544,13 @@ const BlueprintSettings = (props) => {
   }, [setConfiguredBoard])
 
   // @todo: lift higher to dsm hook
-  const getJiraMappedBoards = useCallback((boardIds = [], boardListItems = []) => {
-    return boardIds.map((bId, sIdx) => {
-      const boardObject = boardListItems.find(apiBoard => Number(apiBoard.id) === Number(bId))
+  const getJiraMappedBoards = useCallback((options = []) => {
+    return options.map(({ boardId, title }, sIdx) => {
       return {
-        ...boardObject,
-        id: boardObject?.id || bId || sIdx + 1,
-        key: sIdx,
-        value: boardObject?.name || `Board ${bId}`,
-        title: boardObject?.name || `Board ${bId}`,
-        type: boardObject?.type || 'scrum',
-        self: `https://${scopeConnection?.endpoint}agile/1.0/board/${bId}`
+        id: boardId,
+        key: boardId,
+        value: boardId,
+        title: title || `Board ${boardId}`,
       }
     })
   }, [scopeConnection?.endpoint])
@@ -579,20 +590,45 @@ const BlueprintSettings = (props) => {
   useEffect(() => {
     console.log('>>> ACTIVE BLUEPRINT ....', activeBlueprint)
     const getGithubProjects = (c) => [Providers.GITHUB].includes(c.plugin)
-      ? c.scope.map((s) => `${s.options.owner}/${s.options?.repo}`)
+      ? c.scope.map((s) => ({
+        id: `${s.options.owner}/${s.options?.repo}`,
+        key: `${s.options.owner}/${s.options?.repo}`,
+        value: `${s.options.owner}/${s.options?.repo}`,
+        title: `${s.options.owner}/${s.options?.repo}`,
+      }))
       : []
     const getGitlabProjects = (c) => [Providers.GITLAB].includes(c.plugin)
-      ? c.scope.map((s) => s.options?.projectId)
+      ? c.scope.map((s) => ({
+        id: s.options?.projectId,
+        key: s.options?.projectId,
+        value: s.options?.projectId,
+        title: s.options?.title || `Project ${s.options?.projectId}`,
+      }))
       : []
     // @todo: handle multi-stage
     const getAdvancedGithubProjects = (t, providerId) => [Providers.GITHUB].includes(providerId)
-      ? [`${t.options?.owner}/${t.options?.repo}`]
+      ? [{
+          id: `${t.options?.owner}/${t.options?.repo}`,
+          key: `${t.options?.owner}/${t.options?.repo}`,
+          value: `${t.options?.owner}/${t.options?.repo}`,
+          title: `${t.options?.owner}/${t.options?.repo}`,
+        }]
       : []
     const getAdvancedGitlabProjects = (t, providerId) => [Providers.GITLAB].includes(providerId)
-      ? [t.options?.projectId]
+      ? [{
+          id: t.options?.projectId,
+          key: t.options?.projectId,
+          value: t.options?.projectId,
+          title: t.options?.title || `Project ${t.options?.projectId}`,
+        }]
       : []
     const getAdvancedJiraBoards = (t, providerId) => [Providers.JIRA].includes(providerId)
-      ? [t.options?.boardId]
+      ? [{
+          id: t.options?.boardId,
+          key: t.options?.boardId,
+          value: t.options?.boardId,
+          title: t.options?.title || `Board ${t.options?.boardId}`,
+        }]
       : []
     // @todo: migrate to data scopes manager
     if (activeBlueprint?.id && activeBlueprint?.mode === BlueprintMode.NORMAL) {
@@ -615,12 +651,12 @@ const BlueprintSettings = (props) => {
             ? getGitlabProjects(c)
             : getGithubProjects(c),
           boards: [Providers.JIRA].includes(c.plugin)
-            ? c.scope.map((s) => `Board ${s.options?.boardId}`)
+            ? c.scope.map((s) => s.options?.title || `Board ${s.options?.boardId}`)
             : [],
           boardIds: [Providers.JIRA].includes(c.plugin)
             ? c.scope.map((s) => s.options?.boardId)
             : [],
-          boardsList: allJiraResources?.boards ? getJiraMappedBoards(c.scope.map((s) => s.options?.boardId), allJiraResources?.boards) : [],
+          boardsList: getJiraMappedBoards(c.scope.map((s) => s.options)),
           transformations: c.scope.map((s) => ({ ...s.transformation })),
           transformationStates: c.scope.map((s) =>
             Object.values(s.transformation).some((v) => Array.isArray(v) ? v.length > 0 : (v && typeof v === 'object' ? Object.keys(v)?.length > 0 : v?.toString().length > 0))
@@ -658,13 +694,13 @@ const BlueprintSettings = (props) => {
           entities: ['-'],
           entitityList: getDefaultEntities(c.plugin),
           boards: [Providers.JIRA].includes(c.plugin)
-            ? getAdvancedJiraBoards(c, c.plugin).map(bId => `Board ${bId}`)
+            ? getAdvancedJiraBoards(c, c.plugin).map(board => board.title)
             : [],
           boardIds: [Providers.JIRA].includes(c.plugin)
             ? getAdvancedJiraBoards(c, c.plugin)
             : [],
-          boardList: [Providers.JIRA].includes(c.plugin)
-            ? getAdvancedJiraBoards(c, c.plugin).map(bId => `Board ${bId}`)
+          boardsList: [Providers.JIRA].includes(c.plugin)
+            ? getAdvancedJiraBoards(c, c.plugin)
             : [],
           transformations: {},
           // transformationStates: ['-'],
@@ -1190,7 +1226,7 @@ const BlueprintSettings = (props) => {
                       blueprint={activeBlueprint}
                       onModify={modifyConnection}
                       mode={activeBlueprint?.mode}
-                      loading={isFetchingBlueprint || isFetchingJIRA}
+                      loading={isFetchingBlueprint || isFetchingJIRA || isFetchingGitlab}
                     />
                   </div>
                 )
@@ -1228,7 +1264,7 @@ const BlueprintSettings = (props) => {
                     onModify={() => modifySetting('plan')}
                     mode={activeBlueprint?.mode}
                     classNames={['advanced-mode-grid']}
-                    loading={isFetchingBlueprint || isFetchingJIRA}
+                    loading={isFetchingBlueprint || isFetchingJIRA || isFetchingGitlab}
                   />
                 </div>
                 )}
@@ -1355,6 +1391,10 @@ const BlueprintSettings = (props) => {
         fieldsList={jiraApiFields}
         isFetching={isFetchingBlueprint}
         isFetchingJIRA={isFetchingJIRA}
+        fetchGitlabProjects={fetchGitlabProjects}
+        gitlabProjects={gitlabProjects}
+        isFetchingGitlab={isFetchingGitlab}
+        gitlabProxyError={gitlabProxyError}
         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 8602b6fe..fdc7c717 100644
--- a/config-ui/src/pages/blueprints/create-blueprint.jsx
+++ b/config-ui/src/pages/blueprints/create-blueprint.jsx
@@ -20,7 +20,7 @@ import { CSSTransition } from 'react-transition-group'
 import { useHistory, useLocation, Link } from 'react-router-dom'
 import dayjs from '@/utils/time'
 import {
-  API_PROXY_ENDPOINT,
+  JIRA_API_PROXY_ENDPOINT,
   ISSUE_TYPES_ENDPOINT,
   ISSUE_FIELDS_ENDPOINT,
   BOARDS_ENDPOINT,
@@ -69,6 +69,8 @@ import AdvancedJSON from '@/components/blueprints/create-workflow/AdvancedJSON'
 
 import { DEVLAKE_ENDPOINT } from '@/utils/config'
 import request from '@/utils/request'
+import useGitlab from '@/hooks/useGitlab'
+import { GITLAB_API_PROXY_ENDPOINT, PROJECTS_ENDPOINT } from '@/config/gitlabApiProxy'
 
 // import ConnectionTabs from '@/components/blueprints/ConnectionTabs'
 
@@ -241,7 +243,7 @@ const CreateBlueprint = (props) => {
     error: jiraProxyError,
   } = useJIRA(
     {
-      apiProxyPath: API_PROXY_ENDPOINT,
+      apiProxyPath: JIRA_API_PROXY_ENDPOINT,
       issuesEndpoint: ISSUE_TYPES_ENDPOINT,
       fieldsEndpoint: ISSUE_FIELDS_ENDPOINT,
       boardsEndpoint: BOARDS_ENDPOINT,
@@ -249,6 +251,19 @@ const CreateBlueprint = (props) => {
     configuredConnection
   )
 
+  const {
+    fetchProjects: fetchGitlabProjects,
+    projects: gitlabProjects,
+    isFetching: isFetchingGitlab,
+    error: gitlabProxyError,
+  } = useGitlab(
+    {
+      apiProxyPath: GITLAB_API_PROXY_ENDPOINT,
+      projectsEndpoint: PROJECTS_ENDPOINT,
+    },
+    configuredConnection
+  )
+
   const {
     testConnection,
     // eslint-disable-next-line no-unused-vars
@@ -350,7 +365,7 @@ const CreateBlueprint = (props) => {
     null
   )
 
-  const activeTransformation = useMemo(() => transformations[configuredProject || configuredBoard?.id], [transformations, configuredProject, configuredBoard?.id])
+  const activeTransformation = useMemo(() => transformations[configuredProject?.id || configuredBoard?.id], [transformations, configuredProject?.id, configuredBoard?.id])
 
   // eslint-disable-next-line no-unused-vars
   const isValidStep = useCallback((stepId) => { }, [])
@@ -462,7 +477,7 @@ const CreateBlueprint = (props) => {
     )
     setTransformations((existingTransformations) => ({
       ...existingTransformations,
-      [configuredProject]: {},
+      [configuredProject?.id]: {},
       [configuredBoard?.id]: {},
     }))
     setConfiguredProject(null)
@@ -799,10 +814,8 @@ const CreateBlueprint = (props) => {
     console.log('>> PROJECTS LIST', projects)
     console.log('>> BOARDS LIST', boards)
 
-    const projectTransformation = projects[configuredConnection?.id]
-    const boardTransformation = boards[configuredConnection?.id]?.map(
-      (b) => b.id
-    )
+    const projectTransformation = projects[configuredConnection?.id]?.map(p => p.id)
+    const boardTransformation = boards[configuredConnection?.id]?.map(b => b.id)
     if (projectTransformation) {
       setTransformations((cT) => ({
         ...projectTransformation.reduce(initializeTransformations, {}),
@@ -1034,6 +1047,9 @@ const CreateBlueprint = (props) => {
                       blueprintConnections={blueprintConnections}
                       dataEntitiesList={dataEntitiesList}
                       boardsList={boardsList}
+                      fetchGitlabProjects={fetchGitlabProjects}
+                      isFetchingGitlab={isFetchingGitlab}
+                      gitlabProjects={gitlabProjects}
                       boards={boards}
                       dataEntities={dataEntities}
                       projects={projects}
@@ -1046,6 +1062,7 @@ const CreateBlueprint = (props) => {
                       isSaving={isSaving}
                       isRunning={isRunning}
                       validationErrors={[...validationErrors, ...blueprintValidationErrors]}
+                      isFetching={isFetchingJIRA || isFetchingGitlab || isFetchingConnection}
                     />
                   )}
 
@@ -1058,7 +1075,6 @@ const CreateBlueprint = (props) => {
                       blueprintConnections={blueprintConnections}
                       dataEntities={dataEntities}
                       projects={projects}
-                      boardsList={boardsList}
                       boards={boards}
                       issueTypes={jiraApiIssueTypes}
                       fields={jiraApiFields}
@@ -1115,7 +1131,7 @@ const CreateBlueprint = (props) => {
               onPrev={prevStep}
               onSave={handleBlueprintSave}
               onSaveAndRun={handleBlueprintSaveAndRun}
-              isLoading={isSaving || isFetchingJIRA || isFetchingConnection || isTestingConnection}
+              isLoading={isSaving || isFetchingJIRA || isFetchingGitlab || isFetchingConnection || isTestingConnection}
               isValid={advancedMode ? isValidBlueprint && isValidPipeline : isValidBlueprint}
               canGoNext={canAdvanceNext}
             />
diff --git a/config-ui/src/pages/configure/settings/github.jsx b/config-ui/src/pages/configure/settings/github.jsx
index 9afeb793..0f53a5b2 100644
--- a/config-ui/src/pages/configure/settings/github.jsx
+++ b/config-ui/src/pages/configure/settings/github.jsx
@@ -51,12 +51,12 @@ export default function GithubSettings (props) {
 
   // eslint-disable-next-line no-unused-vars
   const handleSettingsChange = useCallback((setting) => {
-    onSettingsChange(setting, configuredProject)
+    onSettingsChange(setting, configuredProject?.id)
   }, [onSettingsChange, configuredProject])
 
   const handleAdditionalSettings = useCallback((setting) => {
     setEnableAdditionalCalculations(setting)
-    onSettingsChange({ refdiff: setting ? { tagsOrder: '', tagsPattern: '', tagsLimit: 10, } : null }, configuredProject)
+    onSettingsChange({ refdiff: setting ? { tagsOrder: '', tagsPattern: '', tagsLimit: 10, } : null }, configuredProject?.id)
   }, [setEnableAdditionalCalculations, configuredProject, onSettingsChange])
 
   useEffect(() => {
@@ -94,7 +94,7 @@ export default function GithubSettings (props) {
                   placeholder='severity/(.*)$'
                 // defaultValue={transformation?.issueSeverity}
                   value={transformation?.issueSeverity}
-                  onChange={(e) => onSettingsChange({ issueSeverity: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issueSeverity: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -115,7 +115,7 @@ export default function GithubSettings (props) {
                   id='github-issue-component'
                   placeholder='component/(.*)$'
                   value={transformation?.issueComponent}
-                  onChange={(e) => onSettingsChange({ issueComponent: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issueComponent: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -135,7 +135,7 @@ export default function GithubSettings (props) {
                   id='github-issue-priority'
                   placeholder='(highest|high|medium|low)$'
                   value={transformation?.issuePriority}
-                  onChange={(e) => onSettingsChange({ issuePriority: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issuePriority: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -155,7 +155,7 @@ export default function GithubSettings (props) {
                   id='github-issue-requirement'
                   placeholder='(feat|feature|proposal|requirement)$'
                   value={transformation?.issueTypeRequirement}
-                  onChange={(e) => onSettingsChange({ issueTypeRequirement: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issueTypeRequirement: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -175,7 +175,7 @@ export default function GithubSettings (props) {
                   id='github-issue-bug'
                   placeholder='(bug|broken)$'
                   value={transformation?.issueTypeBug}
-                  onChange={(e) => onSettingsChange({ issueTypeBug: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issueTypeBug: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -195,7 +195,7 @@ export default function GithubSettings (props) {
                   id='github-issue-incident'
                   placeholder='(incident|p0|p1|p2)$'
                   value={transformation?.issueTypeIncident}
-                  onChange={(e) => onSettingsChange({ issueTypeIncident: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ issueTypeIncident: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -224,7 +224,7 @@ export default function GithubSettings (props) {
                   id='github-pr-type'
                   placeholder='type/(.*)$'
                   value={transformation?.prType}
-                  onChange={(e) => onSettingsChange({ prType: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ prType: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -244,7 +244,7 @@ export default function GithubSettings (props) {
                   id='github-pr-type'
                   placeholder='component/(.*)$'
                   value={transformation?.prComponent}
-                  onChange={(e) => onSettingsChange({ prComponent: e.target.value }, configuredProject)}
+                  onChange={(e) => onSettingsChange({ prComponent: e.target.value }, configuredProject?.id)}
                   disabled={isSaving || isSavingConnection}
                   className='input'
                   maxLength={255}
@@ -297,7 +297,7 @@ export default function GithubSettings (props) {
                 id='github-pr-body'
                 className='textarea'
                 placeholder='(?mi)(fix|close|resolve|fixes|closes|resolves|fixed|closed|resolved)[\s]*.*(((and )?(#|https:\/\/github.com\/%s\/%s\/issues\/)\d+[ ]*)+)'
-                onChange={(e) => onSettingsChange({ prBodyClosePattern: e.target.value }, configuredProject)}
+                onChange={(e) => onSettingsChange({ prBodyClosePattern: e.target.value }, configuredProject?.id)}
                 disabled={isSaving || isSavingConnection}
                 fill
                 rows={2}
@@ -327,7 +327,7 @@ export default function GithubSettings (props) {
                     fill={true}
                     placeholder='10'
                     allowNumericCharactersOnly={true}
-                    onValueChange={(tagsLimitNumeric) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsLimit: tagsLimitNumeric } }, configuredProject)}
+                    onValueChange={(tagsLimitNumeric) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsLimit: tagsLimitNumeric } }, configuredProject?.id)}
                     value={transformation?.refdiff?.tagsLimit}
                   />
                 </FormGroup>
@@ -342,7 +342,7 @@ export default function GithubSettings (props) {
                     id='refdiff-tags-pattern'
                     placeholder='(regex)$'
                     value={transformation?.refdiff?.tagsPattern}
-                    onChange={(e) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsPattern: e.target.value } }, configuredProject)}
+                    onChange={(e) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsPattern: e.target.value } }, configuredProject?.id)}
                     disabled={isSaving || isSavingConnection}
                     className='input'
                     maxLength={255}
@@ -359,7 +359,7 @@ export default function GithubSettings (props) {
                     id='refdiff-tags-order'
                     placeholder='reverse semver'
                     value={transformation?.refdiff?.tagsOrder}
-                    onChange={(e) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsOrder: e.target.value } }, configuredProject)}
+                    onChange={(e) => onSettingsChange({ refdiff: { ...transformation?.refdiff, tagsOrder: e.target.value } }, configuredProject?.id)}
                     disabled={isSaving || isSavingConnection}
                     className='input'
                     maxLength={255}