Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
"@codemirror/lint": "6.8.4",
"@codemirror/merge": "^6.10.0",
"@codemirror/search": "6.5.8",
"@datasert/cronjs-parser": "^1.4.0",
"@lezer/highlight": "1.2.1",
"@replit/codemirror-indentation-markers": "6.5.3",
"@replit/codemirror-vscode-keymap": "6.0.2",
Expand All @@ -119,6 +120,7 @@
"ansi_up": "^5.2.1",
"chart.js": "^4.5.0",
"codemirror-json-schema": "0.8.0",
"cronstrue": "^3.9.0",
"dayjs": "^1.11.13",
"fast-json-patch": "^3.1.1",
"focus-trap-react": "^10.3.1",
Expand Down
2 changes: 2 additions & 0 deletions src/Shared/Components/SelectPicker/FilterSelectPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const FilterSelectPicker = ({
options,
menuIsOpen = false,
onMenuClose,
isUserIdentifier,
...props
}: FilterSelectPickerProps) => {
const selectRef = useRef<SelectPickerProps<string | number, true>['selectRef']['current']>()
Expand Down Expand Up @@ -110,6 +111,7 @@ const FilterSelectPicker = ({
<div className="dc__mxw-250">
<SelectPicker<string | number, true>
{...props}
isUserIdentifier={isUserIdentifier}
selectRef={selectRef}
options={options}
value={selectedOptions}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({
menuPosition = 'fixed',
variant = SelectPickerVariantType.DEFAULT,
disableDescriptionEllipsis = false,
isUserIdentifier = false,
multiSelectProps = {},
isMulti,
name,
Expand Down Expand Up @@ -302,9 +303,13 @@ const SelectPicker = <OptionValue, IsMulti extends boolean>({

const renderOption = useCallback(
(optionProps: OptionProps<SelectPickerOptionType<OptionValue>>) => (
<SelectPickerOption {...optionProps} disableDescriptionEllipsis={disableDescriptionEllipsis} />
<SelectPickerOption
{...optionProps}
isUserIdentifier={isUserIdentifier}
disableDescriptionEllipsis={disableDescriptionEllipsis}
/>
),
[disableDescriptionEllipsis],
[disableDescriptionEllipsis, isUserIdentifier],
)

const renderMultiValue = (multiValueProps: MultiValueProps<SelectPickerOptionType<OptionValue>, true>) => (
Expand Down
43 changes: 37 additions & 6 deletions src/Shared/Components/SelectPicker/common.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ import { ReactComponent as ICCaretDown } from '@Icons/ic-caret-down.svg'
import { ReactComponent as ICClose } from '@Icons/ic-close.svg'
import { Checkbox } from '@Common/Checkbox'
import { ReactSelectInputAction } from '@Common/Constants'
import { noop } from '@Common/Helper'
import { getAlphabetIcon, noop } from '@Common/Helper'
import { Progressing } from '@Common/Progressing'
import { Tooltip, TooltipProps } from '@Common/Tooltip'
import { CHECKBOX_VALUE } from '@Common/Types'
import { ComponentSizeType } from '@Shared/constants'
import { API_TOKEN_PREFIX, ComponentSizeType } from '@Shared/constants'
import { isNullOrUndefined } from '@Shared/Helpers'

import { Button, ButtonProps, ButtonVariantType } from '../Button'
Expand Down Expand Up @@ -191,9 +191,10 @@ export const SelectPickerValueContainer = <OptionValue, IsMulti extends boolean>

export const SelectPickerOption = <OptionValue, IsMulti extends boolean>({
disableDescriptionEllipsis,
isUserIdentifier,
...props
}: OptionProps<SelectPickerOptionType<OptionValue>> &
Pick<SelectPickerProps<OptionValue, IsMulti>, 'disableDescriptionEllipsis'>) => {
Pick<SelectPickerProps<OptionValue, IsMulti>, 'disableDescriptionEllipsis' | 'isUserIdentifier'>) => {
const {
label,
data,
Expand All @@ -215,17 +216,47 @@ export const SelectPickerOption = <OptionValue, IsMulti extends boolean>({

const iconBaseClass = 'dc__no-shrink icon-dim-16 flex dc__fill-available-space'

const showUserAvatar = isUserIdentifier && !isSelected && typeof label === 'string'

const renderLabelText = () => {
if (showUserAvatar && label.startsWith(API_TOKEN_PREFIX)) {
return label.split(':')?.[1] || '-'
}

return label
}

const renderAvatar = () => {
if (!showUserAvatar) {
return null
}

return (
<div className="flex dc__no-shrink dc__visible-hover--hide-child icon-dim-20">
{label.startsWith(API_TOKEN_PREFIX) ? (
<Icon name="ic-key" color="N700" size={20} />
) : (
getAlphabetIcon(label, 'dc__no-shrink m-0-imp')
)}
</div>
)
}

return (
<components.Option {...props}>
<components.Option
{...props}
className={`${props.className || ''} ${showUserAvatar ? 'dc__visible-hover dc__visible-hover--parent' : ''}`}
>
<Tooltip {...getTooltipProps(tooltipProps)}>
<div className="flexbox dc__align-items-center dc__gap-8">
{showUserAvatar && renderAvatar()}
{isMulti && showCheckboxForMultiSelect && !isCreatableOption && (
<Checkbox
onChange={noop}
onClick={handleChange}
isChecked={isSelected || false}
value={CHECKBOX_VALUE.CHECKED}
rootClassName="mb-0 w-20 p-2 dc__align-self-start dc__no-shrink"
rootClassName={`mb-0 w-20 p-2 dc__align-self-start dc__no-shrink ${showUserAvatar ? 'dc__visible-hover--child' : ''}`}
disabled={isDisabled}
/>
)}
Expand All @@ -243,7 +274,7 @@ export const SelectPickerOption = <OptionValue, IsMulti extends boolean>({
<h4
className={`m-0 fs-13 ${isCreatableOption ? 'cb-5' : 'cn-9'} fw-4 lh-20 dc__truncate`}
>
{label}
{renderLabelText()}
</h4>
</Tooltip>
{/* Add support for custom ellipsis if required */}
Expand Down
3 changes: 3 additions & 0 deletions src/Shared/Components/SelectPicker/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export type SelectPickerProps<OptionValue = number | string, IsMulti extends boo
} & (IsMulti extends true
? {
isMulti: IsMulti | boolean
isUserIdentifier?: boolean
multiSelectProps?: Partial<Pick<SelectProps<OptionValue, IsMulti>, 'customDisplayText'>> & {
/**
* If true, the group heading can be selected
Expand All @@ -328,6 +329,7 @@ export type SelectPickerProps<OptionValue = number | string, IsMulti extends boo
: {
isMulti?: never
multiSelectProps?: never
isUserIdentifier?: never
})

// Doing like this, because of global export error GroupHeadingPropsDefinedProps
Expand All @@ -353,6 +355,7 @@ export interface FilterSelectPickerProps
| 'onMenuClose'
| 'menuIsOpen'
| 'onKeyDown'
| 'isUserIdentifier'
> {
appliedFilterOptions: SelectPickerOptionType[]
handleApplyFilter: (filtersToApply: SelectPickerOptionType<number | string>[]) => void
Expand Down
15 changes: 15 additions & 0 deletions src/Shared/Helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
/* eslint-disable no-param-reassign */
import { ReactElement, useEffect, useRef, useState } from 'react'
import { PromptProps } from 'react-router-dom'
import { parse as parseCronExpression } from '@datasert/cronjs-parser'
import { StrictRJSFSchema } from '@rjsf/utils'
import Tippy from '@tippyjs/react'
import cronstrue from 'cronstrue'
import { animate } from 'framer-motion'
import moment from 'moment'
import { nanoid } from 'nanoid'
Expand Down Expand Up @@ -752,3 +754,16 @@ export const formatNumberToCurrency = (value: number, currency: string, minimumF
return value.toFixed(precision)
}
}

/**
* Returns the human readable explanation of the expression
* NOTE: expectation is that the expression is valid
*
* @throws Error - if given expression is incorrect
* @param expression
* @returns string - helper text explaining the expression in a human readable format
*/
export const explainCronExpression = (expression: string): string => {
parseCronExpression(expression, { hasSeconds: expression.trim().split(' ').length > 5 })
return cronstrue.toString(expression)
}
4 changes: 2 additions & 2 deletions src/Shared/Services/common.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const saveCDPipeline = (request, { isTemplateView }: Required<Pick<AppCon

export const getEnvironmentData = () => get<EnvironmentDataValuesDTO>(ROUTES.ENVIRONMENT_DATA)

export const getClusterOptions = async (): Promise<ClusterType[]> => {
const { result } = await get<ClusterMinDTO[]>(ROUTES.CLUSTER_LIST_MIN)
export const getClusterOptions = async (signal?: AbortSignal): Promise<ClusterType[]> => {
const { result } = await get<ClusterMinDTO[]>(ROUTES.CLUSTER_LIST_MIN, { signal })

if (!result) {
return []
Expand Down
33 changes: 10 additions & 23 deletions src/Shared/validations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

import { parse as parseCronExpression } from '@datasert/cronjs-parser'
import { customizeValidator } from '@rjsf/validator-ajv8'
import { parse } from 'yaml'

Expand Down Expand Up @@ -546,31 +547,17 @@ export const getIsRegexValid = (regexString: string): ValidationResponseType =>
}
}

export const validateCronExpression = (cron: string): ValidationResponseType => {
// Basic cron validation - 5 parts separated by spaces
const parts = cron.trim().split(/\s+/)
if (parts.length !== 5) {
return { isValid: false, message: 'Cron expression must have 5 parts separated by spaces' }
}

const isValid = parts.every((part) => {
if (part === '*') return true
if (/^\d+$/.test(part)) return true
if (/^\d+-\d+$/.test(part)) return true
if (/^\*\/\d+$/.test(part)) return true
if (/^(\d+,)+\d+$/.test(part)) return true
return false
})
export const validateCronExpression = (expression: string): ValidationResponseType => {
try {
parseCronExpression(expression, { hasSeconds: expression.trim().split(' ').length > 5 })

// Basic validation - each part should be either * or a number or a range
if (isValid) {
return {
isValid,
isValid: true,
}
} catch (err) {
return {
isValid: false,
message: (err as Error).message,
}
}

return {
isValid: false,
message: 'Invalid cron expression format',
}
}