@devtron-labs/devtron-fe-common-lib 0.0.7 → 0.0.9

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.
Files changed (82) hide show
  1. package/dist/Common/BreadCrumb/BreadCrumb.d.ts +1 -29
  2. package/dist/Common/BreadCrumb/Types.d.ts +28 -0
  3. package/dist/Common/Checkbox.d.ts +1 -14
  4. package/dist/Common/Constants.d.ts +13 -0
  5. package/dist/Common/CustomTagSelector/ResizableTagTextArea.d.ts +1 -1
  6. package/dist/Common/CustomTagSelector/TagLabelValueSelector.d.ts +1 -1
  7. package/dist/Common/CustomTagSelector/Types.d.ts +11 -3
  8. package/dist/Common/CustomTagSelector/ValidationRules.d.ts +1 -1
  9. package/dist/Common/DeleteComponentModal/DeleteComponent.d.ts +4 -0
  10. package/dist/Common/DeleteComponentModal/types.d.ts +15 -0
  11. package/dist/Common/Dialogs/ConfirmationDialog.d.ts +5 -16
  12. package/dist/Common/Dialogs/DialogForm.d.ts +1 -9
  13. package/dist/Common/Dialogs/Types.d.ts +27 -0
  14. package/dist/Common/EmptyState/EmptyState.d.ts +2 -1
  15. package/dist/Common/EmptyState/GenericEmptyState.d.ts +5 -0
  16. package/dist/Common/Error.d.ts +2 -2
  17. package/dist/Common/Helper.d.ts +31 -1
  18. package/dist/Common/InfoColorBar/InfoColourbar.d.ts +2 -15
  19. package/dist/Common/Modals/Modal.d.ts +2 -9
  20. package/dist/Common/MultiSelectCustomization.d.ts +1 -2
  21. package/dist/Common/PopupMenu.d.ts +4 -21
  22. package/dist/Common/Progressing.d.ts +4 -13
  23. package/dist/Common/RadioGroup/RadioGroup.d.ts +17 -0
  24. package/dist/Common/RadioGroup.d.ts +1 -10
  25. package/dist/Common/Reload.d.ts +2 -4
  26. package/dist/Common/Security/ScanVulnerabilitiesTable.d.ts +4 -0
  27. package/dist/Common/TippyCustomized.d.ts +2 -30
  28. package/dist/Common/Toggle/Toggle.d.ts +12 -0
  29. package/dist/Common/Types.d.ts +266 -0
  30. package/dist/Common/index.d.ts +6 -1
  31. package/dist/index.js +2388 -1943
  32. package/package.json +2 -2
  33. package/rollup.config.js +3 -3
  34. package/src/Assets/Icon/ic-info-filled.svg +7 -0
  35. package/src/Assets/Icon/ic-warning.svg +7 -0
  36. package/src/Assets/Img/app-not-deployed.png +0 -0
  37. package/src/Common/Api.ts +7 -1
  38. package/src/Common/BreadCrumb/BreadCrumb.tsx +1 -30
  39. package/src/Common/BreadCrumb/Types.ts +30 -0
  40. package/src/Common/Checkbox.tsx +3 -16
  41. package/src/Common/Constants.ts +15 -0
  42. package/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +6 -4
  43. package/src/Common/CustomTagSelector/TagDetails.tsx +15 -2
  44. package/src/Common/CustomTagSelector/TagLabelSelect.tsx +7 -3
  45. package/src/Common/CustomTagSelector/TagLabelValueSelector.tsx +66 -26
  46. package/src/Common/CustomTagSelector/Types.ts +11 -3
  47. package/src/Common/CustomTagSelector/ValidationRules.ts +21 -18
  48. package/src/Common/DeleteComponentModal/DeleteComponent.tsx +95 -0
  49. package/src/Common/DeleteComponentModal/types.ts +15 -0
  50. package/src/Common/Dialogs/ConfirmationDialog.tsx +6 -4
  51. package/src/Common/Dialogs/DeleteDialog.tsx +2 -2
  52. package/src/Common/Dialogs/DialogForm.tsx +2 -11
  53. package/src/Common/Dialogs/Types.ts +28 -0
  54. package/src/Common/Drawer/Drawer.scss +12 -11
  55. package/src/Common/EmptyState/EmptyState.tsx +5 -5
  56. package/src/Common/EmptyState/GenericEmptyState.tsx +41 -0
  57. package/src/Common/EmptyState/emptyState.scss +4 -15
  58. package/src/Common/Error.tsx +13 -16
  59. package/src/Common/ErrorScreenNotFound.tsx +23 -30
  60. package/src/Common/Helper.tsx +258 -2
  61. package/src/Common/InfoColorBar/InfoColourbar.tsx +6 -18
  62. package/src/Common/Modals/Modal.tsx +7 -1
  63. package/src/Common/Modals/OpaqueModal.tsx +2 -2
  64. package/src/Common/Modals/VisibleModal.tsx +1 -1
  65. package/src/Common/Modals/VisibleModal2.tsx +1 -1
  66. package/src/Common/MultiSelectCustomization.tsx +3 -3
  67. package/src/Common/PopupMenu.tsx +23 -6
  68. package/src/Common/Progressing.tsx +8 -15
  69. package/src/Common/RadioGroup/RadioGroup.tsx +88 -0
  70. package/src/Common/RadioGroup/radioGroup.scss +46 -0
  71. package/src/Common/RadioGroup.tsx +3 -13
  72. package/src/Common/Reload.tsx +15 -10
  73. package/src/Common/Security/ScanVulnerabilitiesTable.tsx +55 -0
  74. package/src/Common/Security/scanVulnerabilities.css +67 -0
  75. package/src/Common/TippyCustomized.tsx +3 -31
  76. package/src/Common/Toggle/Toggle.scss +70 -0
  77. package/src/Common/Toggle/Toggle.tsx +38 -0
  78. package/src/Common/Types.ts +290 -0
  79. package/src/Common/index.ts +6 -1
  80. package/dist/27bfa7a87d0b7039.png +0 -0
  81. package/dist/b5d52f036bdcd3ed.png +0 -0
  82. package/src/Assets/Icon/ic-close.svg +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devtron-labs/devtron-fe-common-lib",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "Supporting common component library",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -36,7 +36,7 @@
36
36
  "typescript": "^4.9.4",
37
37
  "react-toastify": "^8.2.0",
38
38
  "@tippyjs/react": "^4.2.0",
39
- "react-select": "^5.2.2",
39
+ "react-select": "5.2.2",
40
40
  "@sentry/browser": "^7.31.1"
41
41
  },
42
42
  "peerDependencies": {
package/rollup.config.js CHANGED
@@ -7,7 +7,7 @@ import del from 'rollup-plugin-delete'
7
7
  import pkg from './package.json'
8
8
  import svgr from '@svgr/rollup'
9
9
  import url from '@rollup/plugin-url'
10
- import commonjs from '@rollup/plugin-commonjs';
10
+ import commonjs from '@rollup/plugin-commonjs'
11
11
 
12
12
  export default {
13
13
  input: './src/index.ts',
@@ -24,8 +24,8 @@ export default {
24
24
  exclude: 'node_modules/**',
25
25
  }),
26
26
  del({ targets: ['dist/*'] }),
27
- url(),
28
- svgr({ icon: true }),
27
+ url({limit: '100000'}),
28
+ svgr({ icon: true })
29
29
  ],
30
30
  external: Object.keys(pkg.peerDependencies || {}),
31
31
  }
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 32 32">
2
+ <g fill="none" fill-rule="evenodd">
3
+ <path d="M0 0h32v32H0z"/>
4
+ <path fill="#06C" d="M16 2.667C8.64 2.667 2.667 8.64 2.667 16S8.64 29.333 16 29.333 29.333 23.36 29.333 16 23.36 2.667 16 2.667z"/>
5
+ <path fill="#FFF" d="M16 14.667c.736 0 1.333.597 1.333 1.333v5.333a1.333 1.333 0 1 1-2.666 0V16c0-.736.597-1.333 1.333-1.333zm0-5.334A1.333 1.333 0 1 1 16 12a1.333 1.333 0 0 1 0-2.667z"/>
6
+ </g>
7
+ </svg>
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
2
+ <g fill="none" fill-rule="evenodd">
3
+ <path d="M0 0h32v32H0z"/>
4
+ <path fill="#FFCB5D" d="M18.315 5.052L29.72 25.01A2.667 2.667 0 0 1 27.405 29H4.595a2.667 2.667 0 0 1-2.315-3.99L13.685 5.052a2.667 2.667 0 0 1 4.63 0z"/>
5
+ <path fill="#FFF" d="M16 22.667a1.333 1.333 0 1 1 0 2.666 1.333 1.333 0 0 1 0-2.666zM16 12c.736 0 1.333.597 1.333 1.333v6.334a1.333 1.333 0 1 1-2.666 0v-6.334c0-.736.597-1.333 1.333-1.333z"/>
6
+ </g>
7
+ </svg>
package/src/Common/Api.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ServerErrors } from './ServerError'
2
- import { RequestTimeout, Host } from './Constants'
2
+ import { RequestTimeout, Host, URLS } from './Constants'
3
3
  import { ResponseType, APIOptions } from './Types'
4
4
 
5
5
  const responseMessages = {
@@ -73,6 +73,11 @@ const responseMessages = {
73
73
  599: 'Network connect timeout error',
74
74
  }
75
75
 
76
+ function handleLogout() {
77
+ const continueParam = `${window.location.pathname.replace(process.env.PUBLIC_URL, '')}${window.location.search}`
78
+ window.location.href = `${window.location.origin}${process.env.PUBLIC_URL}${URLS.LOGIN_SSO}?continue=${continueParam}`
79
+ }
80
+
76
81
  async function handleServerError(contentType, response) {
77
82
  //Test for HTTP Status Code
78
83
  let code: number = response.status
@@ -126,6 +131,7 @@ async function fetchAPI(
126
131
  ],
127
132
  })
128
133
  } else {
134
+ handleLogout()
129
135
  return { code: 401, status: 'Unauthorized', result: [] }
130
136
  }
131
137
  } else if (response.status >= 300 && response.status <= 599) {
@@ -2,32 +2,9 @@ import React, { useMemo, useEffect } from 'react'
2
2
  import { Link, useRouteMatch, useParams } from 'react-router-dom'
3
3
  import { useBreadcrumbContext } from './BreadcrumbStore'
4
4
  import { ConditionalWrap } from '../Helper'
5
+ import { Breadcrumb, Breadcrumbs, UseBreadcrumbOptionalProps, UseBreadcrumbState } from './Types'
5
6
  export const BreadcrumbContext = React.createContext(null)
6
7
 
7
- interface Breadcrumb {
8
- to: string
9
- name: string
10
- className?: string
11
- }
12
-
13
- interface UseBreadcrumbState {
14
- breadcrumbs: Breadcrumb[]
15
- setCrumb: (props: { [key: string]: any }) => void
16
- resetCrumb: (props: string[]) => void
17
- }
18
-
19
- interface AdvancedAlias {
20
- component: any
21
- linked: boolean
22
- }
23
-
24
- interface UseBreadcrumbProps {
25
- sep?: string
26
- alias?: { [key: string]: AdvancedAlias | any }
27
- }
28
-
29
- type UseBreadcrumbOptionalProps = UseBreadcrumbProps | null
30
-
31
8
  export function useBreadcrumb(props?: UseBreadcrumbOptionalProps, deps?: any[]): UseBreadcrumbState {
32
9
  let sep = props?.sep || '/'
33
10
  deps = deps || []
@@ -91,12 +68,6 @@ export function useBreadcrumb(props?: UseBreadcrumbOptionalProps, deps?: any[]):
91
68
  return { breadcrumbs, setCrumb, resetCrumb }
92
69
  }
93
70
 
94
- interface Breadcrumbs {
95
- breadcrumbs: Breadcrumb[]
96
- sep?: string
97
- className?: string
98
- }
99
-
100
71
  export const BreadCrumb: React.FC<Breadcrumbs> = ({
101
72
  breadcrumbs,
102
73
  sep = '/',
@@ -0,0 +1,30 @@
1
+
2
+ export interface Breadcrumb {
3
+ to: string
4
+ name: string
5
+ className?: string
6
+ }
7
+
8
+ export interface UseBreadcrumbState {
9
+ breadcrumbs: Breadcrumb[]
10
+ setCrumb: (props: { [key: string]: any }) => void
11
+ resetCrumb: (props: string[]) => void
12
+ }
13
+
14
+ export interface AdvancedAlias {
15
+ component: any
16
+ linked: boolean
17
+ }
18
+
19
+ export interface UseBreadcrumbProps {
20
+ sep?: string
21
+ alias?: { [key: string]: AdvancedAlias | any }
22
+ }
23
+
24
+ export interface Breadcrumbs {
25
+ breadcrumbs: Breadcrumb[]
26
+ sep?: string
27
+ className?: string
28
+ }
29
+
30
+ export type UseBreadcrumbOptionalProps = UseBreadcrumbProps | null
@@ -1,19 +1,5 @@
1
1
  import React, { Component } from 'react'
2
-
3
- export enum CHECKBOX_VALUE {
4
- CHECKED = 'CHECKED',
5
- INTERMEDIATE = 'INTERMEDIATE',
6
- }
7
- export interface CheckboxProps {
8
- onChange: (event) => void
9
- isChecked: boolean
10
- value: 'CHECKED' | 'INTERMEDIATE'
11
- disabled?: boolean
12
- tabIndex?: number
13
- rootClassName?: string
14
- onClick?: (event) => void
15
- id?: string
16
- }
2
+ import { CheckboxProps } from './Types'
17
3
 
18
4
  /*
19
5
  Valid States of Checkbox:
@@ -39,8 +25,9 @@ export class Checkbox extends Component<CheckboxProps> {
39
25
  tabIndex={this.props.tabIndex}
40
26
  checked={this.props.isChecked}
41
27
  id={this.props.id}
28
+ data-testid={this.props.dataTestId}
42
29
  />
43
- <span className="form__checkbox-container"></span>
30
+ <span className="form__checkbox-container" data-testid={`${this.props.dataTestId}-chk-span`}></span>
44
31
  <span className={`form__checkbox-label`}>{this.props.children}</span>
45
32
  </label>
46
33
  </>
@@ -14,6 +14,10 @@ export const PATTERNS = {
14
14
  ALPHANUMERIC_WITH_SPECIAL_CHAR: /^[A-Za-z0-9._-]+$/, // allow alphanumeric,(.) ,(-),(_)
15
15
  }
16
16
 
17
+ export const URLS = {
18
+ LOGIN_SSO: '/login/sso',
19
+ }
20
+
17
21
  export const ROUTES = {
18
22
  PROJECT_LIST_MIN: 'team/autocomplete',
19
23
  }
@@ -42,4 +46,15 @@ export const ERROR_EMPTY_SCREEN = {
42
46
  PAGE_NOT_FOUND: 'We could not find this page',
43
47
  PAGE_NOT_EXIST: 'This page doesn’t exist or was removed. We suggest you go back to home',
44
48
  TAKE_BACK_HOME: 'Take me home',
49
+ ONLY_FOR_SUPERADMIN: 'Information on this page is available only to superadmin users.',
50
+ NOT_AUTHORIZED: 'Not authorized',
51
+ UNAUTHORIZED: 'unauthorized',
52
+ FORBIDDEN: 'forbidden',
53
+ REQUIRED_MANAGER_ACCESS:
54
+ 'Looks like you don’t have access to information on this page. Please contact your manager to request access.',
45
55
  }
56
+ export const TOKEN_COOKIE_NAME = 'argocd.token'
57
+ export const TriggerTypeMap = {
58
+ automatic: 'Auto',
59
+ manual: 'Manual',
60
+ }
@@ -14,6 +14,7 @@ export function ResizableTagTextArea({
14
14
  tabIndex,
15
15
  refVar,
16
16
  dependentRef,
17
+ dataTestId,
17
18
  }: ResizableTagTextAreaProps) {
18
19
  const [text, setText] = useState('')
19
20
 
@@ -28,8 +29,8 @@ export function ResizableTagTextArea({
28
29
 
29
30
  const reInitHeight = () => {
30
31
  if (document.activeElement !== refVar.current) return
31
- refVar.current.style.height = 'auto'
32
- dependentRef.current.style.height = 'auto'
32
+ refVar.current.style.height = minHeight+ 'px'
33
+ dependentRef.current.style.height = minHeight+ 'px'
33
34
  let nextHeight = refVar.current.scrollHeight
34
35
  if (nextHeight < dependentRef.current.scrollHeight) {
35
36
  nextHeight = dependentRef.current.scrollHeight
@@ -47,8 +48,8 @@ export function ResizableTagTextArea({
47
48
  useThrottledEffect(reInitHeight, 500, [text])
48
49
 
49
50
  const handleOnBlur = (event) => {
50
- refVar.current.style.height = 'auto'
51
- dependentRef.current.style.height = 'auto'
51
+ refVar.current.style.height = minHeight+ 'px'
52
+ dependentRef.current.style.height = minHeight+ 'px'
52
53
  onBlur && onBlur(event)
53
54
  }
54
55
 
@@ -69,6 +70,7 @@ export function ResizableTagTextArea({
69
70
  onBlur={handleOnBlur}
70
71
  onFocus={handleOnFocus}
71
72
  tabIndex={tabIndex}
73
+ data-testid={dataTestId}
72
74
  />
73
75
  )
74
76
  }
@@ -5,6 +5,7 @@ import { TagLabelValueSelector } from './TagLabelValueSelector'
5
5
  import { KEY_VALUE } from '../Constants'
6
6
  import { stopPropagation } from '../Helper'
7
7
  import { TagDetailType } from './Types'
8
+ import { ValidationRules } from './ValidationRules'
8
9
 
9
10
  export const TagDetails = ({
10
11
  index,
@@ -16,6 +17,7 @@ export const TagDetails = ({
16
17
  }: TagDetailType) => {
17
18
  const keyRef = useRef(null)
18
19
  const valueRef = useRef(null)
20
+ const validationRules = new ValidationRules()
19
21
 
20
22
  const deleteTag = (e): void => {
21
23
  stopPropagation(e)
@@ -24,13 +26,24 @@ export const TagDetails = ({
24
26
  const propagateTagToResource = (): void => {
25
27
  const _tagData = { ...tagData }
26
28
  _tagData.propagate = !_tagData.propagate
29
+ if (_tagData.propagate) {
30
+ _tagData.isInvalidKey = _tagData.isInvalidKey || !_tagData.key
31
+ _tagData.isInvalidValue = _tagData.isInvalidValue || !_tagData.value
32
+ } else {
33
+ _tagData.isInvalidKey =
34
+ _tagData.key || _tagData.value ? !validationRules.propagateTagKey(_tagData.key).isValid : false
35
+ _tagData.isInvalidValue = _tagData.value
36
+ ? !validationRules.propagateTagValue(_tagData.value, _tagData.key).isValid
37
+ : false
38
+ }
27
39
  setTagData(index, _tagData)
28
40
  }
29
41
  return (
30
42
  <div className="flexbox mb-8">
31
43
  <div
32
- className={`dc__border h-30 pl-4 pr-4 br-4 mr-8 pointer ${tagData.propagate ? 'bcn-7' : ''}`}
44
+ className={`dc__border h-30 pl-4 pr-4 br-4 mr-8 pointer ${tagData.propagate ? 'bcn-7' : ''} ${tagData.key.startsWith("devtron.ai/") ? 'cursor-not-allowed bcn-1' : ''}`}
33
45
  onClick={propagateTagToResource}
46
+ data-testid={`propagate-tag-${index}`}
34
47
  >
35
48
  <InjectTag className={`icon-dim-20 mt-4 ${tagData.propagate ? 'scn-0' : ''}`} />
36
49
  </div>
@@ -55,7 +68,7 @@ export const TagDetails = ({
55
68
  refVar={valueRef}
56
69
  dependentRef={keyRef}
57
70
  />
58
- <div className="dc__border pl-4 pr-4 dc__right-radius-4 pointer flex top" onClick={deleteTag}>
71
+ <div className="dc__border pl-4 pr-4 dc__right-radius-4 pointer flex top" onClick={deleteTag} data-testid={`delete-tag-${index}`}>
59
72
  <DeleteCross className="icon-dim-20 mt-4" />
60
73
  </div>
61
74
  </div>
@@ -13,7 +13,11 @@ export const TagLabelSelect = ({
13
13
  }: TagLabelSelectType) => {
14
14
  const setTagData = (index, tagValue): void => {
15
15
  const _tags = [...labelTags]
16
- _tags[index] = tagValue
16
+ const _tagValue = tagValue
17
+ if(_tagValue.key.startsWith("devtron.ai/")){
18
+ _tagValue.propagate = false
19
+ }
20
+ _tags[index] = _tagValue
17
21
  setLabelTags(_tags)
18
22
  }
19
23
 
@@ -36,10 +40,10 @@ export const TagLabelSelect = ({
36
40
  <PropagateTagInfo isCreateApp={isCreateApp} />
37
41
  </div>
38
42
  <div>
39
- <div className="dc_width-max-content cb-5 fw-6 fs-13 flexbox mr-20 mb-8 cursor" onClick={addNewTag}>
43
+ <div className="dc_width-max-content cb-5 fw-6 fs-13 flexbox mr-20 mb-8 cursor" onClick={addNewTag} data-testid="add-tag-button">
40
44
  <Add className="icon-dim-20 fcb-5" /> Add tag
41
45
  </div>
42
- <div className="mb-8">
46
+ <div className="mb-8" data-testid="tag-rows-container">
43
47
  {labelTags?.map((tagData, index) => (
44
48
  <TagDetails
45
49
  key={`tag-${index}`}
@@ -1,12 +1,13 @@
1
1
  import React, { useState, useEffect } from 'react'
2
2
  import PopupMenu from '../PopupMenu'
3
- import { ValidationRules } from './ValidationRules'
4
3
  import { ReactComponent as ErrorCross } from '../../Assets/Icon/ic-cross.svg'
5
4
  import { ReactComponent as Info } from '../../Assets/Icon/ic-info-outlined.svg'
6
5
  import { KEY_VALUE } from '../Constants'
7
6
  import { stopPropagation } from '../Helper'
8
7
  import { ResizableTagTextArea } from './ResizableTagTextArea'
9
- import { TagLabelValueSelectorType } from './Types'
8
+ import { SuggestedTagOptionType, TagLabelValueSelectorType } from './Types'
9
+ import Tippy from '@tippyjs/react'
10
+ import { ValidationRules } from './ValidationRules'
10
11
 
11
12
  export const TagLabelValueSelector = ({
12
13
  selectedTagIndex,
@@ -19,6 +20,7 @@ export const TagLabelValueSelector = ({
19
20
  tabIndex = null,
20
21
  refVar,
21
22
  dependentRef,
23
+ noBackDrop,
22
24
  }: TagLabelValueSelectorType) => {
23
25
  const [selectedValue, setSelectedValue] = useState<string>('')
24
26
  const [activeElement, setActiveElement] = useState<string>('')
@@ -43,11 +45,12 @@ export const TagLabelValueSelector = ({
43
45
  const _tagData = { ...tagData }
44
46
  _tagData[tagInputType] = selectedValue
45
47
  if (tagInputType === KEY_VALUE.KEY) {
46
- _tagData.isInvalidKey = selectedValue
47
- ? !validationRules.propagateTagKey(selectedValue).isValid
48
- : _tagData.value !== ''
49
- } else if (selectedValue || isRequired) {
50
- _tagData.isInvalidValue = !validationRules.propagateTagValue(selectedValue).isValid
48
+ _tagData.isInvalidKey =
49
+ selectedValue || _tagData.propagate
50
+ ? !validationRules.propagateTagKey(selectedValue).isValid
51
+ : _tagData.value !== ''
52
+ } else if (selectedValue || isRequired || _tagData.propagate) {
53
+ _tagData.isInvalidValue = !validationRules.propagateTagValue(selectedValue, _tagData.key).isValid
51
54
  _tagData.isInvalidKey = !_tagData.key || _tagData.isInvalidKey
52
55
  } else {
53
56
  _tagData.isInvalidValue = false
@@ -65,7 +68,9 @@ export const TagLabelValueSelector = ({
65
68
  stopPropagation(e)
66
69
  const _tagData = { ...tagData }
67
70
  _tagData[tagInputType] = e.currentTarget.dataset.key
71
+ _tagData.propagate = e.currentTarget.dataset.propagate === 'true'
68
72
  setTagData(selectedTagIndex, _tagData)
73
+ setActiveElement('')
69
74
  }
70
75
 
71
76
  const renderValidationsSuggestions = (): JSX.Element => {
@@ -74,12 +79,12 @@ export const TagLabelValueSelector = ({
74
79
  if (selectedValue || tagData.value) {
75
80
  field = validationRules.propagateTagKey(selectedValue)
76
81
  }
77
- } else if (isRequired || selectedValue) {
78
- field = validationRules.propagateTagValue(selectedValue)
82
+ } else if (isRequired || selectedValue || tagData.propagate) {
83
+ field = validationRules.propagateTagValue(selectedValue, tagData.key)
79
84
  }
80
85
  if (!field.isValid) {
81
86
  return (
82
- <div className="p-4" onClick={stopPropagation}>
87
+ <div className="p-4" onClick={stopPropagation} data-testid="tag-label-cross-stop-propagation">
83
88
  {field.messages.map((error) => (
84
89
  <div key={error} className="flexbox pr-4 pl-4">
85
90
  <span>
@@ -102,22 +107,48 @@ export const TagLabelValueSelector = ({
102
107
  return null
103
108
  }
104
109
 
110
+ const option = (tag: SuggestedTagOptionType, index: number): JSX.Element => {
111
+ return (
112
+ <div
113
+ key={`${tag.value}-${index}`}
114
+ data-key={tag.label}
115
+ data-propagate={tag.propagate}
116
+ className="dc__hover-n50 dc__ellipsis-right lh-20 fs-13 fw-4 pt-6 pr-8 pb-6 pl-8 cursor"
117
+ onClick={onSelectValue}
118
+ data-testid={`tag-label-value-${index}`}
119
+ >
120
+ {tag.label}
121
+ </div>
122
+ )
123
+ }
124
+
125
+ const optionWithTippy = (tag: SuggestedTagOptionType, index: number): JSX.Element => {
126
+ return (
127
+ <Tippy
128
+ className="default-tt"
129
+ arrow={false}
130
+ placement="right"
131
+ content={
132
+ <div>
133
+ <div className="mb-10 fs-12 fw-6 cn-0 dc__break-word">{tag.label}</div>
134
+ <div className="fs-12 fw-4 cn-0 dc__break-word">{tag.description}</div>
135
+ </div>
136
+ }
137
+ >
138
+ {option(tag, index)}
139
+ </Tippy>
140
+ )
141
+ }
142
+
105
143
  const renderSuggestions = (): JSX.Element => {
106
144
  if (tagOptions?.length) {
107
- const filteredTags = tagOptions.filter((tag) => tag.value.indexOf(selectedValue) >= 0)
145
+ const filteredTags = tagOptions.filter((tag) => tag.label.indexOf(selectedValue) >= 0)
108
146
  if (filteredTags.length) {
109
147
  return (
110
148
  <div>
111
- {filteredTags.map((tag, index) => (
112
- <div
113
- key={`${tag.value}-${index}`}
114
- data-key={tag.label}
115
- className="dc__hover-n50 lh-20 fs-13 fw-4 pt-6 pr-8 pb-6 pl-8 cursor"
116
- onClick={onSelectValue}
117
- >
118
- {tag.label}
119
- </div>
120
- ))}
149
+ {filteredTags.map((tag, index) =>
150
+ tag.description ? optionWithTippy(tag, index) : option(tag, index),
151
+ )}
121
152
  </div>
122
153
  )
123
154
  }
@@ -125,13 +156,14 @@ export const TagLabelValueSelector = ({
125
156
  return renderValidationsSuggestions()
126
157
  }
127
158
 
159
+ const popupMenuBody = activeElement === `tag-${tagInputType}-${selectedTagIndex}` ? renderSuggestions() : null
128
160
  return (
129
161
  <PopupMenu autoClose autoPosition>
130
- <PopupMenu.Button rootClassName="dc__bg-n50 flex top dc__no-border">
162
+ <PopupMenu.Button rootClassName="dc__bg-n50 flex top dc__no-border-imp">
131
163
  <ResizableTagTextArea
132
164
  minHeight={30}
133
165
  maxHeight={80}
134
- className={`form__input pt-4-imp pb-4-imp fs-13 ${
166
+ className={`form__input tag-input pt-4-imp pb-4-imp fs-13 ${
135
167
  tagInputType === KEY_VALUE.KEY
136
168
  ? `dc__no-right-radius`
137
169
  : `dc__no-border-radius dc__no-right-border dc__no-left-border`
@@ -148,11 +180,19 @@ export const TagLabelValueSelector = ({
148
180
  tabIndex={tabIndex}
149
181
  refVar={refVar}
150
182
  dependentRef={dependentRef}
183
+ dataTestId={`tag-${tagInputType === KEY_VALUE.KEY ? 'key' : 'value'}-${selectedTagIndex}`}
151
184
  />
152
185
  </PopupMenu.Button>
153
- <PopupMenu.Body rootClassName={`tag-${selectedTagIndex}-class`} autoWidth={true} preventWheelDisable={true}>
154
- {activeElement === `tag-${tagInputType}-${selectedTagIndex}` && renderSuggestions()}
155
- </PopupMenu.Body>
186
+ {popupMenuBody && (
187
+ <PopupMenu.Body
188
+ rootClassName={`mxh-210 dc__overflow-auto tag-${selectedTagIndex}-class`}
189
+ autoWidth={true}
190
+ preventWheelDisable={true}
191
+ noBackDrop={noBackDrop}
192
+ >
193
+ {popupMenuBody}
194
+ </PopupMenu.Body>
195
+ )}
156
196
  </PopupMenu>
157
197
  )
158
198
  }
@@ -1,6 +1,10 @@
1
1
  import { KEY_VALUE } from '../Constants'
2
2
  import { OptionType } from '../Types'
3
3
 
4
+ export interface SuggestedTagOptionType extends OptionType {
5
+ description: string
6
+ propagate: boolean
7
+ }
4
8
  export interface TagType {
5
9
  id?: number
6
10
  key: string
@@ -11,6 +15,7 @@ export interface TagType {
11
15
  isInvalidKey?: boolean
12
16
  isInvalidValue?: boolean
13
17
  isSuggested?: boolean
18
+ isPropagateDisabled?: boolean
14
19
  }
15
20
 
16
21
  export interface TagErrorType {
@@ -23,7 +28,8 @@ export interface TagLabelSelectType {
23
28
  setLabelTags: (tagList: TagType[]) => void
24
29
  tabIndex?: number
25
30
  selectedProjectId?: number
26
- suggestedTagsOptions?: OptionType[]
31
+ suggestedTagsOptions?: SuggestedTagOptionType[]
32
+ reloadProjectTags?: boolean
27
33
  }
28
34
 
29
35
  export interface TagDetailType {
@@ -32,20 +38,21 @@ export interface TagDetailType {
32
38
  setTagData: (index: number, tagData: TagType) => void
33
39
  removeTag?: (index: number) => void
34
40
  tabIndex?: number
35
- suggestedTagsOptions?: OptionType[]
41
+ suggestedTagsOptions?: SuggestedTagOptionType[]
36
42
  }
37
43
 
38
44
  export interface TagLabelValueSelectorType {
39
45
  selectedTagIndex: number
40
46
  tagData: TagType
41
47
  setTagData: (index: number, tagData: TagType) => void
42
- tagOptions?: OptionType[]
48
+ tagOptions?: SuggestedTagOptionType[]
43
49
  isRequired?: boolean
44
50
  tagInputType?: KEY_VALUE
45
51
  placeholder?: string
46
52
  tabIndex?: number
47
53
  refVar?: React.MutableRefObject<HTMLTextAreaElement>
48
54
  dependentRef?: React.MutableRefObject<HTMLTextAreaElement>
55
+ noBackDrop?: boolean
49
56
  }
50
57
 
51
58
  export interface ResizableTagTextAreaProps {
@@ -60,4 +67,5 @@ export interface ResizableTagTextAreaProps {
60
67
  tabIndex?: number
61
68
  refVar?: React.MutableRefObject<HTMLTextAreaElement>
62
69
  dependentRef?: React.MutableRefObject<HTMLTextAreaElement>
70
+ dataTestId?: string
63
71
  }
@@ -16,39 +16,42 @@ const validateTagValue = (value: string): string[] => {
16
16
  return errorList
17
17
  }
18
18
  export class ValidationRules {
19
-
20
19
  propagateTagKey = (key: string): { isValid: boolean; messages: string[] } => {
21
20
  const errorList = []
22
21
  if (!key) {
23
22
  errorList.push('Key is required')
24
23
  } else {
25
- const re = new RegExp('/', 'g')
26
- const noOfSlashInKey = key.match(re)?.length
27
- if (noOfSlashInKey > 1) {
28
- errorList.push('Key: Max 1 ( / ) allowed')
29
- } else if (noOfSlashInKey === 1) {
30
- const [prefix, name] = key.split('/')
31
- errorList.push(...validateTagValue(name).map((error) => `Name: ${error}`))
32
- if (prefix.length > 253) {
33
- errorList.push('Prefix: Can be max 253 characters')
34
- }
35
- const validPrefix = PATTERNS.KUBERNETES_KEY_PREFIX.test(prefix)
36
- if (!validPrefix) {
37
- errorList.push('Prefix: Must be a DNS subdomain (a series of DNS labels separated by dots (.)')
24
+ if(!key.startsWith("devtron.ai/")){
25
+ const re = new RegExp('/', 'g')
26
+ const noOfSlashInKey = key.match(re)?.length
27
+ if (noOfSlashInKey > 1) {
28
+ errorList.push('Key: Max 1 ( / ) allowed')
29
+ } else if (noOfSlashInKey === 1) {
30
+ const [prefix, name] = key.split('/')
31
+ errorList.push(...validateTagValue(name).map((error) => `Name: ${error}`))
32
+ if (prefix.length > 253) {
33
+ errorList.push('Prefix: Can be max 253 characters')
34
+ }
35
+ const validPrefix = PATTERNS.KUBERNETES_KEY_PREFIX.test(prefix)
36
+ if (!validPrefix) {
37
+ errorList.push('Prefix: Must be a DNS subdomain (a series of DNS labels separated by dots (.)')
38
+ }
39
+ } else {
40
+ errorList.push(...validateTagValue(key).map((error) => `Name: ${error}`))
38
41
  }
39
- } else {
40
- errorList.push(...validateTagValue(key).map((error) => `Name: ${error}`))
41
42
  }
42
43
  }
43
44
  return { isValid: errorList.length === 0, messages: errorList }
44
45
  }
45
46
 
46
- propagateTagValue = (value: string): { isValid: boolean; messages: string[] } => {
47
+ propagateTagValue = (value: string, key: string): { isValid: boolean; messages: string[] } => {
47
48
  const errorList = []
48
49
  if (!value) {
49
50
  errorList.push('Value is required')
50
51
  } else {
51
- errorList.push(...validateTagValue(value))
52
+ if(!key.startsWith("devtron.ai/")){
53
+ errorList.push(...validateTagValue(value))
54
+ }
52
55
  }
53
56
  return { isValid: errorList.length === 0, messages: errorList }
54
57
  }