@devtron-labs/devtron-fe-common-lib 0.0.7-beta-51 → 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.
@@ -0,0 +1,95 @@
1
+ import React, { useState } from 'react'
2
+ import { useHistory } from 'react-router-dom'
3
+ import { toast } from 'react-toastify'
4
+ import info from '../../Assets/Icon/ic-info-filled.svg'
5
+ import { ConfirmationDialog, DeleteDialog } from '../Dialogs'
6
+ import { ServerErrors } from '../ServerError'
7
+ import { DeleteComponentProps } from './types'
8
+
9
+
10
+ function DeleteComponent({
11
+ setDeleting,
12
+ toggleConfirmation,
13
+ deleteComponent,
14
+ title,
15
+ component,
16
+ payload,
17
+ confirmationDialogDescription = '',
18
+ redirectTo = false,
19
+ url = '',
20
+ reload,
21
+ configuration = '',
22
+ closeCustomComponent,
23
+ }: DeleteComponentProps) {
24
+ const [showCannotDeleteDialogModal, setCannotDeleteDialogModal] = useState(false)
25
+ const { push } = useHistory()
26
+
27
+ async function handleDelete() {
28
+ setDeleting(true)
29
+ try {
30
+ await deleteComponent(payload)
31
+ toast.success('Successfully deleted')
32
+ toggleConfirmation(false)
33
+ if (redirectTo) {
34
+ push(url)
35
+ } else {
36
+ reload()
37
+ }
38
+ if (typeof closeCustomComponent === 'function') {
39
+ closeCustomComponent()
40
+ }
41
+ } catch (serverError) {
42
+ if (serverError instanceof ServerErrors && serverError.code === 500) {
43
+ setCannotDeleteDialogModal(true)
44
+ }
45
+ } finally {
46
+ setDeleting(false)
47
+ }
48
+ }
49
+
50
+ const handleConfirmation = () => {
51
+ setCannotDeleteDialogModal(false)
52
+ toggleConfirmation(false)
53
+ if (typeof closeCustomComponent === 'function') {
54
+ closeCustomComponent()
55
+ }
56
+ }
57
+
58
+ const renderCannotDeleteDialogModal = () => {
59
+ return (
60
+ <ConfirmationDialog className="confirmation-dialog__body--w-360">
61
+ <ConfirmationDialog.Icon src={info} />
62
+ <ConfirmationDialog.Body title={`Cannot delete ${component} '${title}'`} />
63
+ <p className="fs-13 cn-7 ">{confirmationDialogDescription}</p>
64
+ <ConfirmationDialog.ButtonGroup>
65
+ <button type="button" className="cta" onClick={handleConfirmation}>
66
+ Okay
67
+ </button>
68
+ </ConfirmationDialog.ButtonGroup>
69
+ </ConfirmationDialog>
70
+ )
71
+ }
72
+
73
+ const renderDeleteDialog = () => {
74
+ return (
75
+ <DeleteDialog
76
+ title={`Delete ${component} '${title}'`}
77
+ delete={handleDelete}
78
+ closeDelete={() => toggleConfirmation(false)}
79
+ dataTestId="delete-dialog"
80
+ >
81
+ <DeleteDialog.Description>
82
+ <p>Are you sure you want to delete this {configuration ? configuration : component}? </p>
83
+ </DeleteDialog.Description>
84
+ </DeleteDialog>
85
+ )
86
+ }
87
+ return (
88
+ <div>
89
+ {!showCannotDeleteDialogModal && renderDeleteDialog()}
90
+ {showCannotDeleteDialogModal && renderCannotDeleteDialogModal()}
91
+ </div>
92
+ )
93
+ }
94
+
95
+ export default DeleteComponent
@@ -0,0 +1,15 @@
1
+ export interface DeleteComponentProps {
2
+ setDeleting: (boolean) => void
3
+ toggleConfirmation: any
4
+ deleteComponent: (any) => Promise<any>
5
+ title: string
6
+ component: string
7
+ payload: any
8
+ confirmationDialogDescription?: string
9
+ redirectTo?: boolean
10
+ url?: string
11
+ reload?: () => void
12
+ configuration?: string
13
+ dataTestid?: string
14
+ closeCustomComponent?: () => void
15
+ }
@@ -1,53 +1,35 @@
1
- import React from 'react'
2
- import warn from '../../Assets/Img/delete-medium.svg'
1
+ import React from 'react';
2
+ import warn from '../../Assets/Img/warning-medium.svg';
3
3
  import { Progressing } from '../Progressing'
4
- import ConfirmationDialog from './ConfirmationDialog'
5
- import { DeleteDialogProps } from './Types'
4
+ import ConfirmationDialog from './ConfirmationDialog';
5
+ import { DeleteDialogProps } from './Types';
6
6
 
7
7
  export const DeleteDialog: React.FC<DeleteDialogProps> & { Description?: React.FC<any> } = function (props) {
8
- return (
9
- <ConfirmationDialog className="confirmation-dialog__body--w-400">
10
- <ConfirmationDialog.Icon src={warn} />
11
- <ConfirmationDialog.Body title={props.title}>
12
- <div className="fs-13 cn-7 lh-1-54 w-100">
13
- {props.description ? props.description : null}
14
- {props.children}
15
- </div>
16
- </ConfirmationDialog.Body>
17
- <ConfirmationDialog.ButtonGroup>
18
- <div className="flex right">
19
- <button
20
- type="button"
21
- className="cta cancel cta-cd-delete-modal ml-16"
22
- onClick={props.closeDelete}
23
- disabled={props.apiCallInProgress}
24
- data-testid="dialog-cancel"
25
- >
26
- Cancel
27
- </button>
28
- <button
29
- type="button"
30
- className="cta delete cta-cd-delete-modal ml-16"
31
- onClick={() => props.delete()}
32
- disabled={props.apiCallInProgress}
33
- data-testid="dialog-delete"
34
- >
35
- {props.apiCallInProgress ? (
36
- <Progressing />
37
- ) : (
38
- `${props.deletePrefix || ''}${
39
- props.buttonPrimaryText ? props.buttonPrimaryText : 'Delete'
40
- }${props.deletePostfix || ''}`
41
- )}
42
- </button>
43
- </div>
44
- </ConfirmationDialog.ButtonGroup>
45
- </ConfirmationDialog>
46
- )
8
+
9
+ return <ConfirmationDialog className="confirmation-dialog__body--w-400">
10
+ <ConfirmationDialog.Icon src={warn} />
11
+ <ConfirmationDialog.Body title={props.title}>
12
+ <div className="fs-13 cn-7 lh-1-54 w-100">
13
+ {props.description ? props.description : null}
14
+ {props.children}
15
+ </div>
16
+ </ConfirmationDialog.Body>
17
+ <ConfirmationDialog.ButtonGroup>
18
+ <div className="flex right">
19
+ <button type="button" className="cta cancel cta-cd-delete-modal ml-16" onClick={props.closeDelete} disabled={props.apiCallInProgress} data-testid="dialog-cancel">Cancel</button>
20
+ <button type="button" className="cta delete cta-cd-delete-modal ml-16" onClick={() => props.delete()} disabled={props.apiCallInProgress} data-testid="dialog-delete">
21
+ {props.apiCallInProgress ? <Progressing /> : `${props.deletePrefix || ''}Delete${props.deletePostfix || ''}`}
22
+ </button>
23
+ </div>
24
+ </ConfirmationDialog.ButtonGroup>
25
+ </ConfirmationDialog >
47
26
  }
48
27
 
49
28
  function DeleteDialogDescription(props) {
50
- return <>{props.children}</>
29
+
30
+ return <>
31
+ {props.children}
32
+ </>
51
33
  }
52
34
 
53
- DeleteDialog.Description = DeleteDialogDescription
35
+ DeleteDialog.Description = DeleteDialogDescription;
@@ -9,7 +9,6 @@ export interface DeleteDialogProps {
9
9
  deletePostfix?: string
10
10
  apiCallInProgress?: boolean
11
11
  dataTestId?: string
12
- buttonPrimaryText?: string
13
12
  }
14
13
 
15
14
  export interface ForceDeleteDialogType {
@@ -30,7 +30,7 @@ function GenericEmptyState({
30
30
  height={imageType === ImageType.Medium ? '160' : '200'}
31
31
  alt="empty-state"
32
32
  />}
33
- <h4 className="title fw-6 cn-9 mb-8">{title}</h4>
33
+ <h4 className="title fw-6 cn-9 mb-8 mt-20">{title}</h4>
34
34
  {subTitle && <p className="subtitle">{subTitle}</p>}
35
35
  {isButtonAvailable && renderButton()}
36
36
  {children}
@@ -1,48 +1,49 @@
1
- import React, { useEffect, useRef } from 'react'
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react'
2
2
  import { ServerErrors } from './ServerError'
3
3
  import { toast } from 'react-toastify'
4
4
  import * as Sentry from '@sentry/browser'
5
5
  import { toastAccessDenied } from './ToastBody'
6
6
  import { ERROR_EMPTY_SCREEN, TOKEN_COOKIE_NAME } from './Constants'
7
+ import { ReactComponent as FormError } from '../Assets/Icon/ic-warning.svg'
7
8
 
8
9
  toast.configure({
9
- autoClose: 3000,
10
- hideProgressBar: true,
11
- pauseOnHover: true,
12
- pauseOnFocusLoss: true,
13
- closeOnClick: false,
14
- newestOnTop: true,
15
- toastClassName: 'devtron-toast',
16
- bodyClassName: 'devtron-toast__body',
10
+ autoClose: 3000,
11
+ hideProgressBar: true,
12
+ pauseOnHover: true,
13
+ pauseOnFocusLoss: true,
14
+ closeOnClick: false,
15
+ newestOnTop: true,
16
+ toastClassName: 'devtron-toast',
17
+ bodyClassName: 'devtron-toast__body',
17
18
  })
18
19
 
19
20
  export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
20
- if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
21
- serverError.errors.map(({ userMessage, internalMessage }) => {
22
- if (
23
- serverError.code === 403 &&
24
- (userMessage === ERROR_EMPTY_SCREEN.UNAUTHORIZED || userMessage === ERROR_EMPTY_SCREEN.FORBIDDEN)
25
- ) {
26
- if (!hideAccessError) {
27
- toastAccessDenied()
28
- }
29
- } else {
30
- toast.error(userMessage || internalMessage)
31
- }
32
- })
33
- } else {
34
- if (serverError.code !== 403 && serverError.code !== 408) {
35
- Sentry.captureException(serverError)
36
- }
37
-
38
- if (showToastOnUnknownError) {
39
- if (serverError.message) {
40
- toast.error(serverError.message)
41
- } else {
42
- toast.error('Some Error Occurred')
43
- }
44
- }
45
- }
21
+ if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
22
+ serverError.errors.map(({ userMessage, internalMessage }) => {
23
+ if (
24
+ serverError.code === 403 &&
25
+ (userMessage === ERROR_EMPTY_SCREEN.UNAUTHORIZED || userMessage === ERROR_EMPTY_SCREEN.FORBIDDEN)
26
+ ) {
27
+ if (!hideAccessError) {
28
+ toastAccessDenied()
29
+ }
30
+ } else {
31
+ toast.error(userMessage || internalMessage)
32
+ }
33
+ })
34
+ } else {
35
+ if (serverError.code !== 403 && serverError.code !== 408) {
36
+ Sentry.captureException(serverError)
37
+ }
38
+
39
+ if (showToastOnUnknownError) {
40
+ if (serverError.message) {
41
+ toast.error(serverError.message)
42
+ } else {
43
+ toast.error('Some Error Occurred')
44
+ }
45
+ }
46
+ }
46
47
  }
47
48
 
48
49
  interface ConditionalWrapper<T> {
@@ -167,4 +168,166 @@ export function getLoginInfo() {
167
168
  return null
168
169
  }
169
170
  }
170
- }
171
+ }
172
+
173
+ export function useForm(stateSchema, validationSchema = {}, callback) {
174
+ const [state, setState] = useState(stateSchema)
175
+ const [disable, setDisable] = useState(true)
176
+ const [isDirty, setIsDirty] = useState(false)
177
+
178
+ // Disable button in initial render.
179
+ useEffect(() => {
180
+ setDisable(true)
181
+ }, [])
182
+
183
+ // For every changed in our state this will be fired
184
+ // To be able to disable the button
185
+ useEffect(() => {
186
+ if (isDirty) {
187
+ setDisable(validateState(state))
188
+ }
189
+ }, [state, isDirty])
190
+
191
+ // Used to disable submit button if there's an error in state
192
+ // or the required field in state has no value.
193
+ // Wrapped in useCallback to cached the function to avoid intensive memory leaked
194
+ // in every re-render in component
195
+ const validateState = useCallback(
196
+ (state) => {
197
+ //check errors in all fields
198
+ const hasErrorInState = Object.keys(validationSchema).some((key) => {
199
+ const isInputFieldRequired = validationSchema[key].required
200
+ const stateValue = state[key].value // state value
201
+ const stateError = state[key].error // state error
202
+ return (isInputFieldRequired && !stateValue) || stateError
203
+ })
204
+ return hasErrorInState
205
+ },
206
+ [state, validationSchema],
207
+ )
208
+
209
+ function validateField(name, value): string | string[] {
210
+ if (validationSchema[name].required) {
211
+ if (!value) {
212
+ return 'This is a required field.'
213
+ }
214
+ }
215
+
216
+ function _validateSingleValidator(validator, value) {
217
+ if (value && !validator.regex.test(value)) {
218
+ return false
219
+ }
220
+ return true
221
+ }
222
+
223
+ // single validator
224
+ let _validator = validationSchema[name].validator
225
+ if (_validator && typeof _validator === 'object') {
226
+ if (!_validateSingleValidator(_validator, value)) {
227
+ return _validator.error
228
+ }
229
+ }
230
+
231
+ // multiple validators
232
+ let _validators = validationSchema[name].validators
233
+ if (_validators && typeof _validators === 'object' && Array.isArray(_validators)) {
234
+ let errors = []
235
+ _validators.forEach((_validator) => {
236
+ if (!_validateSingleValidator(_validator, value)) {
237
+ errors.push(_validator.error)
238
+ }
239
+ })
240
+ if (errors.length > 0) {
241
+ return errors
242
+ }
243
+ }
244
+
245
+ return ''
246
+ }
247
+
248
+ const handleOnChange = useCallback(
249
+ (event) => {
250
+ setIsDirty(true)
251
+
252
+ const { name, value } = event.target
253
+ let error = validateField(name, value)
254
+ setState((prevState) => ({
255
+ ...prevState,
256
+ [name]: { value, error },
257
+ }))
258
+ },
259
+ [validationSchema],
260
+ )
261
+
262
+ const handleOnSubmit = (event) => {
263
+ event.preventDefault()
264
+ const newState = Object.keys(validationSchema).reduce((agg, curr) => {
265
+ agg[curr] = { ...state[curr], error: validateField(curr, state[curr].value) }
266
+ return agg
267
+ }, state)
268
+ if (!validateState(newState)) {
269
+ callback(state)
270
+ } else {
271
+ setState({ ...newState })
272
+ }
273
+ }
274
+ return { state, disable, handleOnChange, handleOnSubmit }
275
+ }
276
+
277
+ function handleError(error: any): any[] {
278
+ if (!error) {
279
+ return []
280
+ }
281
+
282
+ if (!Array.isArray(error)) {
283
+ return [error]
284
+ }
285
+
286
+ return error
287
+ }
288
+
289
+ export function CustomInput({
290
+ name,
291
+ value,
292
+ error,
293
+ onChange,
294
+ onBlur = (e) => {},
295
+ onFocus = (e) => {},
296
+ label,
297
+ type = 'text',
298
+ disabled = false,
299
+ autoComplete = 'off',
300
+ labelClassName = '',
301
+ placeholder = '',
302
+ tabIndex = 1,
303
+ dataTestid = '',
304
+ }) {
305
+ return (
306
+ <div className="flex column left top">
307
+ <label className={`form__label ${labelClassName}`}>{label}</label>
308
+ <input
309
+ data-testid={dataTestid}
310
+ type={type}
311
+ name={name}
312
+ autoComplete="off"
313
+ className="form__input"
314
+ onChange={(e) => {
315
+ e.persist()
316
+ onChange(e)
317
+ }}
318
+ onBlur={onBlur}
319
+ onFocus={onFocus}
320
+ placeholder={placeholder}
321
+ value={value}
322
+ disabled={disabled}
323
+ tabIndex={tabIndex}
324
+ />
325
+ {handleError(error).map((err) => (
326
+ <div className="form__error">
327
+ <FormError className="form__icon form__icon--error" />
328
+ {err}
329
+ </div>
330
+ ))}
331
+ </div>
332
+ )
333
+ }
@@ -1,16 +1,13 @@
1
- import React, { useRef, useState } from 'react'
1
+ import React, { useRef } from 'react'
2
2
  import Tippy from '@tippyjs/react'
3
3
  import { ReactComponent as CloseIcon } from '../Assets/Icon/ic-cross.svg'
4
- import { ReactComponent as QuestionFilled } from '../Assets/Icon/ic-help.svg'
5
4
  import 'tippy.js/animations/shift-toward-subtle.css'
6
5
  import { TippyCustomizedProps, TippyTheme } from './Types'
7
- import { not } from './Helper'
8
6
 
9
7
  // This component will handle some of the new tippy designs and interactions
10
8
  // So this can be updated to support further for new features or interactions
11
9
  export function TippyCustomized(props: TippyCustomizedProps) {
12
10
  const tippyRef = useRef(null)
13
- const [showHeadingInfo, setShowHeadingInfo] = useState(false)
14
11
  const isWhiteTheme = props.theme === TippyTheme.white
15
12
 
16
13
  const onTippyMount = (tippyInstance) => {
@@ -27,7 +24,6 @@ export function TippyCustomized(props: TippyCustomizedProps) {
27
24
  props.onClose()
28
25
  }
29
26
  }
30
- setShowHeadingInfo(false)
31
27
  }
32
28
 
33
29
  const closeOnEsc = (e) => {
@@ -36,10 +32,6 @@ export function TippyCustomized(props: TippyCustomizedProps) {
36
32
  }
37
33
  }
38
34
 
39
- const toggleHeadingInfo = (e) => {
40
- setShowHeadingInfo(not)
41
- }
42
-
43
35
  const getTippyContent = () => {
44
36
  const {
45
37
  Icon,
@@ -48,7 +40,6 @@ export function TippyCustomized(props: TippyCustomizedProps) {
48
40
  iconSize,
49
41
  onImageLoadError,
50
42
  heading,
51
- headingInfo,
52
43
  infoTextHeading,
53
44
  infoText,
54
45
  showCloseButton,
@@ -67,7 +58,7 @@ export function TippyCustomized(props: TippyCustomizedProps) {
67
58
  <img
68
59
  className={`icon-dim-${iconSize || 20} mr-6 ${iconClass || ''}`}
69
60
  src={iconPath}
70
- alt={heading}
61
+ alt="Heading"
71
62
  onError={onImageLoadError}
72
63
  />
73
64
  ) : (
@@ -78,11 +69,6 @@ export function TippyCustomized(props: TippyCustomizedProps) {
78
69
  )
79
70
  )}
80
71
  {heading && <span className={`fs-14 fw-6 lh-20 ${showCloseButton ? 'mr-6' : ''}`}>{heading}</span>}
81
- {headingInfo && (
82
- <div className="icon-dim-20 cursor" onClick={toggleHeadingInfo}>
83
- <QuestionFilled className="icon-dim-20" />
84
- </div>
85
- )}
86
72
  {showCloseButton && (
87
73
  <div className="icon-dim-16 ml-auto">
88
74
  <CloseIcon
@@ -92,18 +78,6 @@ export function TippyCustomized(props: TippyCustomizedProps) {
92
78
  </div>
93
79
  )}
94
80
  </div>
95
- {showHeadingInfo && (
96
- <div
97
- className={`flex left bcv-1 fs-13 fw-4 lh-20 pt-8 pb-8 ${
98
- isWhiteTheme ? 'pl-12 pr-12' : 'pl-20 pr-20'
99
- }`}
100
- >
101
- <div className="icon-dim-20 mr-8">
102
- <QuestionFilled className="icon-dim-20 fcv-5" />
103
- </div>
104
- <div className="dc__word-break dc__hyphens-auto">{headingInfo}</div>
105
- </div>
106
- )}
107
81
  {infoTextHeading && (
108
82
  <div
109
83
  className={`dc__word-break dc__hyphens-auto fs-14 fw-6 lh-20 ${
@@ -1,6 +1,5 @@
1
1
  import { ReactNode, CSSProperties } from 'react'
2
2
  import { Placement } from 'tippy.js'
3
- import { VariableType } from './CIPipeline.Types'
4
3
  export interface ResponseType {
5
4
  code: number
6
5
  status: string
@@ -52,8 +51,7 @@ export interface CheckboxProps {
52
51
  export interface TippyCustomizedProps {
53
52
  theme: TippyTheme
54
53
  visible?: boolean
55
- heading?: string
56
- headingInfo?: string
54
+ heading?: ReactNode | string
57
55
  infoTextHeading?: string
58
56
  placement: Placement
59
57
  className?: string
@@ -259,7 +257,8 @@ export interface CommonNodeAttr {
259
257
  approvalUsers?: string[]
260
258
  userApprovalConfig?: UserApprovalConfigType
261
259
  requestedUserId?: number
262
- showPluginWarning?: boolean
260
+ helmPackageName?: string
261
+ isVirtualEnvironment?: boolean
263
262
  }
264
263
 
265
264
 
@@ -320,29 +319,3 @@ export interface CDMaterialResponseType {
320
319
  userApprovalConfig: UserApprovalConfigType
321
320
  requestedUserId: number
322
321
  }
323
-
324
- export enum PluginRequiredStageKeys {
325
- PRE_CI = 'PRE_CI',
326
- POST_CI = 'POST_CI',
327
- PRE_OR_POST_CI = 'PRE_OR_POST_CI'
328
- }
329
-
330
- export enum PluginRequiredStage {
331
- PRE_CI = 'preBuildStage',
332
- POST_CI = 'postBuildStage',
333
- PRE_OR_POST_CI = 'PRE_OR_POST_CI'
334
- }
335
- export interface MandatoryPluginDetailType {
336
- id: number
337
- name: string
338
- description?: string;
339
- requiredIn: PluginRequiredStage
340
- applied?: boolean
341
- inputVariables?: VariableType[]
342
- outputVariables?: VariableType[]
343
- }
344
- export interface MandatoryPluginDataType {
345
- pluginData: MandatoryPluginDetailType[]
346
- isValidPre: boolean
347
- isValidPost: boolean
348
- }
@@ -29,4 +29,4 @@ export { default as GenericEmptyState } from './EmptyState/GenericEmptyState'
29
29
  export { default as Toggle } from './Toggle/Toggle'
30
30
  export { default as ScanVulnerabilitiesTable } from './Security/ScanVulnerabilitiesTable'
31
31
  export { default as StyledRadioGroup } from './RadioGroup/RadioGroup'
32
- export * from './CIPipeline.Types'
32
+ export { default as DeleteComponent } from './DeleteComponentModal/DeleteComponent'