@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.
- package/dist/Common/BreadCrumb/BreadCrumb.d.ts +1 -29
- package/dist/Common/BreadCrumb/Types.d.ts +28 -0
- package/dist/Common/Checkbox.d.ts +1 -14
- package/dist/Common/Constants.d.ts +13 -0
- package/dist/Common/CustomTagSelector/ResizableTagTextArea.d.ts +1 -1
- package/dist/Common/CustomTagSelector/TagLabelValueSelector.d.ts +1 -1
- package/dist/Common/CustomTagSelector/Types.d.ts +11 -3
- package/dist/Common/CustomTagSelector/ValidationRules.d.ts +1 -1
- package/dist/Common/DeleteComponentModal/DeleteComponent.d.ts +4 -0
- package/dist/Common/DeleteComponentModal/types.d.ts +15 -0
- package/dist/Common/Dialogs/ConfirmationDialog.d.ts +5 -16
- package/dist/Common/Dialogs/DialogForm.d.ts +1 -9
- package/dist/Common/Dialogs/Types.d.ts +27 -0
- package/dist/Common/EmptyState/EmptyState.d.ts +2 -1
- package/dist/Common/EmptyState/GenericEmptyState.d.ts +5 -0
- package/dist/Common/Error.d.ts +2 -2
- package/dist/Common/Helper.d.ts +31 -1
- package/dist/Common/InfoColorBar/InfoColourbar.d.ts +2 -15
- package/dist/Common/Modals/Modal.d.ts +2 -9
- package/dist/Common/MultiSelectCustomization.d.ts +1 -2
- package/dist/Common/PopupMenu.d.ts +4 -21
- package/dist/Common/Progressing.d.ts +4 -13
- package/dist/Common/RadioGroup/RadioGroup.d.ts +17 -0
- package/dist/Common/RadioGroup.d.ts +1 -10
- package/dist/Common/Reload.d.ts +2 -4
- package/dist/Common/Security/ScanVulnerabilitiesTable.d.ts +4 -0
- package/dist/Common/TippyCustomized.d.ts +2 -30
- package/dist/Common/Toggle/Toggle.d.ts +12 -0
- package/dist/Common/Types.d.ts +266 -0
- package/dist/Common/index.d.ts +6 -1
- package/dist/index.js +2388 -1943
- package/package.json +2 -2
- package/rollup.config.js +3 -3
- package/src/Assets/Icon/ic-info-filled.svg +7 -0
- package/src/Assets/Icon/ic-warning.svg +7 -0
- package/src/Assets/Img/app-not-deployed.png +0 -0
- package/src/Common/Api.ts +7 -1
- package/src/Common/BreadCrumb/BreadCrumb.tsx +1 -30
- package/src/Common/BreadCrumb/Types.ts +30 -0
- package/src/Common/Checkbox.tsx +3 -16
- package/src/Common/Constants.ts +15 -0
- package/src/Common/CustomTagSelector/ResizableTagTextArea.tsx +6 -4
- package/src/Common/CustomTagSelector/TagDetails.tsx +15 -2
- package/src/Common/CustomTagSelector/TagLabelSelect.tsx +7 -3
- package/src/Common/CustomTagSelector/TagLabelValueSelector.tsx +66 -26
- package/src/Common/CustomTagSelector/Types.ts +11 -3
- package/src/Common/CustomTagSelector/ValidationRules.ts +21 -18
- package/src/Common/DeleteComponentModal/DeleteComponent.tsx +95 -0
- package/src/Common/DeleteComponentModal/types.ts +15 -0
- package/src/Common/Dialogs/ConfirmationDialog.tsx +6 -4
- package/src/Common/Dialogs/DeleteDialog.tsx +2 -2
- package/src/Common/Dialogs/DialogForm.tsx +2 -11
- package/src/Common/Dialogs/Types.ts +28 -0
- package/src/Common/Drawer/Drawer.scss +12 -11
- package/src/Common/EmptyState/EmptyState.tsx +5 -5
- package/src/Common/EmptyState/GenericEmptyState.tsx +41 -0
- package/src/Common/EmptyState/emptyState.scss +4 -15
- package/src/Common/Error.tsx +13 -16
- package/src/Common/ErrorScreenNotFound.tsx +23 -30
- package/src/Common/Helper.tsx +258 -2
- package/src/Common/InfoColorBar/InfoColourbar.tsx +6 -18
- package/src/Common/Modals/Modal.tsx +7 -1
- package/src/Common/Modals/OpaqueModal.tsx +2 -2
- package/src/Common/Modals/VisibleModal.tsx +1 -1
- package/src/Common/Modals/VisibleModal2.tsx +1 -1
- package/src/Common/MultiSelectCustomization.tsx +3 -3
- package/src/Common/PopupMenu.tsx +23 -6
- package/src/Common/Progressing.tsx +8 -15
- package/src/Common/RadioGroup/RadioGroup.tsx +88 -0
- package/src/Common/RadioGroup/radioGroup.scss +46 -0
- package/src/Common/RadioGroup.tsx +3 -13
- package/src/Common/Reload.tsx +15 -10
- package/src/Common/Security/ScanVulnerabilitiesTable.tsx +55 -0
- package/src/Common/Security/scanVulnerabilities.css +67 -0
- package/src/Common/TippyCustomized.tsx +3 -31
- package/src/Common/Toggle/Toggle.scss +70 -0
- package/src/Common/Toggle/Toggle.tsx +38 -0
- package/src/Common/Types.ts +290 -0
- package/src/Common/index.ts +6 -1
- package/dist/27bfa7a87d0b7039.png +0 -0
- package/dist/b5d52f036bdcd3ed.png +0 -0
- 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.
|
|
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": "
|
|
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>
|
|
Binary file
|
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
|
package/src/Common/Checkbox.tsx
CHANGED
|
@@ -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
|
</>
|
package/src/Common/Constants.ts
CHANGED
|
@@ -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 = '
|
|
32
|
-
dependentRef.current.style.height = '
|
|
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 = '
|
|
51
|
-
dependentRef.current.style.height = '
|
|
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
|
-
|
|
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 =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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?:
|
|
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?:
|
|
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?:
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
}
|