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/20 17:06:43 UTC

[incubator-devlake] branch main updated: feat: add deployment tag transformation setting (#3093)

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 4f748b7e feat: add deployment tag transformation setting (#3093)
4f748b7e is described below

commit 4f748b7e54c418686e23d6f9b0cf141d00439501
Author: Julien Chinapen <ju...@merico.dev>
AuthorDate: Tue Sep 20 13:06:38 2022 -0400

    feat: add deployment tag transformation setting (#3093)
    
    * feat: setup deployment tag transform property
    
    * fix: update jenkins deploy tag transform
    
    * fix: restore missing github transform settings
    
    * fix: resolve missing github active transform
    
    * fix: apply linting cleanups
    
    * fix: use provider specific deploy tag hint
    
    * fix: populate deploy tag by default
    
    * fix: create active configuration key
    
    * fix: restore jenkins transforms on modify scope
    
    * fix: use change handler directly for deploy tag
    
    * feat: add tooltip warning if deploy tag blank
    
    * fix: resolve linter warnings
    
    * fix: enable project selector for gitlab
    
    * fix: add duplicate project obj validation check
    
    * fix: add array check for all connections response
    
    * fix: enable offline-mode when connections 404
    
    * fix: remove spinner icon from go-back button
    
    * fix: lift new connections state props to dsm hook
    
    * fix: expand deployment to 3 separate properties
    
    * fix: prevent next advance if connection offline
    
    * fix: show no transforms message for tapd
    
    * fix: resolve linter warnings with jira transforms
    
    * fix: resolve lint warnings on integrations manager
    
    * fix: deactivate staging and testing deploy tags
    
    * fix: add handler for deployment option selection
    
    * fix: resolve linter warnings
    
    * chore: resolve linter warnings
    
    * fix: disable trailing comma rule for prettier
    
    * chore: resolve lint warnings
---
 config-ui/.prettierrc.js                           |   2 +-
 .../blueprints/BlueprintDataScopesDialog.jsx       |  10 +-
 .../src/components/blueprints/DataScopesGrid.jsx   |   4 +-
 .../blueprints/GitlabProjectsSelector.jsx          |   3 +-
 .../blueprints/ProviderTransformationSettings.jsx  |  22 +-
 .../blueprints/create-workflow/DataScopes.jsx      |   3 +-
 .../create-workflow/DataTransformations.jsx        | 329 +++++++++------------
 .../blueprints/transformations/CICD/Deployment.jsx | 251 ++++++++++++++++
 .../{.prettierrc.js => src/data/DataScopes.js}     |  13 +-
 config-ui/src/hooks/useBlueprintValidation.jsx     |  16 +-
 config-ui/src/hooks/useConnectionManager.jsx       |  19 +-
 config-ui/src/hooks/useDataScopesManager.jsx       | 133 +++++++--
 config-ui/src/hooks/useNetworkOfflineMode.jsx      |   2 +-
 config-ui/src/models/Connection.js                 |   2 +
 .../src/pages/blueprints/blueprint-settings.jsx    |  12 +-
 .../src/pages/blueprints/create-blueprint.jsx      |  88 +++---
 config-ui/src/pages/blueprints/index.jsx           |  10 +-
 .../pages/configure/connections/AddConnection.jsx  |   2 +-
 .../configure/connections/ConfigureConnection.jsx  |   2 +-
 .../src/pages/configure/integration/manage.jsx     |   2 +-
 config-ui/src/pages/configure/settings/github.jsx  |  24 +-
 config-ui/src/pages/configure/settings/gitlab.jsx  |  50 ++--
 config-ui/src/pages/configure/settings/jenkins.jsx |  54 +++-
 config-ui/src/pages/configure/settings/jira.jsx    |  10 +-
 config-ui/src/pages/offline/index.jsx              |  15 +-
 config-ui/src/styles/offline.scss                  |   6 +-
 26 files changed, 737 insertions(+), 347 deletions(-)

diff --git a/config-ui/.prettierrc.js b/config-ui/.prettierrc.js
index fb4c19b2..edcba14e 100644
--- a/config-ui/.prettierrc.js
+++ b/config-ui/.prettierrc.js
@@ -20,6 +20,6 @@ module.exports = {
   printWidth: 140,
   singleQuote: true,
   jsxSingleQuote: true,
-  trailingComma: 'es5',
+  trailingComma: 'none',
   semi: false,
 }
diff --git a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
index b27fcc79..59a11b0b 100644
--- a/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
+++ b/config-ui/src/components/blueprints/BlueprintDataScopesDialog.jsx
@@ -66,6 +66,7 @@ const BlueprintDataScopesDialog = (props) => {
     configuredConnection,
     configuredProject,
     configuredBoard,
+    configurationKey,
     scopeConnection,
     dataEntitiesList = [],
     boardsList = [],
@@ -141,14 +142,6 @@ const BlueprintDataScopesDialog = (props) => {
     }
   } = props
 
-  // useEffect(() => {
-  //   console.log('>>> MY BOARDS LIST!!!!', boardsList)
-  // }, [boardsList])
-
-  // useEffect(() => {
-  //   console.log('>>> MY SELECTED BOARDS!!!!', boards)
-  // }, [boards])
-
   return (
     <>
       <MultistepDialog
@@ -223,6 +216,7 @@ const BlueprintDataScopesDialog = (props) => {
                 configuredConnection={configuredConnection}
                 configuredProject={configuredProject}
                 configuredBoard={configuredBoard}
+                configurationKey={configurationKey}
                 addBoardTransformation={addBoardTransformation}
                 addProjectTransformation={addProjectTransformation}
                 isSaving={isSaving}
diff --git a/config-ui/src/components/blueprints/DataScopesGrid.jsx b/config-ui/src/components/blueprints/DataScopesGrid.jsx
index 52930371..4424624a 100644
--- a/config-ui/src/components/blueprints/DataScopesGrid.jsx
+++ b/config-ui/src/components/blueprints/DataScopesGrid.jsx
@@ -204,9 +204,9 @@ const DataScopesGrid = (props) => {
               }}
             >
               <Button
-                disabled={mode === BlueprintMode.NORMAL && [Providers.JENKINS, Providers.TAPD].includes(c.providerId)}
+                disabled={mode === BlueprintMode.NORMAL && [Providers.TAPD].includes(c.providerId)}
                 icon='annotation'
-                intent={mode === BlueprintMode.NORMAL && c.providerId === Providers.JENKINS ? Intent.NONE : Intent.PRIMARY}
+                intent={mode === BlueprintMode.NORMAL && c.providerId === Providers.TAPD ? Intent.NONE : Intent.PRIMARY}
                 size={12}
                 small
                 minimal
diff --git a/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx b/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx
index d252335b..ab8d6e25 100644
--- a/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx
+++ b/config-ui/src/components/blueprints/GitlabProjectsSelector.jsx
@@ -18,6 +18,7 @@
 import React, { useEffect, useState } from 'react'
 import { Checkbox, Intent, MenuItem, Position, Tooltip } from '@blueprintjs/core'
 import { MultiSelect } from '@blueprintjs/select'
+import GitlabProject from '@/models/GitlabProject'
 
 const GitlabProjectsSelector = (props) => {
   const {
@@ -128,7 +129,7 @@ const GitlabProjectsSelector = (props) => {
                     ...rT,
                     [configuredConnection.id]: [
                       ...rT[configuredConnection.id],
-                      item,
+                      new GitlabProject(item),
                     ],
                   }
                 : { ...rT }
diff --git a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
index 1d4bfc09..c17e43e4 100644
--- a/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
+++ b/config-ui/src/components/blueprints/ProviderTransformationSettings.jsx
@@ -15,15 +15,15 @@
  * limitations under the License.
  *
  */
-import React, { Fragment, useEffect, useState, useCallback } from 'react'
+import React, { useEffect } from 'react'
 import {
   Providers,
-  ProviderTypes,
-  ProviderIcons,
-  ConnectionStatus,
-  ConnectionStatusLabels,
+  // ProviderTypes,
+  // ProviderIcons,
+  // ConnectionStatus,
+  // ConnectionStatusLabels,
 } from '@/data/Providers'
-import { DataEntities, DataEntityTypes } from '@/data/DataEntities'
+// import { DataEntities, DataEntityTypes } from '@/data/DataEntities'
 import JiraSettings from '@/pages/configure/settings/jira'
 import GitlabSettings from '@/pages/configure/settings/gitlab'
 import JenkinsSettings from '@/pages/configure/settings/jenkins'
@@ -39,6 +39,7 @@ const ProviderTransformationSettings = (props) => {
     configuredBoard,
     transformations = {},
     transformation = {},
+    entityIdKey,
     newTransformation = {},
     boards = {},
     projects = {},
@@ -53,6 +54,10 @@ const ProviderTransformationSettings = (props) => {
     isFetchingJIRA = false
   } = props
 
+  useEffect(() => {
+    console.log('OVER HERE!!!', entityIdKey)
+  }, [entityIdKey])
+
   return (
     <div className='transformation-settings' data-provider={provider?.id}>
       {provider?.id === Providers.GITHUB && (
@@ -62,6 +67,7 @@ const ProviderTransformationSettings = (props) => {
           configuredProject={configuredProject}
           projects={projects}
           transformation={transformation}
+          entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
           entities={entities[connection?.id]}
           isSaving={isSaving}
@@ -76,6 +82,7 @@ const ProviderTransformationSettings = (props) => {
           configuredProject={configuredProject}
           projects={projects}
           transformation={transformation}
+          entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
           entities={entities[connection?.id]}
           isSaving={isSaving}
@@ -93,6 +100,7 @@ const ProviderTransformationSettings = (props) => {
           issueTypes={issueTypes}
           fields={fields}
           transformation={transformation}
+          entityIdKey={entityIdKey}
           transformations={transformations}
           onSettingsChange={onSettingsChange}
           entities={entities[connection?.id]}
@@ -108,6 +116,7 @@ const ProviderTransformationSettings = (props) => {
           provider={provider}
           connection={connection}
           transformation={transformation}
+          entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
           entities={entities[connection?.id]}
           isSaving={isSaving}
@@ -119,6 +128,7 @@ const ProviderTransformationSettings = (props) => {
           provider={provider}
           connection={connection}
           transformation={transformation}
+          entityIdKey={entityIdKey}
           onSettingsChange={onSettingsChange}
           entities={entities[connection?.id]}
           isSaving={isSaving}
diff --git a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
index a550bc79..c2b65f4e 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataScopes.jsx
@@ -23,6 +23,7 @@ import BoardsSelector from '@/components/blueprints/BoardsSelector'
 import DataEntitiesSelector from '@/components/blueprints/DataEntitiesSelector'
 import NoData from '@/components/NoData'
 import GitlabProjectsSelector from '@/components/blueprints/GitlabProjectsSelector'
+import GitHubProject from '@/models/GithubProject'
 
 const DataScopes = (props) => {
   const {
@@ -128,7 +129,7 @@ const DataScopes = (props) => {
                         onChange={(values) =>
                           setProjects((p) => ({
                             ...p,
-                            [configuredConnection.id]: [...values.map((v, vIdx) => ({
+                            [configuredConnection.id]: [...values.map((v, vIdx) => new GitHubProject({
                               id: v,
                               key: v,
                               title: v,
diff --git a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
index ffb3b45d..ff3c9adf 100644
--- a/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
+++ b/config-ui/src/components/blueprints/create-workflow/DataTransformations.jsx
@@ -16,33 +16,12 @@
  *
  */
 import React, { Fragment, useEffect, useState, useCallback, useMemo } from 'react'
-import {
-  Button,
-  Icon,
-  Intent,
-  InputGroup,
-  MenuItem,
-  Divider,
-  Elevation,
-  Card,
-  Colors,
-  Spinner,
-  Tooltip,
-  Position
-} from '@blueprintjs/core'
+import { Button, Icon, Intent, InputGroup, MenuItem, Divider, Elevation, Card, Colors, Spinner, Tooltip, Position } from '@blueprintjs/core'
 import { Select } from '@blueprintjs/select'
 import { integrationsData } from '@/data/integrations'
-import {
-  Providers,
-  ProviderTypes,
-  ProviderIcons,
-  ConnectionStatus,
-  ConnectionStatusLabels,
-} from '@/data/Providers'
+import { Providers, ProviderTypes, ProviderIcons, ConnectionStatus, ConnectionStatusLabels } from '@/data/Providers'
 import { DataEntities, DataEntityTypes } from '@/data/DataEntities'
-import {
-  DEFAULT_DATA_ENTITIES
-} from '@/data/BlueprintWorkflow'
+import { DEFAULT_DATA_ENTITIES } from '@/data/BlueprintWorkflow'
 
 import ConnectionTabs from '@/components/blueprints/ConnectionTabs'
 import NoData from '@/components/NoData'
@@ -66,6 +45,7 @@ const DataTransformations = (props) => {
     configuredConnection,
     configuredProject,
     configuredBoard,
+    configurationKey,
     handleConnectionTabChange = () => {},
     prevStep = () => {},
     addBoardTransformation = () => {},
@@ -88,30 +68,38 @@ const DataTransformations = (props) => {
     useDropdownSelector = false,
     enableGoBack = true,
     elevation = Elevation.TWO,
-    cardStyle = {}
+    cardStyle = {},
   } = props
 
-  const boardsAndProjects = useMemo(() => [
-    ...(Array.isArray(boards[configuredConnection?.id]) ? boards[configuredConnection?.id] : []),
-    ...(Array.isArray(projects[configuredConnection?.id]) ? projects[configuredConnection?.id] : [])], [
-    projects,
-    boards,
-    configuredConnection?.id
-  ])
+  // lifted to dsm hook
+  // const entityIdKey = useMemo(() => provider?.id === Providers.JENKINS ? `C#${configuredConnection?.id}` : (configuredProject?.id || configuredBoard?.id), [provider?.id, configuredConnection?.id, configuredProject?.id, configuredBoard?.id])
 
-  const [entityList, setEntityList] = useState(boardsAndProjects?.map((e, eIdx) => ({
-    id: eIdx,
-    value: e?.value,
-    title: e?.title,
-    entity: e,
-    type: typeof e === 'object' ? 'board' : 'project'
-  })))
+  const boardsAndProjects = useMemo(
+    () => [
+      ...(Array.isArray(boards[configuredConnection?.id]) ? boards[configuredConnection?.id] : []),
+      ...(Array.isArray(projects[configuredConnection?.id]) ? projects[configuredConnection?.id] : []),
+    ],
+    [projects, boards, configuredConnection?.id]
+  )
+
+  const [entityList, setEntityList] = useState(
+    boardsAndProjects?.map((e, eIdx) => ({
+      id: eIdx,
+      value: e?.value,
+      title: e?.title,
+      entity: e,
+      type: e.variant,
+    }))
+  )
   const [activeEntity, setActiveEntity] = useState()
 
-  const transformationHasProperties = useCallback((item) => {
-    const storedTransform = transformations[item] || transformations[item?.id]
-    return storedTransform && Object.values(storedTransform).some(v => v && v.length > 0)
-  }, [transformations])
+  const transformationHasProperties = useCallback(
+    (item) => {
+      const storedTransform = transformations[item?.id]
+      return storedTransform && Object.values(storedTransform).some((v) => v && v.length > 0)
+    },
+    [transformations]
+  )
 
   useEffect(() => {
     console.log('>>> PROJECT/BOARD SELECT LIST DATA...', entityList)
@@ -132,14 +120,15 @@ const DataTransformations = (props) => {
     }
   }, [activeEntity, addBoardTransformation, addProjectTransformation, useDropdownSelector])
 
+  useEffect(() => {
+    console.log('>>> DATA TRANSFORMATIONS: DSM $configurationKey', configurationKey)
+  }, [configurationKey])
+
   return (
     <div className='workflow-step workflow-step-add-transformation' data-step={activeStep?.id}>
       {enableNoticeAlert && (
-        <p
-          className='alert neutral'
-        >
-          Set transformation rules for your selected data to view more complex
-          metrics in the dashboards.
+        <p className='alert neutral'>
+          Set transformation rules for your selected data to view more complex metrics in the dashboards.
           <br />
           <a
             href='#'
@@ -158,15 +147,8 @@ const DataTransformations = (props) => {
       {blueprintConnections.length > 0 && (
         <div style={{ display: 'flex' }}>
           {enableConnectionTabs && (
-            <div
-              className='connection-tab-selector'
-              style={{ minWidth: '200px' }}
-            >
-              <Card
-                className='workflow-card connection-tabs-card'
-                elevation={Elevation.TWO}
-                style={{ padding: '10px' }}
-              >
+            <div className='connection-tab-selector' style={{ minWidth: '200px' }}>
+              <Card className='workflow-card connection-tabs-card' elevation={Elevation.TWO} style={{ padding: '10px' }}>
                 <ConnectionTabs
                   connections={blueprintConnections}
                   onChange={handleConnectionTabChange}
@@ -175,112 +157,91 @@ const DataTransformations = (props) => {
               </Card>
             </div>
           )}
-          <div
-            className='connection-transformation'
-            style={{ marginLeft: '10px', width: '100%' }}
-          >
-            <Card
-              className='workflow-card workflow-panel-card'
-              elevation={elevation}
-              style={{ ...cardStyle }}
-            >
+          <div className='connection-transformation' style={{ marginLeft: '10px', width: '100%' }}>
+            <Card className='workflow-card workflow-panel-card' elevation={elevation} style={{ ...cardStyle }}>
               {configuredConnection && (
                 <>
                   <h3>
                     <span style={{ float: 'left', marginRight: '8px' }}>
-                      {ProviderIcons[configuredConnection.provider]
-                        ? (
-                            ProviderIcons[configuredConnection.provider](24, 24)
-                          )
-                        : (
-                          <></>
-                          )}
+                      {ProviderIcons[configuredConnection.provider] ? ProviderIcons[configuredConnection.provider](24, 24) : <></>}
                     </span>{' '}
                     {configuredConnection.title}
                   </h3>
                   <Divider className='section-divider' />
 
-                  {useDropdownSelector && entityList && [Providers.JIRA, Providers.GITHUB].includes(configuredConnection.provider) && (
-                    <div className='project-or-board-select' style={{ marginBottom: '20px' }}>
-                      <h4>{configuredConnection.provider === Providers.JIRA ? 'Board' : 'Project'}</h4>
-                      <Select
-                        disabled={configuredConnection.provider === Providers.JENKINS}
-                        popoverProps={{ usePortal: false }}
-                        className='selector-entity'
-                        id='selector-entity'
-                        inline={false}
-                        fill={true}
-                        items={entityList}
-                        activeItem={activeEntity}
-                        itemPredicate={(query, item) =>
-                          item?.title?.toString().toLowerCase().indexOf(query.toLowerCase()) >=
-                          0}
-                        itemRenderer={(item, { handleClick, modifiers }) => (
-                          <MenuItem
-                            active={modifiers.active}
-                            key={item.value}
-                            // label={item.value}
-                            onClick={handleClick}
-                            text={item.title}
-                          />
-                        )}
-                        noResults={
-                          <MenuItem disabled={true} text='No projects or boards.' />
-                        }
-                        onItemSelect={(item) => {
-                          setActiveEntity(item)
-                        }}
-                      >
-                        <Button
+                  {useDropdownSelector &&
+                    entityList &&
+                    [Providers.JIRA, Providers.GITHUB, Providers.GITLAB].includes(configuredConnection.provider) && (
+                      <div className='project-or-board-select' style={{ marginBottom: '20px' }}>
+                        <h4>{configuredConnection.provider === Providers.JIRA ? 'Board' : 'Project'}</h4>
+                        <Select
                           disabled={configuredConnection.provider === Providers.JENKINS}
-                          className='btn-select-entity'
-                          intent={Intent.PRIMARY}
-                          outlined
-                          text={
-                            activeEntity
-                              ? `${activeEntity?.title || '- None Available -'}`
-                              : '< Select Project / Board >'
-                          }
-                          rightIcon='caret-down'
-                          fill
-                          style={{
-                            maxWidth: '100%',
-                            display: 'flex',
-                            justifyContent: 'space-between',
+                          popoverProps={{ usePortal: false }}
+                          className='selector-entity'
+                          id='selector-entity'
+                          inline={false}
+                          fill={true}
+                          items={entityList}
+                          activeItem={activeEntity}
+                          itemPredicate={(query, item) => item?.title?.toString().toLowerCase().indexOf(query.toLowerCase()) >= 0}
+                          itemRenderer={(item, { handleClick, modifiers }) => (
+                            <MenuItem
+                              active={modifiers.active}
+                              key={item.value}
+                              // label={item.value}
+                              onClick={handleClick}
+                              text={item.title}
+                            />
+                          )}
+                          noResults={<MenuItem disabled={true} text='No projects or boards.' />}
+                          onItemSelect={(item) => {
+                            setActiveEntity(item)
                           }}
-                        />
-                      </Select>
-                    </div>
-                  )}
+                        >
+                          <Button
+                            disabled={configuredConnection.provider === Providers.JENKINS}
+                            className='btn-select-entity'
+                            intent={Intent.PRIMARY}
+                            outlined
+                            text={activeEntity ? `${activeEntity?.title || '- None Available -'}` : '< Select Project / Board >'}
+                            rightIcon='caret-down'
+                            fill
+                            style={{
+                              maxWidth: '100%',
+                              display: 'flex',
+                              justifyContent: 'space-between',
+                            }}
+                          />
+                        </Select>
+                      </div>
+                    )}
 
-                  {[Providers.GITLAB, Providers.GITHUB].includes(
-                    configuredConnection.provider
-                  ) && !useDropdownSelector && (!configuredProject) && (
-                    <>
-                      <StandardStackedList
-                        items={projects}
-                        transformations={transformations}
-                        className='selected-items-list selected-projects-list'
-                        connection={configuredConnection}
-                        activeItem={configuredProject}
-                        onAdd={addProjectTransformation}
-                        onChange={addProjectTransformation}
-                        isEditing={transformationHasProperties}
-                      />
-                      {projects[configuredConnection.id].length === 0 && (
-                        <NoData
-                          title='No Projects Selected'
-                          icon='git-branch'
-                          message='Please select specify at least one project.'
-                          onClick={prevStep}
+                  {[Providers.GITLAB, Providers.GITHUB].includes(configuredConnection.provider) &&
+                    !useDropdownSelector &&
+                    !configuredProject && (
+                      <>
+                        <StandardStackedList
+                          items={projects}
+                          transformations={transformations}
+                          className='selected-items-list selected-projects-list'
+                          connection={configuredConnection}
+                          activeItem={configuredProject}
+                          onAdd={addProjectTransformation}
+                          onChange={addProjectTransformation}
+                          isEditing={transformationHasProperties}
                         />
-                      )}
-                    </>
-                  )}
+                        {projects[configuredConnection.id].length === 0 && (
+                          <NoData
+                            title='No Projects Selected'
+                            icon='git-branch'
+                            message='Please select specify at least one project.'
+                            onClick={prevStep}
+                          />
+                        )}
+                      </>
+                    )}
 
-                  {[Providers.JIRA].includes(
-                    configuredConnection.provider
-                  ) && !useDropdownSelector && (!configuredBoard) && (
+                  {[Providers.JIRA].includes(configuredConnection.provider) && !useDropdownSelector && !configuredBoard && (
                     <>
                       <StandardStackedList
                         items={boards}
@@ -303,18 +264,24 @@ const DataTransformations = (props) => {
                     </>
                   )}
 
-                  {(configuredProject || configuredBoard) && (
+                  {(configuredProject ||
+                    configuredBoard ||
+                    (configuredConnection?.provider === Providers.JENKINS && configuredConnection)) && (
                     <div>
-                      {!useDropdownSelector && (
+                      {!useDropdownSelector && (configuredProject || configuredBoard) && (
                         <>
                           <h4>Project</h4>
                           <p style={{ color: '#292B3F' }}>{configuredProject?.title || configuredBoard?.title || '< select a project >'}</p>
                         </>
                       )}
-                      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
-                        <h4 style={{ margin: 0 }}>
-                          Data Transformation Rules
-                        </h4>
+                      <div
+                        style={{
+                          display: 'flex',
+                          justifyContent: 'space-between',
+                          alignItems: 'center',
+                        }}
+                      >
+                        <h4 style={{ margin: 0 }}>Data Transformation Rules</h4>
                         <div>
                           {/* @todo: reactivate clear all functionality */}
                           {/* <Button
@@ -331,17 +298,16 @@ const DataTransformations = (props) => {
                       </div>
 
                       {!dataEntities[configuredConnection.id] ||
-                        (dataEntities[configuredConnection.id]?.length ===
-                          0 && <p>(No Data Entities Selected)</p>)}
-                      {dataEntities[configuredConnection.id]?.find(
-                        (e) => DEFAULT_DATA_ENTITIES.some(dE => dE.value === e.value)
-                      ) && (
+                        (dataEntities[configuredConnection.id]?.length === 0 && <p>(No Data Entities Selected)</p>)}
+
+                      {dataEntities[configuredConnection.id]?.find((e) => DEFAULT_DATA_ENTITIES.some((dE) => dE.value === e.value)) && (
                         <ProviderTransformationSettings
-                          provider={integrationsData.find(i => i.id === configuredConnection?.provider)}
+                          provider={integrationsData.find((i) => i.id === configuredConnection?.provider)}
                           blueprint={blueprint}
                           connection={configuredConnection}
                           configuredProject={configuredProject}
                           configuredBoard={configuredBoard}
+                          entityIdKey={configurationKey}
                           issueTypes={issueTypes}
                           fields={fields}
                           boards={boards}
@@ -350,7 +316,6 @@ const DataTransformations = (props) => {
                           transformation={activeTransformation}
                           transformations={transformations}
                           onSettingsChange={setTransformationSettings}
-                          entity={DataEntityTypes.TICKET}
                           isSaving={isSaving}
                           isFetchingJIRA={isFetchingJIRA}
                           isSavingConnection={isSavingConnection}
@@ -374,25 +339,18 @@ const DataTransformations = (props) => {
                           disabled={[Providers.GITLAB].includes(configuredConnection?.provider)}
                           style={{ marginLeft: '5px' }}
                         /> */}
-                        {enableGoBack && (
-                          <Button
-                            text='Go Back'
-                            intent={Intent.PRIMARY}
-                            small
-                            outlined
-                            onClick={() => onSave()}
-                            // disabled={[Providers.GITLAB].includes(configuredConnection?.provider)}
-                            style={{ marginLeft: '5px' }}
-                            icon={(
-                              <Tooltip
-                                position={Position.TOP}
-                                intent={Intent.PRIMARY}
-                                content='Close Editor to Continue'
-                              >
-                                <Spinner size={12} />
-                              </Tooltip>
-                            )}
-                          />
+                        {enableGoBack && (configuredProject || configuredBoard) && (
+                          <Tooltip position={Position.TOP} intent={Intent.PRIMARY} content='Close Editor to Continue'>
+                            <Button
+                              text='Go Back'
+                              intent={Intent.PRIMARY}
+                              small
+                              outlined
+                              onClick={() => onSave()}
+                              // disabled={[Providers.GITLAB].includes(configuredConnection?.provider)}
+                              style={{ marginLeft: '5px' }}
+                            />
+                          </Tooltip>
                         )}
                       </div>
                     </div>
@@ -400,7 +358,9 @@ const DataTransformations = (props) => {
                 </>
               )}
 
-              {[Providers.JENKINS].includes(configuredConnection.provider) && (
+              {([Providers.TAPD].includes(configuredConnection.provider) ||
+                ([Providers.GITLAB].includes(configuredConnection.provider) &&
+                  dataEntities[configuredConnection.id].every((e) => e.value !== DataEntityTypes.DEVOPS))) && (
                 <>
                   <div className='bp3-non-ideal-state'>
                     <div className='bp3-non-ideal-state-visual'>
@@ -431,10 +391,7 @@ const DataTransformations = (props) => {
               </h4>
               <div>Please select at least one connection source.</div>
             </div>
-            <button
-              className='bp3-button bp4-intent-primary'
-              onClick={prevStep}
-            >
+            <button className='bp3-button bp4-intent-primary' onClick={prevStep}>
               Go Back
             </button>
           </div>
diff --git a/config-ui/src/components/blueprints/transformations/CICD/Deployment.jsx b/config-ui/src/components/blueprints/transformations/CICD/Deployment.jsx
new file mode 100644
index 00000000..74ea0f98
--- /dev/null
+++ b/config-ui/src/components/blueprints/transformations/CICD/Deployment.jsx
@@ -0,0 +1,251 @@
+/*
+ * 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, useEffect, useMemo, useCallback } from 'react'
+import {
+  Intent,
+  FormGroup,
+  RadioGroup,
+  InputGroup,
+  Radio,
+  Tag,
+  Icon,
+  Colors,
+  Tooltip
+} from '@blueprintjs/core'
+import { Providers, ProviderLabels } from '@/data/Providers'
+const Deployment = (props) => {
+  const {
+    connection,
+    provider,
+    transformation,
+    configuredProject,
+    configuredBoard,
+    entityIdKey,
+    entities = [],
+    isSaving = false,
+    onSettingsChange = () => {},
+  } = props
+
+  const [deployTag, setDeployTag] = useState(transformation?.productionPattern || '')
+  const [enableDeployTag, setEnableDeployTag] = useState([
+    transformation?.productionPattern,
+    // transformation?.stagingPattern,
+    // transformation?.testingPattern
+  ].some(t => t && t !== '') ? 1 : 0)
+
+  const clearDeploymentTags = useCallback((deploymentOption) => {
+    if (entityIdKey && deploymentOption === 0) {
+      onSettingsChange({ productionPattern: '' }, entityIdKey)
+      // onSettingsChange({ stagingPattern: '' }, entityIdKey)
+      // onSettingsChange({ testingPattern: '' }, entityIdKey)
+    }
+  }, [entityIdKey, onSettingsChange])
+
+  const handleDeploymentPreference = useCallback((deployOptionState) => {
+    setEnableDeployTag(deployOptionState)
+    switch (deployOptionState) {
+      case 0:
+        clearDeploymentTags(deployOptionState)
+        break
+      case 1:
+        break
+    }
+  }, [clearDeploymentTags])
+
+  // @todo: check w/ product team about using standard message and avoid customized hints
+  const getDeployTagHint = (providerId, providerName = 'Plugin') => {
+    let tagHint = ''
+    switch (providerId) {
+      case Providers.JENKINS:
+        // eslint-disable-next-line max-len
+        tagHint = `The ${providerName} build with a name that matches the given regEx is considered as a deployment. You can define your Deployments for three environments: Production, Staging and Testing.`
+        break
+      case Providers.GITHUB:
+      case Providers.GITLAB:
+      case 'default':
+        // eslint-disable-next-line max-len
+        tagHint = 'A CI job/build with a name that matches the given regEx is considered as an deployment. You can define your Deployments for three environments: Production, Staging and Testing.'
+        break
+    }
+    return tagHint
+  }
+
+  const getDeployOptionLabel = (providerId, providerName) => {
+    let label = ''
+    switch (providerId) {
+      case Providers.JENKINS:
+        // eslint-disable-next-line max-len
+        label = `Detect Deployment from Builds in ${providerName}`
+        break
+      case Providers.GITHUB:
+        label = `Detect Deployment from Jobs in ${providerName} Action`
+        break
+      case Providers.GITLAB:
+      case 'default':
+        // eslint-disable-next-line max-len
+        label = `Detect Deployment from Jobs in ${providerName} CI`
+        break
+    }
+    return label
+  }
+
+  useEffect(() => {
+    console.log('>>> CI/CD Deployment: TRANSFORMATION OBJECT!', transformation)
+    setEnableDeployTag(
+      [transformation?.productionPattern,
+      // transformation?.stagingPattern,
+      // transformation?.testingPattern
+      ].some(t => t && t !== '') ? 1 : 0
+    )
+  }, [transformation, transformation?.productionPattern])
+
+  return (
+    <>
+      <h5>CI/CD <Tag className='bp3-form-helper-text' minimal>RegExp</Tag></h5>
+      <p>Define deployment using one of the followng options</p>
+      <p style={{ color: '#292B3F' }}>
+        <strong>What is a deployment?</strong>{' '}
+        <Tag intent={Intent.PRIMARY} style={{ fontSize: '10px' }} minimal>
+          DORA
+        </Tag>
+      </p>
+
+      <RadioGroup
+        inline={false}
+        label={false}
+        name='deploy-tag'
+        onChange={(e) => handleDeploymentPreference(Number(e.target.value))}
+        selectedValue={enableDeployTag}
+        required
+      >
+        <Radio
+          label={getDeployOptionLabel(provider?.id, ProviderLabels[provider?.id?.toUpperCase()])}
+          value={1}
+        />
+        {enableDeployTag === 1 && (
+          <>
+            <div
+              className='bp3-form-helper-text'
+              style={{ display: 'block', textAlign: 'left', color: '#94959F', marginBottom: '5px' }}
+            >
+              {getDeployTagHint(provider?.id, ProviderLabels[provider?.id?.toUpperCase()])}
+            </div>
+            <div className='formContainer'>
+              <FormGroup
+                disabled={isSaving}
+                inline={true}
+                label={<label className='bp3-label' style={{ minWidth: '150px', marginRight: '10px' }}>Deployment (Production)</label>}
+                labelFor='deploy-tag-production'
+                className='formGroup'
+                contentClassName='formGroupContent'
+              >
+                <InputGroup
+                  id='deploy-tag-production'
+                  placeholder='(?i)deploy'
+                  value={transformation?.productionPattern}
+                  onChange={(e) => onSettingsChange({ productionPattern: e.target.value }, entityIdKey)}
+                  disabled={isSaving}
+                  className='input'
+                  maxLength={255}
+                  rightElement={
+                    enableDeployTag && (transformation?.productionPattern === '' || !transformation?.productionPattern)
+                      ? (
+                        <Tooltip intent={Intent.PRIMARY} content='Deployment Tag RegEx required'>
+                          <Icon icon='warning-sign' color={Colors.GRAY3} size={12} style={{ margin: '8px' }} />
+                        </Tooltip>
+                        )
+                      : null
+                  }
+                  required
+                />
+              </FormGroup>
+            </div>
+            {/* <div className='formContainer'>
+              <FormGroup
+                disabled={isSaving}
+                inline={true}
+                label={<label className='bp3-label' style={{ minWidth: '150px', marginRight: '10px' }}>Deployment (Staging)</label>}
+                labelFor='deploy-tag-staging'
+                className='formGroup'
+                contentClassName='formGroupContent'
+              >
+                <InputGroup
+                  id='deploy-tag-staging'
+                  placeholder='(?i)stag'
+                  value={transformation?.stagingPattern}
+                  onChange={(e) => onSettingsChange({ stagingPattern: e.target.value }, entityIdKey)}
+                  disabled={isSaving}
+                  className='input'
+                  maxLength={255}
+                  rightElement={
+                    enableDeployTag && (transformation?.stagingPattern === '' || !transformation?.stagingPattern)
+                      ? (
+                        <Tooltip intent={Intent.PRIMARY} content='Deployment Tag RegEx required'>
+                          <Icon icon='warning-sign' color={Colors.GRAY3} size={12} style={{ margin: '8px' }} />
+                        </Tooltip>
+                        )
+                      : null
+                  }
+                  required
+                />
+              </FormGroup>
+            </div>
+            <div className='formContainer'>
+              <FormGroup
+                disabled={isSaving}
+                inline={true}
+                label={<label className='bp3-label' style={{ minWidth: '150px', marginRight: '10px' }}>Deployment (Testing)</label>}
+                labelFor='deploy-tag-testing'
+                className='formGroup'
+                contentClassName='formGroupContent'
+              >
+                <InputGroup
+                  id='deploy-tag-testing'
+                  placeholder='(?i)test'
+                  value={transformation?.testingPattern}
+                  onChange={(e) => onSettingsChange({ testingPattern: e.target.value }, entityIdKey)}
+                  disabled={isSaving}
+                  className='input'
+                  maxLength={255}
+                  rightElement={
+                    enableDeployTag && (transformation?.testingPattern === '' || !transformation?.testingPattern)
+                      ? (
+                        <Tooltip intent={Intent.PRIMARY} content='Deployment Tag RegEx required'>
+                          <Icon icon='warning-sign' color={Colors.GRAY3} size={12} style={{ margin: '8px' }} />
+                        </Tooltip>
+                        )
+                      : null
+                  }
+                  required
+                />
+              </FormGroup>
+            </div> */}
+          </>
+        )}
+        <Radio
+          label={`Not using ${
+            ProviderLabels[provider?.id.toUpperCase()]
+          } Builds as Deployments`}
+          value={0}
+        />
+      </RadioGroup>
+    </>
+  )
+}
+
+export default Deployment
diff --git a/config-ui/.prettierrc.js b/config-ui/src/data/DataScopes.js
similarity index 86%
copy from config-ui/.prettierrc.js
copy to config-ui/src/data/DataScopes.js
index fb4c19b2..0aa84b66 100644
--- a/config-ui/.prettierrc.js
+++ b/config-ui/src/data/DataScopes.js
@@ -16,10 +16,11 @@
  *
  */
 
-module.exports = {
-  printWidth: 140,
-  singleQuote: true,
-  jsxSingleQuote: true,
-  trailingComma: 'es5',
-  semi: false,
+const DataScopeModes = {
+  CREATE: 'create',
+  EDIT: 'edit'
+}
+
+export {
+  DataScopeModes
 }
diff --git a/config-ui/src/hooks/useBlueprintValidation.jsx b/config-ui/src/hooks/useBlueprintValidation.jsx
index ef66ff6b..b15c5de9 100644
--- a/config-ui/src/hooks/useBlueprintValidation.jsx
+++ b/config-ui/src/hooks/useBlueprintValidation.jsx
@@ -93,6 +93,10 @@ function useBlueprintValidation ({
     return set.length > 0
   }, [])
 
+  const validateUniqueObjectSet = useCallback((set = []) => {
+    return [...new Set(set.map(o => JSON.stringify(o)))].length === set.length
+  }, [])
+
   const validateBlueprintName = useCallback((name = '') => {
     return name && name.length >= 2
   }, [])
@@ -145,12 +149,21 @@ function useBlueprintValidation ({
           if (activeProvider?.id === Providers.GITHUB && !validateRepositoryName(projects[activeConnection?.id])) {
             errs.push('Projects: Only Git Repository Names are supported (username/repo).')
           }
+          if (activeProvider?.id === Providers.GITHUB && !validateRepositoryName(projects[activeConnection?.id])) {
+            errs.push('Projects: Only Git Repository Names are supported (username/repo).')
+          }
+          if (activeProvider?.id === Providers.GITHUB && !validateUniqueObjectSet(projects[activeConnection?.id])) {
+            errs.push('Projects: Duplicate project detected.')
+          }
           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])) {
+            errs.push('Projects: Duplicate project detected.')
+          }
 
           connections.forEach(c => {
             if (c.provider === Providers.JIRA && boards[c?.id]?.length === 0) {
@@ -191,7 +204,8 @@ function useBlueprintValidation ({
     activeConnection,
     isValidCronExpression,
     validateNumericSet,
-    validateRepositoryName
+    validateRepositoryName,
+    validateUniqueObjectSet
   ])
 
   const fieldHasError = useCallback((fieldId) => {
diff --git a/config-ui/src/hooks/useConnectionManager.jsx b/config-ui/src/hooks/useConnectionManager.jsx
index 210087f2..b00cdd57 100644
--- a/config-ui/src/hooks/useConnectionManager.jsx
+++ b/config-ui/src/hooks/useConnectionManager.jsx
@@ -334,14 +334,16 @@ function useConnectionManager (
             ),
           ])
           const builtConnections = aC
-            .map((providerResponse) => [].concat(providerResponse.data || []).map(c => new Connection({
-              ...c,
-              connectionId: c.id,
-              plugin: providerResponse.config?.url?.split('/')[3],
-              provider: providerResponse.config?.url?.split('/')[3],
-              // @note: realtime connection status will be later injected by hook callees (non-blocking)
-              status: ConnectionStatus.ONLINE
-            })))
+            .map((providerResponse) => []
+              .concat(Array.isArray(providerResponse.data) ? providerResponse.data : [])
+              .map(c => new Connection({
+                ...c,
+                connectionId: c.id,
+                plugin: providerResponse.config?.url?.split('/')[3],
+                provider: providerResponse.config?.url?.split('/')[3],
+                // @note: realtime connection status will be later injected by hook callees (non-blocking)
+                status: ConnectionStatus.ONLINE
+              })))
           setAllProviderConnections(builtConnections.flat())
           console.log(
             '>> ALL SOURCE CONNECTIONS: FETCHING ALL CONNECTION FROM ALL DATA SOURCES'
@@ -352,6 +354,7 @@ function useConnectionManager (
             `${DEVLAKE_ENDPOINT}/plugins/${provider?.id}/connections`
           )
           console.log('>> RAW ALL CONNECTIONS DATA FROM API...', c?.data)
+          handleOfflineMode(c?.status, c)
           const providerConnections = []
             .concat(Array.isArray(c?.data) ? c?.data : [])
             .map((conn, idx) =>
diff --git a/config-ui/src/hooks/useDataScopesManager.jsx b/config-ui/src/hooks/useDataScopesManager.jsx
index 41f71520..6faa907b 100644
--- a/config-ui/src/hooks/useDataScopesManager.jsx
+++ b/config-ui/src/hooks/useDataScopesManager.jsx
@@ -27,12 +27,16 @@ import JiraBoard from '@/models/JiraBoard'
 import GitHubProject from '@/models/GithubProject'
 import GitlabProject from '@/models/GitlabProject'
 import { Providers, ProviderLabels, ProviderIcons } from '@/data/Providers'
+import { DataScopeModes } from '@/data/DataScopes'
 
-function useDataScopesManager ({ provider, blueprint, /* connection, */ settings = {}, setSettings = () => {} }) {
+function useDataScopesManager ({ mode = DataScopeModes.CREATE, provider, blueprint, /* connection, */ settings = {}, setSettings = () => {} }) {
   const [connections, setConnections] = useState([])
+  const [newConnections, setNewConnections] = useState([])
 
   const [scopeConnection, setScopeConnection] = useState()
-  const connection = useMemo(() => scopeConnection, [scopeConnection])
+  const [configuredConnection, setConfiguredConnection] = useState()
+  const connection = useMemo(() => mode === DataScopeModes.EDIT ? scopeConnection : configuredConnection, [scopeConnection, configuredConnection, mode])
+  // const connection = useMemo(() => scopeConnection, [scopeConnection])
 
   // const [blueprint, setBlueprint] = useState(NullBlueprint)
   const [boards, setBoards] = useState({})
@@ -43,21 +47,42 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
 
   const [configuredProject, setConfiguredProject] = useState(null)
   const [configuredBoard, setConfiguredBoard] = useState(null)
+  const configurationKey = useMemo(() => {
+    let key = `C#${connection?.id}`
+    switch (connection?.providerId) {
+      case Providers.GITHUB:
+      case Providers.GITLAB:
+        key = configuredProject?.id
+        break
+      case Providers.JIRA:
+        key = configuredBoard?.id
+        break
+      case Providers.JENKINS:
+      case 'default':
+        key = `C#${connection?.id}`
+        break
+    }
+    console.log('>>> DSM: Active Configuration Key ===', key)
+    return key
+  }, [connection?.providerId, connection?.id, configuredProject?.id, configuredBoard?.id])
 
   const activeProject = useMemo(() => configuredProject, [configuredProject])
   const activeBoard = useMemo(() => configuredBoard, [configuredBoard])
 
-  const selectedProjects = useMemo(() => projects[connection?.id] || [], [projects, connection?.id])
+  const selectedProjects = useMemo(() => projects[connection?.id]?.map(
+    (p) => p && p?.id
+  ), [projects, connection?.id])
   const selectedBoards = useMemo(() => boards[connection?.id]?.map(
     (b) => b && b?.id
   ), [boards, connection?.id])
 
-  const storedProjectTransformation = useMemo(() => connection?.transformations[connection?.projects?.findIndex(p => p === configuredProject)], [connection, configuredProject])
-  const storedBoardTransformation = useMemo(() => connection?.transformations[connection?.boardIds?.findIndex(b => b === configuredBoard?.id)], [connection, configuredBoard?.id])
+  const storedProjectTransformation = useMemo(() => connection?.transformations && connection?.transformations[connection?.projects?.findIndex(p => p === configuredProject?.id)], [connection, configuredProject?.id])
+  const storedBoardTransformation = useMemo(() => connection?.transformations && connection?.transformations[connection?.boardIds?.findIndex(b => b === configuredBoard?.id)], [connection, configuredBoard?.id])
 
-  const activeProjectTransformation = useMemo(() => transformations[activeProject], [transformations, activeProject])
+  const activeProjectTransformation = useMemo(() => transformations[activeProject?.id], [transformations, activeProject?.id])
   const activeBoardTransformation = useMemo(() => transformations[activeBoard?.id], [transformations, activeBoard?.id])
-  const activeTransformation = useMemo(() => transformations[connection?.providerId === Providers.JIRA ? configuredBoard?.id : configuredProject], [transformations, configuredProject, configuredBoard?.id, connection?.providerId])
+  const activeTransformation = useMemo(() => transformations[configurationKey], [configurationKey, transformations])
+  // const activeTransformation = useMemo(() => transformations[connection?.providerId === Providers.JIRA ? configuredBoard?.id : configuredProject?.id], [transformations, configuredProject?.id, configuredBoard?.id, connection?.providerId])
 
   const getDefaultTransformations = useCallback((providerId) => {
     let transforms = {}
@@ -74,6 +99,9 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           issueTypeBug: '',
           issueTypeIncident: '',
           refdiff: null,
+          productionPattern: '',
+          // stagingPattern: '',
+          // testingPattern: ''
         }
         break
       case Providers.JIRA:
@@ -85,16 +113,36 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           bugTags: [],
           incidentTags: [],
           requirementTags: [],
+          // @todo: verify if jira utilizes deploy tag(s)?
+          productionPattern: '',
+          // stagingPattern: '',
+          // testingPattern: ''
         }
         break
       case Providers.JENKINS:
-        // No Transform Settings...
+        transforms = {
+          productionPattern: '',
+          // stagingPattern: '',
+          // testingPattern: ''
+        }
         break
       case Providers.GITLAB:
-        // No Transform Settings...
+        transforms = {
+          productionPattern: '',
+          // stagingPattern: '',
+          // testingPattern: ''
+        }
         break
       case Providers.TAPD:
-        // No Transform Settings...
+        // @todo: complete tapd transforms #2673
+        transforms = {
+          issueTypeRequirement: '',
+          issueTypeBug: '',
+          issueTypeIncident: '',
+          productionPattern: '',
+          // stagingPattern: '',
+          // testingPattern: ''
+        }
         break
     }
     console.log('>>>>> DATA SCOPES MANAGER: Getting Default Transformation Values for PROVIDER Type ', providerId, transforms)
@@ -104,7 +152,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
   const initializeTransformations = useCallback((pV, cV, iDx) => ({
     ...pV,
     [cV]: new TransformationSettings(getDefaultTransformations(connection?.providerId, iDx)),
-  }), [connection, getDefaultTransformations])
+  }), [connection?.providerId, getDefaultTransformations])
 
   // @todo: generate scopes dynamically from $integrationsData (in future Integrations Hook [plugin registry])
   const createProviderScopes = useCallback(
@@ -147,7 +195,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
               projectId: Number(p.value),
               title: p.title
             },
-            transformation: {},
+            transformation: { ...transformations[p?.id] },
           }))
           break
         case Providers.JENKINS:
@@ -155,7 +203,8 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
             ...newScope,
             // options: {
             // },
-            // transformation: {},
+            // NOTE: Jenkins has no concept of projects/boards. Transformations Key'ed by Conn *INDEX* ID!
+            transformation: { ...transformations[`C#${connection?.id}`] },
           }
           break
         case Providers.GITHUB:
@@ -165,7 +214,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
               owner: p.value.split('/')[0],
               repo: p.value.split('/')[1],
             },
-            transformation: { ...transformations[p] },
+            transformation: { ...transformations[p?.id] },
           }))
           break
         case Providers.TAPD:
@@ -230,23 +279,25 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
         configuredEntity,
         settings
       )
-      setTransformations((existingTransformations) => ({
+      setTransformations((existingTransformations) => configuredEntity ? ({
         ...existingTransformations,
         [configuredEntity]: new TransformationSettings({
           ...existingTransformations[configuredEntity],
           ...settings,
-        }),
-      }))
+        })
+      }) : existingTransformations)
     },
     [setTransformations]
   )
 
   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}`,
-      value: `${s.options.owner}/${s.options?.repo}`,
-      title: `${s.options.owner}/${s.options?.repo}`,
+      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}`,
     }))
     : [], [])
 
@@ -318,7 +369,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
     switch (providerId) {
       case Providers.GITHUB:
       case Providers.GITLAB:
-        entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name !== 'ci-cd')
+        entities = DEFAULT_DATA_ENTITIES
         break
       case Providers.JIRA:
         entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'issue-tracking' || d.name === 'cross-domain')
@@ -366,7 +417,8 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
           : '-'
       ),
       scope: c.scope,
-      editable: ![Providers.JENKINS].includes(c.plugin),
+      // editable: ![Providers.JENKINS].includes(c.plugin),
+      editable: true,
       advancedEditable: false,
       isMultiStage: false,
       isSingleStage: true,
@@ -431,7 +483,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
       case Providers.GITLAB:
         setProjects(p => ({ ...p, [connection?.id]: connection?.projects || [] }))
         setEntities(e => ({ ...e, [connection?.id]: connection?.entityList || [] }))
-        connection?.projects.forEach((p, pIdx) => setTransformationSettings(connection.transformations[pIdx], p))
+        connection?.projects.forEach((p, pIdx) => setTransformationSettings(connection.transformations[pIdx], p?.id))
         break
       case Providers.JIRA:
         // fetchBoards()
@@ -443,6 +495,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
         break
       case Providers.JENKINS:
         setEntities(e => ({ ...e, [connection?.id]: connection?.entityList || [] }))
+        setTransformationSettings(connection.transformations[0], `C#${connection?.id}`)
         break
     }
   }, [
@@ -479,7 +532,22 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
   }, [provider])
 
   useEffect(() => {
-    console.log('>>>>> DATA SCOPES MANAGER: BOARDS...', boards)
+    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]
     if (Array.isArray(boardTransformations) && boardTransformations?.length > 0) {
       setTransformations((cT) => ({
@@ -491,7 +559,7 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
   }, [boards, connection?.id, initializeTransformations])
 
   useEffect(() => {
-    console.log('>>>>> DATA SCOPES MANAGER: PROJECTS...', projects)
+    console.log('>>>>> DATA SCOPES MANAGER: INITIALIZE PROJECTS...', projects)
     const projectTransformations = projects[connection?.id]
     if (Array.isArray(projectTransformations)) {
       setTransformations((cT) => ({
@@ -506,6 +574,10 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
     console.log('>>>>> DATA SCOPES MANAGER: DATA ENTITIES...', entities)
   }, [entities])
 
+  useEffect(() => {
+    console.log('>>>>> DATA SCOPES MANAGER: DATA ENTITIES...', entities)
+  }, [entities])
+
   useEffect(() => {
     console.log('>>>>> DATA SCOPES MANAGER: TRANSFORMATIONS...', transformations)
   }, [transformations])
@@ -526,15 +598,22 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
     console.log('>>>>> DATA SCOPES MANAGER: ACTIVE BOARD TRANSFORMATION RULES...', activeBoardTransformation)
   }, [activeBoardTransformation])
 
+  useEffect(() => {
+    console.log('>>>>> DATA SCOPES MANAGER: MEMOIZED ACTIVE CONNECTION...', connection)
+  }, [connection])
+
   return {
     connections,
+    newConnections,
     // blueprint,
     boards,
     projects,
     entities,
     transformations,
+    configuredConnection,
     configuredBoard,
     configuredProject,
+    configurationKey,
     storedProjectTransformation,
     storedBoardTransformation,
     activeBoardTransformation,
@@ -543,8 +622,10 @@ function useDataScopesManager ({ provider, blueprint, /* connection, */ settings
     scopeConnection,
     enabledProviders,
     // setActiveTransformation,
+    setNewConnections,
     setConnections,
     setScopeConnection,
+    setConfiguredConnection,
     setConfiguredBoard,
     setConfiguredProject,
     // setBlueprint,
diff --git a/config-ui/src/hooks/useNetworkOfflineMode.jsx b/config-ui/src/hooks/useNetworkOfflineMode.jsx
index 3cc96cbb..10cf14d9 100644
--- a/config-ui/src/hooks/useNetworkOfflineMode.jsx
+++ b/config-ui/src/hooks/useNetworkOfflineMode.jsx
@@ -18,7 +18,7 @@
 import { useCallback, useState, useEffect } from 'react'
 import { useHistory } from 'react-router-dom'
 
-function useNetworkOfflineMode (offlineStatuses = [502, 504], offlineRoute = '/offline') {
+function useNetworkOfflineMode (offlineStatuses = [502, 504, 404], offlineRoute = '/offline') {
   const history = useHistory()
   const [status, setStatus] = useState()
   const [response, setResponse] = useState()
diff --git a/config-ui/src/models/Connection.js b/config-ui/src/models/Connection.js
index 08e7a126..2d787d37 100644
--- a/config-ui/src/models/Connection.js
+++ b/config-ui/src/models/Connection.js
@@ -34,6 +34,7 @@
  * @property {plain|token?} authentication
  * @property {string|object?} plugin
  * @property {string|object?} provider
+ * @property {string?} providerId
  * @property {<Array<DataEntity>>} entities
  * @property {boolean} multiConnection
  * @property {number|string?} status
@@ -61,6 +62,7 @@ class Connection {
     this.plugin = data?.plugin || null
     // @todo: will be replaced out by $this.plugin
     this.provider = data?.provider || null
+    this.providerId = data?.providerId || null
     this.entities = data?.entities || []
     this.multiConnection = data?.multiConnection || true
     this.status = data?.status || null
diff --git a/config-ui/src/pages/blueprints/blueprint-settings.jsx b/config-ui/src/pages/blueprints/blueprint-settings.jsx
index b946e06f..646061f6 100644
--- a/config-ui/src/pages/blueprints/blueprint-settings.jsx
+++ b/config-ui/src/pages/blueprints/blueprint-settings.jsx
@@ -60,6 +60,7 @@ import CodeInspector from '@/components/pipelines/CodeInspector'
 
 // import { DataEntities, DataEntityTypes } from '@/data/DataEntities'
 import { DEFAULT_DATA_ENTITIES } from '@/data/BlueprintWorkflow'
+import { DataScopeModes } from '@/data/DataScopes'
 
 import useBlueprintManager from '@/hooks/useBlueprintManager'
 import useConnectionManager from '@/hooks/useConnectionManager'
@@ -96,7 +97,7 @@ const BlueprintSettings = (props) => {
   // @disabled Provided By Data Scopes Manager
   // const [connections, setConnections] = useState([])
   const [blueprintConnections, setBlueprintConnections] = useState([])
-  const [configuredConnection, setConfiguredConnection] = useState()
+  // const [configuredConnection, setConfiguredConnection] = useState()
 
   // @todo: relocate or discard
   const [newConnectionScopes, setNewConnectionScopes] = useState({})
@@ -165,9 +166,12 @@ const BlueprintSettings = (props) => {
     activeBoardTransformation,
     activeProjectTransformation,
     activeTransformation,
+    configuredConnection,
     configuredBoard,
     configuredProject,
+    configurationKey,
     enabledProviders,
+    setConfiguredConnection,
     setConfiguredBoard,
     setConfiguredProject,
     setBoards,
@@ -186,6 +190,7 @@ const BlueprintSettings = (props) => {
     getJiraMappedBoards,
     getDefaultEntities
   } = useDataScopesManager({
+    mode: DataScopeModes.EDIT,
     blueprint: activeBlueprint,
     provider: activeProvider,
     // connection: scopeConnection,
@@ -445,7 +450,8 @@ const BlueprintSettings = (props) => {
     // activeProvider,
     connectionsList,
     connections,
-    setScopeConnection
+    setScopeConnection,
+    setConfiguredConnection
   ])
 
   const validateActiveSetting = useCallback(() => {
@@ -518,6 +524,7 @@ const BlueprintSettings = (props) => {
     activeBlueprint?.mode
   ])
 
+  // @note: lifted higher to dsm hook
   // const getDefaultEntities = useCallback((providerId) => {
   //   let entities = []
   //   switch (providerId) {
@@ -1288,6 +1295,7 @@ const BlueprintSettings = (props) => {
         configuredConnection={configuredConnection}
         configuredProject={configuredProject}
         configuredBoard={configuredBoard}
+        configurationKey={configurationKey}
         scopeConnection={scopeConnection}
         activeTransformation={activeTransformation}
         addProjectTransformation={addProjectTransformation}
diff --git a/config-ui/src/pages/blueprints/create-blueprint.jsx b/config-ui/src/pages/blueprints/create-blueprint.jsx
index 8d14e57f..965e1b17 100644
--- a/config-ui/src/pages/blueprints/create-blueprint.jsx
+++ b/config-ui/src/pages/blueprints/create-blueprint.jsx
@@ -111,8 +111,9 @@ const CreateBlueprint = (props) => {
   ])
   const [boardsList, setBoardsList] = useState([])
 
-  const [blueprintConnections, setBlueprintConnections] = useState([])
-  const [configuredConnection, setConfiguredConnection] = useState()
+  // @note: lifted to dsm hook
+  // const [blueprintConnections, setBlueprintConnections] = useState([])
+  // const [configuredConnection, setConfiguredConnection] = useState()
 
   const [activeConnectionTab, setActiveConnectionTab] = useState()
 
@@ -173,20 +174,31 @@ const CreateBlueprint = (props) => {
   } = useBlueprintManager()
 
   const {
+    newConnections: blueprintConnections,
     boards,
     projects,
     entities: dataEntities,
     transformations,
+    activeTransformation,
+    setNewConnections: setBlueprintConnections,
+    setConfiguredConnection,
+    setConfiguredBoard,
+    setConfiguredProject,
     setBoards,
     setProjects,
     setEntities: setDataEntities,
     setTransformations,
     setTransformationSettings,
+    configuredConnection,
+    configuredProject,
+    configuredBoard,
+    configurationKey,
     createProviderScopes,
     createProviderConnections,
     getDefaultTransformations,
+    getDefaultEntities,
     initializeTransformations
-  } = useDataScopesManager({ connection: configuredConnection, settings: blueprintSettings })
+  } = useDataScopesManager({ settings: blueprintSettings })
 
   const {
     pipelineName,
@@ -358,16 +370,16 @@ const CreateBlueprint = (props) => {
     password,
   })
 
-  const [configuredProject, setConfiguredProject] = useState(
-    // projects.length > 0 ? projects[0] : null
-    null
-  )
-  const [configuredBoard, setConfiguredBoard] = useState(
-    // boards.length > 0 ? boards[0] : null
-    null
-  )
+  // const [configuredProject, setConfiguredProject] = useState(
+  //   // projects.length > 0 ? projects[0] : null
+  //   null
+  // )
+  // const [configuredBoard, setConfiguredBoard] = useState(
+  //   // boards.length > 0 ? boards[0] : null
+  //   null
+  // )
 
-  const activeTransformation = useMemo(() => transformations[configuredProject?.id || configuredBoard?.id], [transformations, configuredProject?.id, 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) => { }, [])
@@ -386,7 +398,7 @@ const CreateBlueprint = (props) => {
     )
     setConfiguredProject(null)
     setConfiguredBoard(null)
-  }, [blueprintSteps])
+  }, [blueprintSteps, setConfiguredBoard, setConfiguredProject])
 
   const testSelectedConnections = useCallback((connections, savedConnection = {}, callback = () => {}) => {
     const runTest = async () => {
@@ -431,7 +443,7 @@ const CreateBlueprint = (props) => {
       )
       setConfiguredConnection(selectedConnection)
     },
-    [blueprintConnections, setProvider]
+    [blueprintConnections, setProvider, setConfiguredConnection]
   )
 
   const handleConnectionDialogOpen = useCallback(() => {
@@ -484,7 +496,7 @@ const CreateBlueprint = (props) => {
     }))
     setConfiguredProject(null)
     setConfiguredBoard(null)
-  }, [setTransformations, configuredProject, configuredBoard])
+  }, [setTransformations, setConfiguredBoard, setConfiguredProject, configuredProject, configuredBoard])
 
   const handleBlueprintSave = useCallback(() => {
     console.log('>>> SAVING BLUEPRINT!!')
@@ -570,7 +582,7 @@ const CreateBlueprint = (props) => {
     setConfiguredBoard(null)
     ToastNotification.clear()
     ToastNotification.show({ message: 'Transformation Rules Added.', intent: Intent.SUCCESS, icon: 'small-tick' })
-  }, [])
+  }, [setConfiguredProject, setConfiguredBoard])
 
   const handleAdvancedMode = (enableAdvanced = true) => {
     setAdvancedMode(enableAdvanced)
@@ -698,25 +710,25 @@ const CreateBlueprint = (props) => {
         integrationsData.find((p) => p.id === someConnection.provider)
       )
     }
-    const getDefaultEntities = (providerId) => {
-      let entities = []
-      switch (providerId) {
-        case Providers.GITHUB:
-        case Providers.GITLAB:
-          entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name !== 'ci-cd')
-          break
-        case Providers.JIRA:
-          entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'issue-tracking' || d.name === 'cross-domain')
-          break
-        case Providers.JENKINS:
-          entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
-          break
-        case Providers.TAPD:
-          entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
-          break
-      }
-      return entities
-    }
+    // const getDefaultEntities = (providerId) => {
+    //   let entities = []
+    //   switch (providerId) {
+    //     case Providers.GITHUB:
+    //     case Providers.GITLAB:
+    //       entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name !== 'ci-cd')
+    //       break
+    //     case Providers.JIRA:
+    //       entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'issue-tracking' || d.name === 'cross-domain')
+    //       break
+    //     case Providers.JENKINS:
+    //       entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
+    //       break
+    //     case Providers.TAPD:
+    //       entities = DEFAULT_DATA_ENTITIES.filter((d) => d.name === 'ci-cd')
+    //       break
+    //   }
+    //   return entities
+    // }
     const initializeEntities = (pV, cV) => ({
       ...pV,
       [cV.id]: !pV[cV.id] ? getDefaultEntities(cV?.provider) : [],
@@ -739,6 +751,8 @@ const CreateBlueprint = (props) => {
     testSelectedConnections(blueprintConnections)
   }, [
     blueprintConnections,
+    getDefaultEntities,
+    setConfiguredConnection,
     setProvider,
     setBoards,
     setDataEntities,
@@ -774,7 +788,7 @@ const CreateBlueprint = (props) => {
           break
       }
     }
-  }, [configuredConnection, setActiveConnectionTab])
+  }, [configuredConnection, setActiveConnectionTab, setConfiguredBoard, setConfiguredProject])
 
   useEffect(() => {
     console.log('>> DATA ENTITIES', dataEntities)
@@ -960,6 +974,7 @@ const CreateBlueprint = (props) => {
       statusResponse: dataConnections.find(dC => dC.id === c.id && dC.provider === c.provider),
       status: dataConnections.find(dC => dC.id === c.id && dC.provider === c.provider)?.status
     })))
+    // @todo: re-enable next disable on offline connection! (disabled to allow GitHub Test)
     setCanAdvanceNext(dataConnections.every(dC => dC.status === 200))
   }, [dataConnections, setConnectionsList])
 
@@ -1085,6 +1100,7 @@ const CreateBlueprint = (props) => {
                       configuredConnection={configuredConnection}
                       configuredProject={configuredProject}
                       configuredBoard={configuredBoard}
+                      configurationKey={configurationKey}
                       handleConnectionTabChange={handleConnectionTabChange}
                       prevStep={prevStep}
                       addBoardTransformation={addBoardTransformation}
diff --git a/config-ui/src/pages/blueprints/index.jsx b/config-ui/src/pages/blueprints/index.jsx
index 288450ba..0035ace3 100644
--- a/config-ui/src/pages/blueprints/index.jsx
+++ b/config-ui/src/pages/blueprints/index.jsx
@@ -136,7 +136,7 @@ const Blueprints = (props) => {
     setExpandDetails(opened => blueprint.id === activeBlueprint?.id && opened ? false : !opened)
     fetchAllPipelines()
     setActiveBlueprint(blueprint)
-  }, [fetchAllPipelines, setExpandDetails, setActiveBlueprint])
+  }, [fetchAllPipelines, setExpandDetails, setActiveBlueprint, activeBlueprint?.id])
 
   const configureBlueprint = useCallback((blueprint) => {
     history.push(`/blueprints/detail/${blueprint.id}`)
@@ -152,7 +152,7 @@ const Blueprints = (props) => {
 
   const isActiveBlueprint = useCallback((bId) => {
     return activeBlueprint?.id === bId
-  }, [])
+  }, [activeBlueprint?.id])
 
   const isStandardCronPreset = useCallback((cronConfig) => {
     return cronPresets.some(p => p.cronConfig === cronConfig)
@@ -204,7 +204,7 @@ const Blueprints = (props) => {
       setBlueprintDialogIsOpen(false)
       fetchAllBlueprints()
     }
-  }, [saveComplete])
+  }, [saveComplete, fetchAllBlueprints])
 
   useEffect(() => {
     if (deleteComplete.status === 200) {
@@ -267,7 +267,7 @@ const Blueprints = (props) => {
       }
     })
     setFilterParams(activeFilterStatus)
-  }, [activeFilterStatus, setFilterParams, getCronPreset])
+  }, [activeFilterStatus, setFilterParams, getCronPreset, setFilterFunc])
 
   // useEffect(() => {
   //   if (Array.isArray(tasks)) {
@@ -282,7 +282,7 @@ const Blueprints = (props) => {
 
   useEffect(() => {
     setPaginatorData(blueprints)
-  }, [blueprints])
+  }, [blueprints, setPaginatorData])
 
   return (
     <>
diff --git a/config-ui/src/pages/configure/connections/AddConnection.jsx b/config-ui/src/pages/configure/connections/AddConnection.jsx
index 09e5351e..40e38461 100644
--- a/config-ui/src/pages/configure/connections/AddConnection.jsx
+++ b/config-ui/src/pages/configure/connections/AddConnection.jsx
@@ -126,7 +126,7 @@ export default function AddConnection () {
   useEffect(() => {
     console.log('>>>> DETECTED PROVIDER = ', providerId)
     setActiveProvider(integrationsData.find(p => p.id === providerId))
-  }, [])
+  }, [providerId])
 
   return (
     <>
diff --git a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
index 7bcab51b..278acc01 100644
--- a/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
+++ b/config-ui/src/pages/configure/connections/ConfigureConnection.jsx
@@ -144,7 +144,7 @@ export default function ConfigureConnection () {
     } else {
       console.log('NO PARAMS!')
     }
-  }, [connectionId, providerId, integrationsData])
+  }, [connectionId, providerId])
 
   useEffect(() => {
 
diff --git a/config-ui/src/pages/configure/integration/manage.jsx b/config-ui/src/pages/configure/integration/manage.jsx
index e26a1a37..7c7c1a81 100644
--- a/config-ui/src/pages/configure/integration/manage.jsx
+++ b/config-ui/src/pages/configure/integration/manage.jsx
@@ -130,7 +130,7 @@ export default function ManageIntegration () {
     console.log('>> ACTIVE PROVIDER = ', providerId)
     setIntegrations(integrations)
     setActiveProvider(integrations.find(p => p.id === providerId))
-  }, [])
+  }, [integrations, providerId])
 
   useEffect(() => {
     let flushTimeout
diff --git a/config-ui/src/pages/configure/settings/github.jsx b/config-ui/src/pages/configure/settings/github.jsx
index 0f53a5b2..9c79827b 100644
--- a/config-ui/src/pages/configure/settings/github.jsx
+++ b/config-ui/src/pages/configure/settings/github.jsx
@@ -29,16 +29,18 @@ import {
   Position
 } from '@blueprintjs/core'
 import { DataEntityTypes } from '@/data/DataEntities'
+import Deployment from '@/components/blueprints/transformations/CICD/Deployment'
 
 import '@/styles/integration.scss'
 import '@/styles/connections.scss'
 
 export default function GithubSettings (props) {
   const {
-    // eslint-disable-next-line no-unused-vars
+    provider,
     connection,
     entities = [],
     transformation = {},
+    entityIdKey,
     isSaving,
     isSavingConnection,
     onSettingsChange = () => {},
@@ -60,12 +62,12 @@ export default function GithubSettings (props) {
   }, [setEnableAdditionalCalculations, configuredProject, onSettingsChange])
 
   useEffect(() => {
-    console.log('>>>> TRANSFORMATION SETTINGS OBJECT....', transformation)
+    console.log('>>>> GITHUB: TRANSFORMATION SETTINGS OBJECT....', transformation)
     setEnableAdditionalCalculations(!!transformation?.refdiff)
   }, [transformation])
 
   useEffect(() => {
-    console.log('>>>> ENABLE GITHUB ADDITIONAL SETTINGS..?', enableAdditionalCalculations)
+    console.log('>>>> GITHUB: ENABLE GITHUB ADDITIONAL SETTINGS..?', enableAdditionalCalculations)
     if (enableAdditionalCalculations === 'disabled') {
       // onSettingsChange({gitextractorCalculation: ''}, configuredProject)
     }
@@ -206,6 +208,18 @@ export default function GithubSettings (props) {
         </>
       )}
 
+      {entities.some(e => e.value === DataEntityTypes.DEVOPS) && configuredProject && (
+        <Deployment
+          provider={provider}
+          entities={entities}
+          entityIdKey={entityIdKey}
+          transformation={transformation}
+          connection={connection}
+          onSettingsChange={onSettingsChange}
+          isSaving={isSaving || isSavingConnection}
+        />
+      )}
+
       {entities.some(e => e.value === DataEntityTypes.CODE_REVIEW) && (
         <><h5>Code Review{' '} <Tag className='bp3-form-helper-text' minimal>RegExp</Tag></h5>
           <p className=''>Map your pull requests labels with each category to view corresponding metrics in the dashboard.</p>
@@ -296,6 +310,7 @@ export default function GithubSettings (props) {
               <TextArea
                 id='github-pr-body'
                 className='textarea'
+                value={transformation?.prBodyClosePattern}
                 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?.id)}
                 disabled={isSaving || isSavingConnection}
@@ -303,8 +318,7 @@ export default function GithubSettings (props) {
                 rows={2}
                 growVertically={false}
                 autoFocus
-              >{transformation?.prBodyClosePattern}
-              </TextArea>
+              />
             </FormGroup>
           </div>
 
diff --git a/config-ui/src/pages/configure/settings/gitlab.jsx b/config-ui/src/pages/configure/settings/gitlab.jsx
index 85119ae2..a8fa23d5 100644
--- a/config-ui/src/pages/configure/settings/gitlab.jsx
+++ b/config-ui/src/pages/configure/settings/gitlab.jsx
@@ -18,6 +18,7 @@
 import React, { useEffect } from 'react'
 import { useHistory } from 'react-router-dom'
 import { DataEntityTypes } from '@/data/DataEntities'
+import Deployment from '@/components/blueprints/transformations/CICD/Deployment'
 
 import '@/styles/integration.scss'
 import '@/styles/connections.scss'
@@ -26,44 +27,35 @@ export default function GitlabSettings (props) {
   const {
     connection,
     entities = [],
-    // eslint-disable-next-line max-len
     transformation = {},
+    entityIdKey,
     provider,
+    projects,
+    configuredProject,
+    isSaving = false,
+    isSavingConnection = false,
     onSettingsChange = () => {}
   } = props
-  const history = useHistory()
 
   useEffect(() => {
-    const settings = {
-      // no additional settings
-    }
-    onSettingsChange(settings)
-    console.log('>> GITLAB INSTANCE SETTINGS FIELDS CHANGED!', settings)
-  }, [
-    onSettingsChange
-  ])
-
-  // eslint-disable-next-line max-len
-  const cancel = () => {
-    history.push(`/integrations/${provider.id}`)
-  }
-
-  useEffect(() => {
-    if (connection && connection.id) {
-      console.log('>> GITLAB CONNECTION OBJECT RECEIVED...', connection)
-    } else {
-      console.log('>>>> WARNING!! NO CONNECTION OBJECT', connection)
-    }
-  }, [connection])
+    console.log('>>>> GITLAB: TRANSFORMATION SETTINGS OBJECT....', transformation)
+  }, [transformation])
 
   return (
     <>
-      <div className='headlineContainer'>
-        <h5>No Additional Settings</h5>
-        <p className='description'>
-          This project doesn’t require any configuration.
-        </p>
-      </div>
+      {entities.some(e => e.value === DataEntityTypes.DEVOPS) && configuredProject ? (
+        <Deployment
+          provider={provider}
+          entities={entities}
+          entityIdKey={entityIdKey}
+          transformation={transformation}
+          connection={connection}
+          onSettingsChange={onSettingsChange}
+          isSaving={isSaving || isSavingConnection}
+        />
+      ) : (
+        <></>
+      )}
     </>
   )
 }
diff --git a/config-ui/src/pages/configure/settings/jenkins.jsx b/config-ui/src/pages/configure/settings/jenkins.jsx
index 287cf794..8d29b3e9 100644
--- a/config-ui/src/pages/configure/settings/jenkins.jsx
+++ b/config-ui/src/pages/configure/settings/jenkins.jsx
@@ -20,13 +20,37 @@ import {
   useParams,
   useHistory
 } from 'react-router-dom'
+import {
+  Button,
+  ButtonGroup,
+  Classes,
+  Intent,
+  FormGroup,
+  InputGroup,
+  Radio,
+  RadioGroup,
+  Switch,
+  Tag,
+  Tooltip,
+} from '@blueprintjs/core'
+
 import { DataEntityTypes } from '@/data/DataEntities'
+import Deployment from '@/components/blueprints/transformations/CICD/Deployment'
 
 import '@/styles/integration.scss'
 import '@/styles/connections.scss'
 
 export default function JenkinsSettings (props) {
-  const { provider, connection, entities = [], onSettingsChange = () => {} } = props
+  const {
+    provider,
+    transformation,
+    entityIdKey,
+    connection,
+    entities = [],
+    onSettingsChange = () => {},
+    isSaving = false,
+    isSavingConnection = false
+  } = props
   const history = useHistory()
   const { providerId, connectionId } = useParams()
 
@@ -49,14 +73,30 @@ export default function JenkinsSettings (props) {
     })
   }, [errors, onSettingsChange, connectionId, providerId])
 
+  useEffect(() => {
+    console.log('>>> JENKINS: DATA ENTITIES...', entities)
+  }, [entities])
+
   return (
     <>
-      <div className='headlineContainer'>
-        <h3 className='headline'>No Additional Settings</h3>
-        <p className='description'>
-          This integration doesn’t require any configuration.
-        </p>
-      </div>
+      {entities.some(e => e.value === DataEntityTypes.DEVOPS) ? (
+        <Deployment
+          provider={provider}
+          entities={entities}
+          entityIdKey={entityIdKey}
+          transformation={transformation}
+          connection={connection}
+          onSettingsChange={onSettingsChange}
+          isSaving={isSaving || isSavingConnection}
+        />
+      ) : (
+        <div className='headlineContainer'>
+          <h3 className='headline'>No Additional Settings</h3>
+          <p className='description'>
+            This integration doesn’t require any configuration.
+          </p>
+        </div>
+      )}
     </>
   )
 }
diff --git a/config-ui/src/pages/configure/settings/jira.jsx b/config-ui/src/pages/configure/settings/jira.jsx
index 07be90f4..482b33c8 100644
--- a/config-ui/src/pages/configure/settings/jira.jsx
+++ b/config-ui/src/pages/configure/settings/jira.jsx
@@ -53,11 +53,13 @@ const createTypeMapObject = (customType, standardType) => {
 
 export default function JiraSettings (props) {
   const {
+    provider,
     connection,
     blueprint,
     entities = [],
     configuredBoard,
     transformation = {},
+    entityIdKey,
     transformations = {},
     isSaving,
     onSettingsChange = () => {},
@@ -86,13 +88,13 @@ export default function JiraSettings (props) {
   // @todo: lift higher to dsm hook
   const savedRequirementTags = useMemo(() => boards[connection?.id]
     ? boards[connection?.id].reduce((pV, cV, iDx) => ({ ...pV, [cV?.id]: connection?.transformations ? connection?.transformations[iDx]?.requirementTags : transformation?.requirementTags, }), {})
-    : {}, [connection?.id, configuredBoard?.id, boards, transformations])
+    : {}, [connection?.id, boards, connection?.transformations, transformation?.requirementTags])
   const savedBugTags = useMemo(() => boards[connection?.id]
     ? boards[connection?.id].reduce((pV, cV, iDx) => ({ ...pV, [cV?.id]: connection?.transformations ? connection?.transformations[iDx]?.bugTags : transformation?.bugTags, }), {})
-    : {}, [connection?.id, configuredBoard?.id, boards, transformations])
+    : {}, [connection?.id, boards, connection?.transformations, transformation?.bugTags])
   const savedIncidentTags = useMemo(() => boards[connection?.id]
     ? boards[connection?.id].reduce((pV, cV, iDx) => ({ ...pV, [cV?.id]: connection?.transformations ? connection?.transformations[iDx]?.incidentTags : transformation?.incidentTags, }), {})
-    : {}, [connection?.id, configuredBoard?.id, boards, transformations])
+    : {}, [connection?.id, boards, connection?.transformations, transformation?.incidentTags])
 
   const [requirementTags, setRequirementTags] = useState(savedRequirementTags)
   const [bugTags, setBugTags] = useState(savedBugTags)
@@ -363,7 +365,7 @@ export default function JiraSettings (props) {
 
           <div className='issue-type-multiselect' style={{ display: 'flex', marginBottom: '10px' }}>
             <div className='issue-type-label' style={{ minWidth: '120px', paddingRight: '10px', paddingTop: '3px' }}>
-              <label>Incident</label>
+              <label>Incident <Tag intent={Intent.PRIMARY} style={{ fontSize: '10px' }} minimal>DORA</Tag></label>
             </div>
             <div className='issue-type-multiselect-selector' style={{ minWidth: '200px', width: '100%' }}>
               <MultiSelect
diff --git a/config-ui/src/pages/offline/index.jsx b/config-ui/src/pages/offline/index.jsx
index 30ff9ab1..30977417 100644
--- a/config-ui/src/pages/offline/index.jsx
+++ b/config-ui/src/pages/offline/index.jsx
@@ -29,6 +29,8 @@ import {
   Spinner,
   Colors
 } from '@blueprintjs/core'
+import { ReactComponent as Logo } from '@/images/devlake-logo.svg'
+import { ReactComponent as LogoText } from '@/images/devlake-textmark.svg'
 import '@/styles/offline.scss'
 
 const Offline = (props) => {
@@ -134,6 +136,10 @@ const Offline = (props) => {
             style={{ maxWidth: '500px', margin: '0 auto', display: 'flex', height: '100%', flexDirection: 'column' }}
           >
             <div style={{ margin: 'auto auto', padding: '16px' }}>
+              <div className='devlake-logo' style={{ margin: 0 }}>
+                <Logo width={48} height={48} className='logo' />
+                <LogoText width={100} height={13} className='logo-textmark' />
+              </div>
               <CSSTransition in={cardReady} timeout={900} classNames='offline-card'>
                 <Card
                   elevation={Elevation.THREE}
@@ -207,10 +213,10 @@ const Offline = (props) => {
                           style={{ cursor: 'pointer' }}
                         >
                           <Button
-                            intent={Intent.DANGER} icon='warning-sign'
+                            intent={Intent.PRIMARY}
+                            icon='warning-sign'
                             style={{ fontWeight: 700 }}
                             onClick={() => history.replace('/')}
-                            large
                           >Continue
                           </Button>
                         </Tooltip>
@@ -220,7 +226,6 @@ const Offline = (props) => {
                           style={{ marginLeft: '5px' }}
                           disabled={!cardReady}
                           onClick={() => testEndpoint()}
-                          large
                         />
                       </>
                       )
@@ -232,7 +237,6 @@ const Offline = (props) => {
                         <Button
                           icon='cog' intent={Intent.PRIMARY}
                           onClick={() => history.replace('/')}
-                          large
                         >
                           Open Dashboard
                         </Button>
@@ -240,7 +244,7 @@ const Offline = (props) => {
                           href='https://github.com/apache/incubator-devlake/blob/main/README.md'
                           target='_blank'
                           rel='noreferrer'
-                          style={{ marginLeft: '5px' }} className='bp3-button bp3-large bp3-minimal'
+                          style={{ marginLeft: '5px' }} className='bp3-button bp3-minimal'
                         >
                           <Icon icon='help' size={16} style={{ marginRight: '5px' }} />
                           Read Documentation
@@ -250,7 +254,6 @@ const Offline = (props) => {
                 </Card>
               </CSSTransition>
               <div style={{ margin: '10px 5px 10px 5px', display: 'flex', justifyContnt: 'flex-start' }}>
-                <img src='/logo.svg' width={20} style={{ width: '20px', marginTop: '3px', marginRight: '5px', alignSelf: 'flex-start' }} />
                 <div style={{ color: 'rgba(33, 33, 33, 0.6)' }}>
                   {isOffline
                     ? (
diff --git a/config-ui/src/styles/offline.scss b/config-ui/src/styles/offline.scss
index 85e31c5a..72cdbdd0 100644
--- a/config-ui/src/styles/offline.scss
+++ b/config-ui/src/styles/offline.scss
@@ -38,9 +38,9 @@
   }
 
   &.is-online::before {
-    background: #DE6262;
-    background: -webkit-linear-gradient(to right, #FFB88C, #DE6262);
-    background: linear-gradient(to right, #FFB88C, #DE6262);
+    background: #606c88;
+    background: -webkit-linear-gradient(to left, #3f4c6b, #606c88);
+    background: linear-gradient(to left, #3f4c6b, #606c88);
   }
 
   .bp3-card {