@devtron-labs/devtron-fe-common-lib 0.0.47-beta-5 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devtron-labs/devtron-fe-common-lib",
3
- "version": "0.0.47-beta-5",
3
+ "version": "0.0.48",
4
4
  "description": "Supporting common component library",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -50,8 +50,6 @@
50
50
  "react-router-dom": "^5.3.0"
51
51
  },
52
52
  "dependencies": {
53
- "fast-json-patch": "^3.1.1",
54
- "jsonpath-plus": "^7.2.0",
55
53
  "sass": "^1.56.2",
56
54
  "tslib": "^2.4.1"
57
55
  }
@@ -0,0 +1,68 @@
1
+ import React from 'react'
2
+ import Tippy from '@tippyjs/react'
3
+ import { AddCDButtonProps, TooltipContentProps } from './types'
4
+ import { AddCDPositions } from '../Types'
5
+
6
+ function TooltipContent({ tooltipContent }: TooltipContentProps) {
7
+ if (tooltipContent) {
8
+ return (
9
+ <div className="dc__white-space-pre-wrap">
10
+ <p className="m-0 cn-0 fs-12 fw-6 lh-18">Click to add</p>
11
+
12
+ {tooltipContent}
13
+ </div>
14
+ )
15
+ }
16
+
17
+ return <div className="dc__white-space-pre-wrap">Click to add here</div>
18
+ }
19
+
20
+ export default function AddCDButton({
21
+ position,
22
+ addCDButtons,
23
+ endNode,
24
+ startNode,
25
+ handleAddCD,
26
+ tooltipContent,
27
+ }: Readonly<AddCDButtonProps>) {
28
+ const referenceNode = position === AddCDPositions.RIGHT ? endNode : startNode
29
+ const handleAddCDClick = () => {
30
+ handleAddCD(position)
31
+ }
32
+
33
+ if (addCDButtons?.includes(position)) {
34
+ return (
35
+ <Tippy
36
+ placement="top"
37
+ content={<TooltipContent tooltipContent={tooltipContent} />}
38
+ className="default-tt"
39
+ arrow={false}
40
+ >
41
+ <svg
42
+ x={referenceNode.x + (position === AddCDPositions.RIGHT ? -20 - 5 : referenceNode.width + 5)}
43
+ // Here 10 is the height of the button / 2
44
+ y={referenceNode.y + referenceNode.height / 2 - 10}
45
+ width="20"
46
+ height="20"
47
+ viewBox="0 0 20 20"
48
+ fill="none"
49
+ xmlns="http://www.w3.org/2000/svg"
50
+ data-testid={`add-cd-to-${position}`}
51
+ onClick={handleAddCDClick}
52
+ className="dc__outline-none-imp"
53
+ >
54
+ <rect width="20" height="20" rx="10" fill="#664BEE" className="add-cd-edge-btn" />
55
+ <path
56
+ d="M6.5 10H13.5M10 6.5V13.5"
57
+ stroke="white"
58
+ strokeWidth="1.5"
59
+ strokeLinecap="round"
60
+ strokeLinejoin="round"
61
+ />
62
+ </svg>
63
+ </Tippy>
64
+ )
65
+ }
66
+
67
+ return null
68
+ }
@@ -0,0 +1,2 @@
1
+ export { default as AddCDButton } from './AddCDButton'
2
+ export { handleAddCD } from './utils'
@@ -0,0 +1,38 @@
1
+ import { AddCDPositions, AddPipelineType, EdgeEndNodeType, EdgeNodeType, Point } from '../Types'
2
+
3
+ export interface AddCDButtonProps {
4
+ position: AddCDPositions
5
+ addCDButtons: AddCDPositions[]
6
+ endNode: Point & EdgeEndNodeType
7
+ startNode: Point & EdgeNodeType
8
+ handleAddCD: (position: AddCDPositions) => void
9
+ tooltipContent?: string
10
+ }
11
+
12
+ export interface HandleAddCD {
13
+ position: AddCDPositions
14
+ handleCDSelect: (
15
+ workflowId: number | string,
16
+ ciPipelineId: number | string,
17
+ parentPipelineType: string,
18
+ parentPipelineId: number | string,
19
+ isWebhookCD?: boolean,
20
+ childPipelineId?: number | string,
21
+ addType?: AddPipelineType,
22
+ ) => void
23
+ startNode: EdgeNodeType
24
+ endNode: EdgeEndNodeType
25
+ workflowId: number | string
26
+ ciPipelineId: number | string
27
+ isWebhookCD: boolean
28
+ isParallelEdge: boolean
29
+ }
30
+
31
+ export interface GetPipelineType {
32
+ isWebhookCD: boolean
33
+ startNode: EdgeNodeType
34
+ }
35
+
36
+ export interface TooltipContentProps {
37
+ tooltipContent?: string
38
+ }
@@ -0,0 +1,35 @@
1
+ import { AddCDPositions, AddPipelineType, PipelineType, WorkflowNodeType } from '../Types'
2
+ import { HandleAddCD, GetPipelineType } from './types'
3
+
4
+ const getPipelineType = ({ isWebhookCD, startNode }: GetPipelineType) => {
5
+ if (isWebhookCD) {
6
+ return PipelineType.WEBHOOK
7
+ }
8
+
9
+ if (startNode.type === WorkflowNodeType.CI) {
10
+ return PipelineType.CI_PIPELINE
11
+ }
12
+
13
+ return PipelineType.CD_PIPELINE
14
+ }
15
+
16
+ export const handleAddCD = ({
17
+ position,
18
+ handleCDSelect,
19
+ startNode,
20
+ endNode,
21
+ workflowId,
22
+ ciPipelineId,
23
+ isWebhookCD,
24
+ isParallelEdge,
25
+ }: HandleAddCD) => {
26
+ if (!handleCDSelect) {
27
+ return
28
+ }
29
+ const pipelineType = getPipelineType({ isWebhookCD, startNode })
30
+ const addPipelineType =
31
+ isParallelEdge && position === AddCDPositions.RIGHT ? AddPipelineType.PARALLEL : AddPipelineType.SEQUENTIAL
32
+ const endNodeId = !isParallelEdge && position === AddCDPositions.RIGHT ? endNode.id : null
33
+
34
+ handleCDSelect(workflowId, ciPipelineId, pipelineType, startNode.id, isWebhookCD, endNodeId, addPipelineType)
35
+ }
@@ -309,7 +309,3 @@ export function createGitCommitUrl(url: string, revision: string): string {
309
309
  }
310
310
  return "NA"
311
311
  }
312
-
313
- export function fetchChartTemplateVersions() {
314
- return get(`${ROUTES.DEPLOYMENT_TEMPLATE_LIST}?appId=-1&envId=-1`)
315
- }
@@ -24,8 +24,7 @@ export const ROUTES = {
24
24
  PROJECT_LIST_MIN: 'team/autocomplete',
25
25
  USER_CHECK_ROLE: 'user/check/roles',
26
26
  IMAGE_TAGGING:'app/image-tagging',
27
- CD_MATERIAL_GET: 'app/cd-pipeline',
28
- DEPLOYMENT_TEMPLATE_LIST: 'app/template/list'
27
+ CD_MATERIAL_GET: 'app/cd-pipeline'
29
28
  }
30
29
 
31
30
  export enum KEY_VALUE {
@@ -45,7 +45,7 @@ export const DeleteDialog: React.FC<DeleteDialogProps> & { Description?: React.F
45
45
  type="button"
46
46
  className="cta delete cta-cd-delete-modal ml-16"
47
47
  onClick={handleDelete}
48
- disabled={props.apiCallInProgress}
48
+ disabled={props.apiCallInProgress || props.disabled}
49
49
  data-testid="dialog-delete"
50
50
  >
51
51
  {props.apiCallInProgress ? (
@@ -11,6 +11,7 @@ export interface DeleteDialogProps {
11
11
  dataTestId?: string
12
12
  buttonPrimaryText?: string
13
13
  shouldStopPropagation?: boolean
14
+ disabled?: boolean
14
15
  }
15
16
 
16
17
  export interface ForceDeleteDialogType {
@@ -7,8 +7,6 @@ import { ERROR_EMPTY_SCREEN, TOKEN_COOKIE_NAME } from './Constants'
7
7
  import { ServerErrors } from './ServerError'
8
8
  import { toastAccessDenied } from './ToastBody'
9
9
  import { AsyncOptions, AsyncState, UseSearchString } from './Types'
10
- import { JSONPath } from 'jsonpath-plus'
11
- import * as jsonpatch from 'fast-json-patch'
12
10
 
13
11
  toast.configure({
14
12
  autoClose: 3000,
@@ -460,99 +458,3 @@ export const getUrlWithSearchParams = (url: string, params: Record<string | numb
460
458
  * Custom exception logger function for logging errors to sentry
461
459
  */
462
460
  export const logExceptionToSentry = Sentry.captureException.bind(window)
463
-
464
- export const customStyles = {
465
- control: (base, state) => ({
466
- ...base,
467
- minHeight: '32px',
468
- boxShadow: 'none',
469
- border: 'none',
470
- cursor: 'pointer',
471
- background: 'transparent',
472
- }),
473
- indicatorSeparator: (base, state) => ({
474
- ...base,
475
- width: 0,
476
- }),
477
- valueContainer: (base, state) => ({
478
- ...base,
479
- padding: '0',
480
- fontSize: '13px',
481
- fontWeight: '600',
482
- }),
483
- dropdownIndicator: (base, state) => ({
484
- ...base,
485
- color: 'var(--N400)',
486
- padding: '0 8px',
487
- transition: 'all .2s ease',
488
- transform: state.selectProps.menuIsOpen ? 'rotate(180deg)' : 'rotate(0deg)',
489
- }),
490
- }
491
-
492
- export const getFilteredChartVersions = (charts, selectedChartType) => {
493
- // Filter chart versions based on selected chart type
494
- return charts
495
- .filter((item) => item?.chartType === selectedChartType.value)
496
- .map((item) => ({
497
- value: item?.chartVersion,
498
- label: item?.chartVersion,
499
- chartRefId: item.chartRefId,
500
- }))
501
- }
502
- function removeEmptyObjectKeysAndNullValues(obj) {
503
- //it recursively removes empty object keys and array values that are null
504
- for (let key in obj) {
505
- if (Array.isArray(obj[key])) obj[key] = obj[key].filter((item) => item !== null)
506
- if (obj[key].length === 0) {
507
- // Check if the array is empty
508
- delete obj[key] // Delete the key if the array is empty
509
- } else if (obj[key] && typeof obj[key] === 'object') {
510
- if (removeEmptyObjectKeysAndNullValues(obj[key])) {
511
- delete obj[key]
512
- }
513
- } else if (obj[key] === null || obj[key] === undefined) {
514
- delete obj[key]
515
- }
516
- }
517
- return Object.keys(obj).length === 0
518
- }
519
-
520
- export function getUnlockedJSON(json, jsonPathArray) {
521
- const jsonCopy = JSON.parse(JSON.stringify(json))
522
- let patches = jsonPathArray.flatMap((jsonPath) => {
523
- const pathsToRemove = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' })
524
- return pathsToRemove.map((result) =>
525
- Array.isArray(result.parent)
526
- ? { op: 'replace', path: result.pointer, value: null }
527
- : { op: 'remove', path: result.pointer },
528
- )
529
- })
530
- let newDocument = jsonpatch.applyPatch(jsonCopy, patches).newDocument
531
-
532
- removeEmptyObjectKeysAndNullValues(newDocument)
533
- return newDocument
534
- }
535
-
536
- export function getLockedJSON(json, jsonPathArray: string[]) {
537
- const jsonCopy = JSON.parse(JSON.stringify(json))
538
- let resultJson = {}
539
- jsonPathArray.forEach((jsonPath) => {
540
- const elements = JSONPath({ path: jsonPath, json: jsonCopy, resultType: 'all' })
541
- elements.forEach((element) => {
542
- const pathArray: string[] = JSONPath.toPathArray(element.path)
543
- const lastPath = pathArray.pop()
544
- let current = resultJson
545
- for (let i = 0; i < pathArray.length; i++) {
546
- let key = isNaN(Number(pathArray[i])) ? pathArray[i] : parseInt(pathArray[i])
547
- if (!current[key]) {
548
- current[key] = isNaN(Number(pathArray[i + 1])) ? {} : []
549
- }
550
- current = current[key]
551
- }
552
- let key = isNaN(Number(lastPath)) ? lastPath : parseInt(lastPath)
553
- current[key] = element.value
554
- })
555
- })
556
- return resultJson['$']
557
- }
558
-
@@ -293,6 +293,11 @@ export interface CDMaterialServiceQueryParams {
293
293
  filter?: CDMaterialFilterQuery
294
294
  }
295
295
 
296
+ export interface DownstreamNodesEnvironmentsType {
297
+ environmentId: number
298
+ environmentName: string
299
+ }
300
+
296
301
  export interface CommonNodeAttr {
297
302
  connectingCiPipelineId?: number
298
303
  parents: string | number[] | string[]
@@ -354,6 +359,9 @@ export interface CommonNodeAttr {
354
359
  }
355
360
  appReleaseTagNames?: string[]
356
361
  tagsEditable?: boolean
362
+ deploymentAppCreated?: boolean
363
+ isLast?: boolean
364
+ downstreamEnvironments?: DownstreamNodesEnvironmentsType[]
357
365
  }
358
366
 
359
367
  export enum DeploymentAppTypes {
@@ -624,72 +632,50 @@ export interface CiPipeline {
624
632
  pipelineType?: string
625
633
  }
626
634
 
627
- export interface DeploymentChartVersionType {
628
- chartRefId: number
629
- chartVersion: string
630
- chartType: string
631
- type: number
635
+ export enum PipelineType {
636
+ CI_PIPELINE = 'CI_PIPELINE',
637
+ CD_PIPELINE = 'CD_PIPELINE',
638
+ WEBHOOK = 'WEBHOOK',
639
+ LINKED_CD = 'LINKED_CD',
632
640
  }
633
641
 
634
- export interface ChartVersionAndTypeSelectorProps {
635
- setSelectedChartRefId: React.Dispatch<React.SetStateAction<number>>
642
+ export enum WorkflowNodeType {
643
+ GIT = 'GIT',
644
+ CI = 'CI',
645
+ WEBHOOK = 'WEBHOOK',
646
+ PRE_CD = 'PRECD',
647
+ CD = 'CD',
648
+ POST_CD = 'POSTCD',
636
649
  }
637
650
 
638
- export interface InformationBarProps {
639
- text: string
640
- className?: string
641
- children?: React.ReactNode
651
+ export enum AddCDPositions {
652
+ LEFT = 'left',
653
+ RIGHT = 'right',
642
654
  }
643
655
 
644
- export interface CodeEditorInterface {
645
- value?: string
646
- lineDecorationsWidth?: number
647
- responseType?: string
648
- onChange?: (string) => void
649
- onBlur?: () => void
650
- onFocus?: () => void
651
- children?: any
652
- defaultValue?: string
653
- mode?: 'json' | 'yaml' | 'shell' | 'dockerfile' | 'plaintext'
654
- tabSize?: number
655
- readOnly?: boolean
656
- noParsing?: boolean
657
- minHeight?: number
658
- maxHeight?: number
659
- inline?: boolean
660
- height?: number | string
661
- shebang?: string | JSX.Element
662
- diffView?: boolean
663
- loading?: boolean
664
- customLoader?: JSX.Element
665
- theme?: string
666
- original?: string
667
- focus?: boolean
668
- validatorSchema?: any
669
- isKubernetes?: boolean
670
- cleanData?: boolean
671
- chartVersion?: any
672
- }
673
-
674
- export interface CodeEditorComposition {
675
- Header?: React.FC<any>
676
- LanguageChanger?: React.FC<any>
677
- ThemeChanger?: React.FC<any>
678
- ValidationError?: React.FC<any>
679
- Clipboard?: React.FC<any>
680
- Warning?: React.FC<InformationBarProps>
681
- ErrorBar?: React.FC<InformationBarProps>
682
- Information?: React.FC<InformationBarProps>
683
- }
684
-
685
- export interface CodeEditorHeaderComposition {
686
- LanguageChanger?: React.FC<any>;
687
- ThemeChanger?: React.FC<any>;
688
- ValidationError?: React.FC<any>;
689
- Clipboard?: React.FC<any>;
690
- }
691
- export interface CodeEditorHeaderInterface {
692
- children?: any;
693
- className?: string
694
- hideDefaultSplitHeader?: boolean;
695
- }
656
+ export interface SelectedNode {
657
+ nodeType: WorkflowNodeType
658
+ id: string
659
+ }
660
+
661
+ export enum AddPipelineType {
662
+ SEQUENTIAL = 'SEQUENTIAL',
663
+ PARALLEL = 'PARALLEL',
664
+ }
665
+
666
+ export interface Point {
667
+ x: number
668
+ y: number
669
+ }
670
+
671
+ export interface EdgeNodeType {
672
+ height: number
673
+ width: number
674
+ userApprovalConfig?: UserApprovalConfigType
675
+ type?: any
676
+ id?: number | string
677
+ }
678
+
679
+ export interface EdgeEndNodeType extends EdgeNodeType {
680
+ userApprovalConfig?: UserApprovalConfigType
681
+ }
@@ -42,5 +42,5 @@ export * from './Hooks'
42
42
  export { AppStatus } from './AppStatus'
43
43
  export * from './RJSF'
44
44
  export * from './DevtronProgressing'
45
- export { default as ChartVersionAndTypeSelector } from './ChartVersionAndTypeSelector'
45
+ export * from './AddCDButton'
46
46
  export * from './CustomInput'
@@ -1,4 +0,0 @@
1
- /// <reference types="react" />
2
- import { ChartVersionAndTypeSelectorProps } from './Types';
3
- declare const ChartVersionAndTypeSelector: ({ setSelectedChartRefId }: ChartVersionAndTypeSelectorProps) => JSX.Element;
4
- export default ChartVersionAndTypeSelector;
@@ -1,78 +0,0 @@
1
- import React, { useEffect, useState } from 'react'
2
- import { fetchChartTemplateVersions } from './Common.service'
3
- import { ChartVersionAndTypeSelectorProps, DeploymentChartVersionType } from './Types'
4
- import ReactSelect from 'react-select'
5
- import { customStyles, getFilteredChartVersions, showError } from './Helper'
6
-
7
- // @TODO: Generalize this component to be used in CodeEditor as Chart selector toolbar
8
- // when the Code Editor is moved to the fe-common-lib
9
- const ChartVersionAndTypeSelector = ({ setSelectedChartRefId }: ChartVersionAndTypeSelectorProps) => {
10
- const [charts, setCharts] = useState<DeploymentChartVersionType[]>([])
11
- const [selectedChartType, setSelectedChartType] = useState(null)
12
- const [chartVersionOptions, setChartVersionOptions] = useState([])
13
- const [chartTypeOptions, setChartTypeOptions] = useState([])
14
- const [selectedChartVersion, setSelectedChartVersion] = useState(null)
15
-
16
- useEffect(() => {
17
- fetchChartTemplateVersions()
18
- .then((res) => {
19
- const charts = res?.result || []
20
- setCharts(charts)
21
- // Extract unique chart types from the data
22
- const chartTypeOptions = [...new Set(charts.map((item) => item.chartType))].map((type) => ({
23
- value: type,
24
- label: type,
25
- }))
26
- setChartTypeOptions(chartTypeOptions)
27
- const filteredVersions = getFilteredChartVersions(charts, chartTypeOptions[0])
28
- selectFirstChartVersion(filteredVersions)
29
- })
30
- .catch((err) => {
31
- showError(err)
32
- })
33
- }, [])
34
-
35
- const selectFirstChartVersion = (filteredVersions) => {
36
- setChartVersionOptions(filteredVersions)
37
- setSelectedChartVersion(filteredVersions[0]) // Select the first chart version by default
38
- setSelectedChartRefId(filteredVersions[0]?.chartRefId)
39
- }
40
-
41
- // Function to update chart version options based on selected chart type
42
- const handleChartTypeChange = (selectedOption) => {
43
- setSelectedChartType(selectedOption)
44
- const filteredVersions = getFilteredChartVersions(charts, selectedOption)
45
- selectFirstChartVersion(filteredVersions)
46
- }
47
-
48
- // Function to handle the change of the selected chart version
49
- const handleChartVersionChange = (selectedOption) => {
50
- setSelectedChartVersion(selectedOption)
51
- setSelectedChartRefId(selectedOption.chartRefId)
52
- }
53
-
54
- return (
55
- <div className="flex">
56
- <div className="chart-type-options flex" data-testid="chart-type-options">
57
- <span className="chart-type-options-label mr-4">Chart Type</span>
58
- <ReactSelect
59
- value={selectedChartType ?? chartTypeOptions[0]}
60
- options={chartTypeOptions}
61
- onChange={handleChartTypeChange}
62
- styles={customStyles}
63
- />
64
- </div>
65
- <div className="chart-version-options flex" data-testid="chart-version-options">
66
- <span className="chart-version-options-label mr-4">Chart Version</span>
67
- <ReactSelect
68
- value={selectedChartVersion ?? chartVersionOptions[0]}
69
- options={chartVersionOptions}
70
- onChange={handleChartVersionChange}
71
- styles={customStyles}
72
- />
73
- </div>
74
- </div>
75
- )
76
- }
77
-
78
- export default ChartVersionAndTypeSelector