diff --git a/src/Common/DeleteComponentModal/DeleteComponent.tsx b/src/Common/DeleteComponentModal/DeleteComponent.tsx
index d139138cf..f3f22d931 100644
--- a/src/Common/DeleteComponentModal/DeleteComponent.tsx
+++ b/src/Common/DeleteComponentModal/DeleteComponent.tsx
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-import React, { useState } from 'react'
+import { useState } from 'react'
import { useHistory } from 'react-router-dom'
-import { toast } from 'react-toastify'
import info from '../../Assets/Icon/ic-info-filled.svg'
import { ConfirmationDialog, DeleteDialog } from '../Dialogs'
import { ServerErrors } from '../ServerError'
import { DeleteComponentProps } from './types'
+import { ToastManager, ToastVariantType } from '@Shared/Services'
+import { showError } from '@Common/Helper'
const DeleteComponent = ({
setDeleting,
@@ -36,14 +37,19 @@ const DeleteComponent = ({
configuration = '',
closeCustomComponent,
}: DeleteComponentProps) => {
+ const [isDeleting, setIsDeleting] = useState(false)
const [showCannotDeleteDialogModal, setCannotDeleteDialogModal] = useState(false)
const { push } = useHistory()
async function handleDelete() {
- setDeleting(true)
+ setDeleting?.(true)
+ setIsDeleting(true)
try {
await deleteComponent(payload)
- toast.success('Successfully deleted')
+ ToastManager.showToast({
+ variant: ToastVariantType.success,
+ description: 'Successfully deleted',
+ })
toggleConfirmation(false)
if (redirectTo) {
push(url)
@@ -56,9 +62,12 @@ const DeleteComponent = ({
} catch (serverError) {
if (serverError instanceof ServerErrors && serverError.code === 500) {
setCannotDeleteDialogModal(true)
+ } else {
+ showError(serverError)
}
} finally {
- setDeleting(false)
+ setDeleting?.(false)
+ setIsDeleting(false)
}
}
@@ -89,6 +98,7 @@ const DeleteComponent = ({
delete={handleDelete}
closeDelete={() => toggleConfirmation(false)}
dataTestId="delete-dialog"
+ apiCallInProgress={isDeleting}
>
Are you sure you want to delete this {configuration || component}?
diff --git a/src/Common/DeleteComponentModal/types.ts b/src/Common/DeleteComponentModal/types.ts
index 6ec69083e..7a1fceef4 100644
--- a/src/Common/DeleteComponentModal/types.ts
+++ b/src/Common/DeleteComponentModal/types.ts
@@ -15,7 +15,10 @@
*/
export interface DeleteComponentProps {
- setDeleting: (boolean) => void
+ /**
+ * @deprecated - Delete component internally handles loading for the `Delete Button`.
+ */
+ setDeleting?: (boolean) => void
toggleConfirmation: any
deleteComponent: (any) => Promise
title: string
diff --git a/src/Common/Drawer/Drawer.tsx b/src/Common/Drawer/Drawer.tsx
index 8e7a56b6c..b047a5298 100644
--- a/src/Common/Drawer/Drawer.tsx
+++ b/src/Common/Drawer/Drawer.tsx
@@ -41,6 +41,7 @@ export const Drawer = ({
maxWidth,
parentClassName,
onEscape,
+ onClose,
}: drawerInterface) => {
const drawerRef = useRef(null)
useEffect(() => {
@@ -61,7 +62,7 @@ export const Drawer = ({
style['--height'] = height
}
return (
-
+
diff --git a/src/Common/GenericDescription/GenericDescription.tsx b/src/Common/GenericDescription/GenericDescription.tsx
index 0c2e727a3..75c7f9399 100644
--- a/src/Common/GenericDescription/GenericDescription.tsx
+++ b/src/Common/GenericDescription/GenericDescription.tsx
@@ -18,7 +18,6 @@ import { useState, useRef, useEffect } from 'react'
import Tippy from '@tippyjs/react'
import ReactMde from 'react-mde'
import 'react-mde/lib/styles/css/react-mde-all.css'
-import { toast } from 'react-toastify'
import moment from 'moment'
import Markdown from '../Markdown/MarkDown'
import { DATE_TIME_FORMATS, deepEqual, showError } from '..'
@@ -31,7 +30,7 @@ import {
MARKDOWN_EDITOR_COMMAND_TITLE,
MARKDOWN_EDITOR_COMMAND_ICON_TIPPY_CONTENT,
} from '../Markdown/constant'
-import { ButtonWithLoader } from '../../Shared'
+import { ButtonWithLoader, ToastManager, ToastVariantType } from '../../Shared'
import { ReactComponent as HeaderIcon } from '../../Assets/Icon/ic-header.svg'
import { ReactComponent as BoldIcon } from '../../Assets/Icon/ic-bold.svg'
import { ReactComponent as ItalicIcon } from '../../Assets/Icon/ic-italic.svg'
@@ -73,7 +72,10 @@ const GenericDescription = ({
const validateDescriptionText = (description: string): boolean => {
let isValid = true
if (description.length === 0) {
- toast.error(DESCRIPTION_EMPTY_ERROR_MSG)
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: DESCRIPTION_EMPTY_ERROR_MSG,
+ })
isValid = false
}
return isValid
diff --git a/src/Common/Helper.tsx b/src/Common/Helper.tsx
index 86e3207a3..46dc671b0 100644
--- a/src/Common/Helper.tsx
+++ b/src/Common/Helper.tsx
@@ -21,26 +21,27 @@ import { components } from 'react-select'
import * as Sentry from '@sentry/browser'
import moment from 'moment'
import { useLocation } from 'react-router-dom'
-import { toast } from 'react-toastify'
import YAML from 'yaml'
-import { ERROR_EMPTY_SCREEN, SortingOrder, EXCLUDED_FALSY_VALUES, DISCORD_LINK, ZERO_TIME_STRING } from './Constants'
+import { deepEquals } from '@rjsf/utils'
+import {
+ ERROR_EMPTY_SCREEN,
+ SortingOrder,
+ EXCLUDED_FALSY_VALUES,
+ DISCORD_LINK,
+ ZERO_TIME_STRING,
+ TOAST_ACCESS_DENIED,
+} from './Constants'
import { ServerErrors } from './ServerError'
-import { toastAccessDenied } from './ToastBody'
import { AsyncOptions, AsyncState, UseSearchString } from './Types'
-import { scrollableInterface, DATE_TIME_FORMAT_STRING } from '../Shared'
+import {
+ scrollableInterface,
+ DATE_TIME_FORMAT_STRING,
+ ToastManager,
+ ToastVariantType,
+ versionComparatorBySortOrder,
+} from '../Shared'
import { ReactComponent as ArrowDown } from '../Assets/Icon/ic-chevron-down.svg'
-toast.configure({
- autoClose: 3000,
- hideProgressBar: true,
- pauseOnHover: true,
- pauseOnFocusLoss: true,
- closeOnClick: false,
- newestOnTop: true,
- toastClassName: 'devtron-toast',
- bodyClassName: 'devtron-toast__body',
-})
-
export function showError(serverError, showToastOnUnknownError = true, hideAccessError = false) {
if (serverError instanceof ServerErrors && Array.isArray(serverError.errors)) {
serverError.errors.map(({ userMessage, internalMessage }) => {
@@ -49,10 +50,16 @@ export function showError(serverError, showToastOnUnknownError = true, hideAcces
(userMessage === ERROR_EMPTY_SCREEN.UNAUTHORIZED || userMessage === ERROR_EMPTY_SCREEN.FORBIDDEN)
) {
if (!hideAccessError) {
- toastAccessDenied()
+ ToastManager.showToast({
+ variant: ToastVariantType.notAuthorized,
+ description: TOAST_ACCESS_DENIED.SUBTITLE,
+ })
}
} else {
- toast.error(userMessage || internalMessage)
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: userMessage || internalMessage,
+ })
}
})
} else {
@@ -62,9 +69,15 @@ export function showError(serverError, showToastOnUnknownError = true, hideAcces
if (showToastOnUnknownError) {
if (serverError.message) {
- toast.error(serverError.message)
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: serverError.message,
+ })
} else {
- toast.error('Some Error Occurred')
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: 'Some Error Occurred',
+ })
}
}
}
@@ -193,110 +206,6 @@ export function getCookie(sKey) {
)
}
-export function useForm(stateSchema, validationSchema = {}, callback) {
- const [state, setState] = useState(stateSchema)
- const [disable, setDisable] = useState(true)
- const [isDirty, setIsDirty] = useState(false)
-
- // Disable button in initial render.
- useEffect(() => {
- setDisable(true)
- }, [])
-
- // For every changed in our state this will be fired
- // To be able to disable the button
- useEffect(() => {
- if (isDirty) {
- setDisable(validateState(state))
- }
- }, [state, isDirty])
-
- // Used to disable submit button if there's an error in state
- // or the required field in state has no value.
- // Wrapped in useCallback to cached the function to avoid intensive memory leaked
- // in every re-render in component
- const validateState = useCallback(
- (state) => {
- // check errors in all fields
- const hasErrorInState = Object.keys(validationSchema).some((key) => {
- const isInputFieldRequired = validationSchema[key].required
- const stateValue = state[key].value // state value
- const stateError = state[key].error // state error
- return (isInputFieldRequired && !stateValue) || stateError
- })
- return hasErrorInState
- },
- [state, validationSchema],
- )
-
- function validateField(name, value): string | string[] {
- if (validationSchema[name].required) {
- if (!value) {
- return 'This is a required field.'
- }
- }
-
- function _validateSingleValidator(validator, value) {
- if (value && !validator.regex.test(value)) {
- return false
- }
- return true
- }
-
- // single validator
- const _validator = validationSchema[name].validator
- if (_validator && typeof _validator === 'object') {
- if (!_validateSingleValidator(_validator, value)) {
- return _validator.error
- }
- }
-
- // multiple validators
- const _validators = validationSchema[name].validators
- if (_validators && typeof _validators === 'object' && Array.isArray(_validators)) {
- const errors = []
- _validators.forEach((_validator) => {
- if (!_validateSingleValidator(_validator, value)) {
- errors.push(_validator.error)
- }
- })
- if (errors.length > 0) {
- return errors
- }
- }
-
- return ''
- }
-
- const handleOnChange = useCallback(
- (event) => {
- setIsDirty(true)
-
- const { name, value } = event.target
- const error = validateField(name, value)
- setState((prevState) => ({
- ...prevState,
- [name]: { value, error },
- }))
- },
- [validationSchema],
- )
-
- const handleOnSubmit = (event) => {
- event.preventDefault()
- const newState = Object.keys(validationSchema).reduce((agg, curr) => {
- agg[curr] = { ...state[curr], error: validateField(curr, state[curr].value) }
- return agg
- }, state)
- if (!validateState(newState)) {
- callback(state)
- } else {
- setState({ ...newState })
- }
- }
- return { state, disable, handleOnChange, handleOnSubmit }
-}
-
export function handleUTCTime(ts: string, isRelativeTime = false) {
let timestamp = ''
try {
@@ -465,7 +374,10 @@ export function copyToClipboard(str, callback = noop) {
callback()
})
.catch(() => {
- toast.error('Failed to copy to clipboard')
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: 'Failed to copy to clipboard',
+ })
})
} else {
unsecureCopyToClipboard(str, callback)
@@ -618,6 +530,7 @@ export const getFilteredChartVersions = (charts, selectedChartType) =>
// Filter chart versions based on selected chart type
charts
.filter((item) => item?.chartType === selectedChartType.value)
+ .sort((a, b) => versionComparatorBySortOrder(a?.chartVersion, b?.chartVersion))
.map((item) => ({
value: item?.chartVersion,
label: item?.chartVersion,
@@ -816,28 +729,7 @@ export const compareObjectLength = (objA: any, objB: any): boolean => {
* Return deep copy of the object
*/
export function deepEqual(configA: any, configB: any): boolean {
- try {
- if (configA === configB) {
- return true
- }
- if ((configA && !configB) || (!configA && configB) || !compareObjectLength(configA, configB)) {
- return false
- }
- let isEqual = true
- for (const idx in configA) {
- if (!isEqual) {
- break
- } else if (typeof configA[idx] === 'object' && typeof configB[idx] === 'object') {
- isEqual = deepEqual(configA[idx], configB[idx])
- } else if (configA[idx] !== configB[idx]) {
- isEqual = false
- }
- }
- return isEqual
- } catch (err) {
- showError(err)
- return true
- }
+ return deepEquals(configA, configB)
}
export function shallowEqual(objA, objB) {
diff --git a/src/Common/ImageTags.tsx b/src/Common/ImageTags.tsx
index 3610d5654..479bd31ce 100644
--- a/src/Common/ImageTags.tsx
+++ b/src/Common/ImageTags.tsx
@@ -15,7 +15,6 @@
*/
import { useEffect, useRef, useState } from 'react'
-import { toast } from 'react-toastify'
import Tippy from '@tippyjs/react'
import { ReactComponent as Add } from '../Assets/Icon/ic-add.svg'
import { ReactComponent as Close } from '../Assets/Icon/ic-cross.svg'
@@ -30,7 +29,7 @@ import { ImageButtonType, ImageTaggingContainerType, ReleaseTag } from './ImageT
import { showError, stopPropagation } from './Helper'
import { setImageTags } from './Common.service'
import { Progressing } from './Progressing'
-import { InfoIconTippy } from '../Shared'
+import { InfoIconTippy, ToastManager, ToastVariantType } from '../Shared'
export const ImageTagsContainer = ({
// Setting it to zero in case of external pipeline
@@ -257,7 +256,10 @@ export const ImageTagsContainer = ({
.catch((err) => {
// Fix toast message
if (err.errors?.[0]?.userMessage?.appReleaseTags?.length) {
- toast.error(err.errors?.[0]?.internalMessage)
+ ToastManager.showToast({
+ variant: ToastVariantType.error,
+ description: err.errors?.[0]?.internalMessage,
+ })
errorStateHandling(err.errors)
} else {
showError(err)
@@ -395,7 +397,7 @@ export const ImageTagsContainer = ({
{showTagsWarning && (