Skip to content

Commit 0cc3333

Browse files
committed
Merge branch 'develop' of https://github.com/devtron-labs/devtron-fe-common-lib into feat/hibernation-patch
2 parents 7a76975 + 0ec38c1 commit 0cc3333

File tree

16 files changed

+188
-59
lines changed

16 files changed

+188
-59
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@devtron-labs/devtron-fe-common-lib",
3-
"version": "1.6.4",
3+
"version": "1.6.6",
44
"description": "Supporting common component library",
55
"type": "module",
66
"main": "dist/index.js",
@@ -70,7 +70,7 @@
7070
"sharp": "^0.33.5",
7171
"svgo": "^3.3.2",
7272
"typescript": "5.5.4",
73-
"vite": "5.4.11",
73+
"vite": "5.4.14",
7474
"vite-plugin-dts": "4.0.3",
7575
"vite-plugin-image-optimizer": "^1.1.8",
7676
"vite-plugin-lib-inject-css": "2.1.1",
@@ -127,7 +127,7 @@
127127
"react-dom": "^17.0.2"
128128
},
129129
"vite-plugin-svgr": {
130-
"vite": "5.4.11"
130+
"vite": "5.4.14"
131131
},
132132
"react-virtualized-sticky-tree": {
133133
"react": "^17.0.2",

src/Common/Hooks/useUrlFilters/types.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,20 @@ export interface UseUrlFiltersProps<T, K> {
2525
* Callback function for parsing the search params
2626
*/
2727
parseSearchParams?: (searchParams: URLSearchParams) => K
28+
/**
29+
* Local storage key to store the filters
30+
*/
2831
localStorageKey?: `${string}__${string}`
32+
/**
33+
* Method to be used for redirection in case of filter update
34+
*
35+
* @default 'replace'
36+
*/
37+
redirectionMethod?: 'replace' | 'push'
2938
}
3039

40+
export type UpdateSearchParamsOptionsType<T, K = unknown> = Partial<Pick<UseUrlFiltersProps<T, K>, 'redirectionMethod'>>
41+
3142
export type UseUrlFiltersReturnType<T, K = unknown> = K & {
3243
/**
3344
* Currently applied page size
@@ -74,5 +85,5 @@ export type UseUrlFiltersReturnType<T, K = unknown> = K & {
7485
/**
7586
* Update the search params with the passed object
7687
*/
77-
updateSearchParams: (paramsToSerialize: Partial<K>) => void
88+
updateSearchParams: (paramsToSerialize: Partial<K>, options?: UpdateSearchParamsOptionsType<T, K>) => void
7889
}

src/Common/Hooks/useUrlFilters/useUrlFilters.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { useHistory, useLocation } from 'react-router-dom'
1919
import { getUrlWithSearchParams } from '@Common/Helper'
2020
import { DEFAULT_BASE_PAGE_SIZE, EXCLUDED_FALSY_VALUES, SortingOrder } from '../../Constants'
2121
import { DEFAULT_PAGE_NUMBER, URL_FILTER_KEYS } from './constants'
22-
import { UseUrlFiltersProps, UseUrlFiltersReturnType } from './types'
22+
import { UpdateSearchParamsOptionsType, UseUrlFiltersProps, UseUrlFiltersReturnType } from './types'
2323
import { setItemInLocalStorageIfKeyExists } from './utils'
2424

2525
const { PAGE_SIZE, PAGE_NUMBER, SEARCH_KEY, SORT_BY, SORT_ORDER } = URL_FILTER_KEYS
@@ -45,6 +45,7 @@ const useUrlFilters = <T = string, K = unknown>({
4545
initialSortKey,
4646
parseSearchParams,
4747
localStorageKey,
48+
redirectionMethod = 'replace',
4849
}: UseUrlFiltersProps<T, K> = {}): UseUrlFiltersReturnType<T, K> => {
4950
const location = useLocation()
5051
const history = useHistory()
@@ -62,6 +63,7 @@ const useUrlFilters = <T = string, K = unknown>({
6263
const localSearchString = getUrlWithSearchParams('', JSON.parse(localStorageValue))
6364
const localSearchParams = new URLSearchParams(localSearchString.split('?')[1] ?? '')
6465

66+
// This would remain replace since the initial value is being set from local storage
6567
history.replace({ search: localSearchParams.toString() })
6668
return localSearchParams
6769
} catch {
@@ -118,23 +120,34 @@ const useUrlFilters = <T = string, K = unknown>({
118120
*/
119121
const offset = pageSize * (pageNumber - 1)
120122

123+
const updateFiltersInUrl = (updatedSearchString: string, options: UpdateSearchParamsOptionsType<T, K>) => {
124+
const params = { search: updatedSearchString }
125+
const methodForRedirection = options.redirectionMethod ?? redirectionMethod
126+
127+
if (methodForRedirection === 'push') {
128+
history.push(params)
129+
} else {
130+
history.replace(params)
131+
}
132+
}
133+
121134
/**
122135
* Update and replace the search params in the URL.
123136
*
124137
* Note: Currently only primitive data types are supported
125138
*/
126-
const _updateSearchParam = (key: string, value) => {
139+
const _updateSearchParam = (key: string, value: unknown, options: UpdateSearchParamsOptionsType<T, K> = {}) => {
127140
searchParams.set(key, String(value))
128-
history.replace({ search: searchParams.toString() })
141+
updateFiltersInUrl(searchParams.toString(), options)
129142
}
130143

131-
const _resetPageNumber = () => {
144+
const _resetPageNumber = (options: UpdateSearchParamsOptionsType<T, K> = {}) => {
132145
if (pageNumber !== DEFAULT_PAGE_NUMBER) {
133-
_updateSearchParam(PAGE_NUMBER, DEFAULT_PAGE_NUMBER)
146+
_updateSearchParam(PAGE_NUMBER, DEFAULT_PAGE_NUMBER, options)
134147
return
135148
}
136149

137-
history.replace({ search: searchParams.toString() })
150+
updateFiltersInUrl(searchParams.toString(), options)
138151
}
139152

140153
const changePage = (page: number) => {
@@ -167,13 +180,13 @@ const useUrlFilters = <T = string, K = unknown>({
167180
}
168181

169182
const clearFilters = () => {
170-
history.replace({ search: '' })
183+
updateFiltersInUrl('', {})
171184
if (localStorageKey) {
172185
localStorage.removeItem(localStorageKey)
173186
}
174187
}
175188

176-
const updateSearchParams = (paramsToSerialize: Partial<K>) => {
189+
const updateSearchParams = (paramsToSerialize: Partial<K>, options: UpdateSearchParamsOptionsType<T, K> = {}) => {
177190
Object.keys(paramsToSerialize).forEach((key) => {
178191
if (!EXCLUDED_FALSY_VALUES.includes(paramsToSerialize[key])) {
179192
if (Array.isArray(paramsToSerialize[key])) {
@@ -191,7 +204,7 @@ const useUrlFilters = <T = string, K = unknown>({
191204
// Skipping primary params => pageSize, pageNumber, searchKey, sortBy, sortOrder
192205
setItemInLocalStorageIfKeyExists(localStorageKey, JSON.stringify(getParsedSearchParams(searchParams)))
193206
// Not replacing the params as it is being done by _resetPageNumber
194-
_resetPageNumber()
207+
_resetPageNumber(options)
195208
}
196209

197210
return {

src/Common/RJSF/Form.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,17 @@
1616

1717
import { forwardRef } from 'react'
1818
import RJSF from '@rjsf/core'
19-
import RJSFValidator from '@rjsf/validator-ajv8'
2019

2120
import { templates, widgets } from './config'
2221
import { FormProps } from './types'
2322
import { translateString } from './utils'
23+
import { SCHEMA_07_VALIDATOR_STRICT } from '@Shared/validations'
2424
import './rjsfForm.scss'
2525

2626
// Need to use this way because the default import was not working as expected
2727
// The default import resolves to an object intead of a function
2828
const Form = RJSF
29-
const validator = RJSFValidator
29+
const validator = SCHEMA_07_VALIDATOR_STRICT
3030

3131
export const RJSFForm = forwardRef((props: FormProps, ref: FormProps['ref']) => (
3232
<Form

src/Common/SortableTableHeaderCell/SortableTableHeaderCell.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const SortableTableHeaderCell = ({
7676
if (isSorted) {
7777
return (
7878
<SortArrowDown
79-
className={`icon-dim-12 mw-12 scn-7 dc__no-shrink dc__transition--transform ${sortOrder === SortingOrder.DESC ? 'dc__flip-180' : ''}`}
79+
className={`icon-dim-12 mw-12 scn-7 dc__no-shrink dc__transition--transform ${sortOrder !== SortingOrder.DESC ? 'dc__flip-180' : ''}`}
8080
/>
8181
)
8282
}
@@ -99,6 +99,7 @@ const SortableTableHeaderCell = ({
9999
onClick={isSortable ? triggerSorting : noop}
100100
disabled={disabled}
101101
tabIndex={disabled || !isSortable ? -1 : 0}
102+
data-testid={title}
102103
>
103104
<Tooltip showOnTruncate={showTippyOnTruncate} content={title}>
104105
<span className="dc__uppercase dc__truncate">{title}</span>

src/Shared/Components/AnnouncementBanner/AnnouncementBanner.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { useState } from 'react'
18+
import DOMPurify from 'dompurify'
1819
import { InfoColourBar } from '../../../Common'
1920
import { ReactComponent as MegaphoneIcon } from '../../../Assets/Icon/ic-megaphone.svg'
2021
import { ReactComponent as Close } from '../../../Assets/Icon/ic-close.svg'
@@ -26,7 +27,8 @@ interface AnnouncementBannerType {
2627
}
2728

2829
const AnnouncementBanner = ({ parentClassName = '', isCDMaterial = false }: AnnouncementBannerType) => {
29-
const message = window?._env_?.ANNOUNCEMENT_BANNER_MSG
30+
const rawMessage = window?._env_?.ANNOUNCEMENT_BANNER_MSG
31+
const message = rawMessage ? DOMPurify.sanitize(rawMessage) : null
3032
const showAnnouncementBanner = (): boolean => {
3133
const expiryDateOfHidingAnnouncementBanner: string =
3234
typeof Storage !== 'undefined' &&
@@ -61,7 +63,11 @@ const AnnouncementBanner = ({ parentClassName = '', isCDMaterial = false }: Anno
6163

6264
const renderAnnouncementBanner = () => (
6365
<div className="flex dc__gap-4">
64-
<div className="dc__word-break cn-7">{message}</div>
66+
<div
67+
className="dc__word-break cn-7"
68+
// eslint-disable-next-line react/no-danger
69+
dangerouslySetInnerHTML={{ __html: message }}
70+
/>
6571
{isCDMaterial ? null : (
6672
<Close className="icon-dim-20 ml-8 fcn-9" onClick={onClickCloseAnnouncememtBanner} />
6773
)}

src/Shared/Components/Button/Button.component.tsx

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { ButtonHTMLAttributes, PropsWithChildren } from 'react'
17+
import { ButtonHTMLAttributes, MutableRefObject, PropsWithChildren, useEffect, useRef, useState } from 'react'
1818
import { Link, LinkProps } from 'react-router-dom'
1919
import { Progressing } from '@Common/Progressing'
2020
import { Tooltip } from '@Common/Tooltip'
@@ -29,6 +29,7 @@ const ButtonElement = ({
2929
linkProps,
3030
buttonProps,
3131
onClick,
32+
elementRef,
3233
...props
3334
}: PropsWithChildren<
3435
Omit<
@@ -49,6 +50,7 @@ const ButtonElement = ({
4950
className: string
5051
'data-testid': ButtonProps['dataTestId']
5152
'aria-label': ButtonProps['ariaLabel']
53+
elementRef: MutableRefObject<HTMLButtonElement | HTMLAnchorElement>
5254
}
5355
>) => {
5456
if (component === ButtonComponentType.link) {
@@ -59,6 +61,7 @@ const ButtonElement = ({
5961
// Added the specific class to ensure that the link override is applied
6062
className={`${props.className} button__link ${props.disabled ? 'dc__disable-click' : ''}`}
6163
onClick={onClick as LinkProps['onClick']}
64+
ref={elementRef as MutableRefObject<HTMLAnchorElement>}
6265
/>
6366
)
6467
}
@@ -70,6 +73,7 @@ const ButtonElement = ({
7073
// eslint-disable-next-line react/button-has-type
7174
type={buttonProps?.type || 'button'}
7275
onClick={onClick as ButtonHTMLAttributes<HTMLButtonElement>['onClick']}
76+
ref={elementRef as MutableRefObject<HTMLButtonElement>}
7377
/>
7478
)
7579
}
@@ -155,14 +159,46 @@ const Button = ({
155159
showAriaLabelInTippy = true,
156160
fullWidth = false,
157161
isOpacityHoverChild = false,
162+
triggerAutoClickTimestamp,
158163
...props
159164
}: ButtonProps) => {
165+
const [isAutoClickActive, setIsAutoClickActive] = useState(false)
166+
const autoClickTimeoutRef = useRef<number>()
167+
const elementRef = useRef<HTMLButtonElement | HTMLAnchorElement>(null)
168+
160169
const isDisabled = disabled || isLoading
161170
const iconClass = `dc__no-shrink flex dc__fill-available-space ${getButtonIconClassName({
162171
size,
163172
icon,
164173
})}`
165174

175+
useEffect(() => {
176+
if (triggerAutoClickTimestamp) {
177+
// Adding after timeout to ensure the transition is triggered after the button is rendered
178+
setTimeout(() => {
179+
setIsAutoClickActive(true)
180+
181+
autoClickTimeoutRef.current = setTimeout(() => {
182+
elementRef.current.click()
183+
// This is 100ms less than the duration of the transition in CSS
184+
// Make sure to update the same in CSS if this is changed
185+
}, 2400)
186+
}, 100)
187+
}
188+
189+
return () => {
190+
setIsAutoClickActive(false)
191+
clearTimeout(autoClickTimeoutRef.current)
192+
}
193+
}, [triggerAutoClickTimestamp])
194+
195+
const handleClick: ButtonProps['onClick'] = (e) => {
196+
setIsAutoClickActive(false)
197+
clearTimeout(autoClickTimeoutRef.current)
198+
199+
props.onClick?.(e)
200+
}
201+
166202
const getTooltipProps = (): TooltipProps => {
167203
// Show the aria label as tippy only if the action based tippy is not to be shown
168204
if (!showTooltip && showAriaLabelInTippy && icon && ariaLabel) {
@@ -189,9 +225,11 @@ const Button = ({
189225
<ButtonElement
190226
{...props}
191227
disabled={isDisabled}
192-
className={`br-4 flex cursor dc__tab-focus dc__position-rel dc__capitalize ${isOpacityHoverChild ? 'dc__opacity-hover--child' : ''} ${getButtonDerivedClass({ size, variant, style, isLoading, icon })} ${isDisabled ? 'dc__disabled' : ''} ${fullWidth ? 'w-100' : ''}`}
228+
className={`br-4 flex cursor dc__tab-focus dc__position-rel dc__capitalize ${isOpacityHoverChild ? 'dc__opacity-hover--child' : ''} ${getButtonDerivedClass({ size, variant, style, isLoading, icon, isAutoTriggerActive: isAutoClickActive })} ${isDisabled ? 'dc__disabled' : ''} ${fullWidth ? 'w-100' : ''}`}
193229
data-testid={dataTestId}
194230
aria-label={ariaLabel}
231+
elementRef={elementRef}
232+
onClick={handleClick}
195233
>
196234
{icon ? (
197235
<span className={iconClass}>{icon}</span>

0 commit comments

Comments
 (0)