diff --git a/package-lock.json b/package-lock.json index 4995bef46..ba00ed72c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "0.6.5", + "version": "0.7.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "0.6.5", + "version": "0.7.1", "license": "ISC", "dependencies": { "@types/react-dates": "^21.8.6", diff --git a/package.json b/package.json index 8b3b3cf5e..535ff33fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@devtron-labs/devtron-fe-common-lib", - "version": "0.6.5", + "version": "0.7.1", "description": "Supporting common component library", "type": "module", "main": "dist/index.js", diff --git a/src/Common/CIPipeline.Types.ts b/src/Common/CIPipeline.Types.ts index 86418dfde..ea92fb660 100644 --- a/src/Common/CIPipeline.Types.ts +++ b/src/Common/CIPipeline.Types.ts @@ -170,7 +170,6 @@ export interface StepType { inlineStepDetail?: InlineStepDetailType pluginRefStepDetail?: PluginRefStepDetailType triggerIfParentStageFail: boolean - isMandatory?: boolean } export interface BuildStageType { diff --git a/src/Common/Policy.Types.ts b/src/Common/Policy.Types.ts index f99dcfb45..b18617026 100644 --- a/src/Common/Policy.Types.ts +++ b/src/Common/Policy.Types.ts @@ -14,34 +14,35 @@ * limitations under the License. */ -import { PluginDataStoreType } from '../Shared' +import { PipelineFormType } from '@Pages/Applications' +import { PluginDataStoreType, PluginDetailPayloadType, ResourceKindType } from '../Shared' import { VariableType } from './CIPipeline.Types' import { ServerErrors } from './ServerError' -import { ResponseType } from './Types' export enum ApplyPolicyToStage { PRE_CI = 'PRE_CI', POST_CI = 'POST_CI', + /** + * @deprecated in mandatory plugin policy v2 + */ PRE_OR_POST_CI = 'PRE_OR_POST_CI', + PRE_CD = 'PRE_CD', + POST_CD = 'POST_CD', } +// FIXME: The name build is getting is used in CDPipeline. +// This enum is mapping values from BuildStageVariable export enum PluginRequiredStage { - PRE_CI = 'preBuildStage', - POST_CI = 'postBuildStage', - PRE_OR_POST_CI = 'PRE_OR_POST_CI', + PRE_STAGE = 'preBuildStage', + POST_STAGE = 'postBuildStage', + PRE_OR_POST_STAGE = 'PRE_OR_POST_CI', } export interface DefinitionSourceType { - projectName: string - isDueToProductionEnvironment: boolean - isDueToLinkedPipeline: boolean - policyName: string - appName?: string - clusterName?: string - environmentName?: string - branchNames?: string[] - ciPipelineName?: string + policyNames: string[] + linkedCIPipelineNames?: string[] } + export interface MandatoryPluginDetailType { id: number parentPluginId: number @@ -52,7 +53,7 @@ export interface MandatoryPluginDetailType { applied?: boolean inputVariables?: VariableType[] outputVariables?: VariableType[] - definitionSources?: DefinitionSourceType[] + definitionSources?: DefinitionSourceType } export interface MandatoryPluginDataType { pluginData: MandatoryPluginDetailType[] @@ -66,23 +67,67 @@ export interface ProcessPluginDataReturnType { mandatoryPluginsError?: ServerErrors } +export type ProcessPluginDataCIParamsType = { + resourceKind: ResourceKindType.ciPipeline + ciPipelineId: number + /** + * Comma separated branch names used for v1 api + * For v2 format is [branchName1],[branchName2] + */ + branchName?: string + + envName?: never +} + +export type ProcessPluginDataCDParamsType = { + resourceKind: ResourceKindType.cdPipeline + envName?: string + + ciPipelineId?: never + branchName?: never +} + +export type ProcessPluginDataParamsType = { + formData: PipelineFormType + pluginDataStoreState: PluginDataStoreType + appId: number + appName: string + /** + * Would be sent in case we have to get data for steps + */ + requiredPluginIds?: PluginDetailPayloadType['pluginId'] +} & (ProcessPluginDataCIParamsType | ProcessPluginDataCDParamsType) + export enum ConsequenceAction { + /** + * This is used if the policy is enforced immediately. + */ BLOCK = 'BLOCK', + /** + * This is used if the policy will be enforced after a certain timestamp. + */ ALLOW_UNTIL_TIME = 'ALLOW_UNTIL_TIME', + /** + * This is used if the policy is not enforced yet (just to show waring). + */ ALLOW_FOREVER = 'ALLOW_FOREVER', } -export interface ConsequenceType { - action: ConsequenceAction - metadataField: string -} +export type ConsequenceType = + | { + action: Exclude + metadataField?: never | null + } + | { + action: ConsequenceAction.ALLOW_UNTIL_TIME + /** + * Denotes the time till which the policy enforcement is relaxed + */ + metadataField: string + } export interface BlockedStateData { isOffendingMandatoryPlugin: boolean isCITriggerBlocked: boolean ciBlockState: ConsequenceType } - -export interface GetBlockedStateResponse extends ResponseType { - result?: BlockedStateData -} diff --git a/src/Common/Types.ts b/src/Common/Types.ts index d8228f764..f87e43f8f 100644 --- a/src/Common/Types.ts +++ b/src/Common/Types.ts @@ -18,8 +18,15 @@ import React, { ReactNode, CSSProperties, ReactElement } from 'react' import { Placement } from 'tippy.js' import { UserGroupDTO } from '@Pages/GlobalConfigurations' import { ImageComment, ReleaseTag } from './ImageTags.Types' -import { ACTION_STATE, DEPLOYMENT_WINDOW_TYPE, DockerConfigOverrideType, SortingOrder, TaskErrorObj } from '.' -import { RegistryType, RuntimeParamsListItemType, Severity } from '../Shared' +import { MandatoryPluginBaseStateType, RegistryType, RuntimeParamsListItemType, Severity } from '../Shared' +import { + ACTION_STATE, + ConsequenceType, + DEPLOYMENT_WINDOW_TYPE, + DockerConfigOverrideType, + SortingOrder, + TaskErrorObj, +} from '.' /** * Generic response type object with support for overriding the result type @@ -450,12 +457,12 @@ export interface ArtifactReleaseMappingType { } export interface CDMaterialListModalServiceUtilProps { - artifacts: any[], - offset: number, - artifactId?: number, - artifactStatus?: string, - disableDefaultSelection?: boolean, - userApprovalConfig?: UserApprovalConfigType, + artifacts: any[] + offset: number + artifactId?: number + artifactStatus?: string + disableDefaultSelection?: boolean + userApprovalConfig?: UserApprovalConfigType } export interface CDMaterialType { @@ -552,7 +559,7 @@ export interface DownstreamNodesEnvironmentsType { environmentName: string } -export interface CommonNodeAttr { +export interface CommonNodeAttr extends Pick { connectingCiPipelineId?: number parents: string | number[] | string[] x: number @@ -602,15 +609,10 @@ export interface CommonNodeAttr { approvalUsers?: string[] userApprovalConfig?: UserApprovalConfigType requestedUserId?: number - showPluginWarning?: boolean + showPluginWarning: boolean helmPackageName?: string isVirtualEnvironment?: boolean deploymentAppType?: DeploymentAppTypes - isCITriggerBlocked?: boolean - ciBlockState?: { - action: any - metadataField: string - } appReleaseTagNames?: string[] tagsEditable?: boolean isGitOpsRepoNotConfigured?: boolean @@ -793,7 +795,7 @@ export interface CDStageConfigMapSecretNames { secrets: any[] } -export interface PrePostDeployStageType { +export interface PrePostDeployStageType extends MandatoryPluginBaseStateType { isValid: boolean steps: TaskErrorObj[] triggerType: string @@ -831,6 +833,8 @@ export interface CdPipeline { preDeployStage?: PrePostDeployStageType postDeployStage?: PrePostDeployStageType isProdEnv?: boolean + isGitOpsRepoNotConfigured?: boolean + isDeploymentBlocked?: boolean } export interface ExternalCiConfig { diff --git a/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx b/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx index 96839067b..a406bbb9d 100644 --- a/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx +++ b/src/Pages/GlobalConfigurations/BuildInfra/Descriptor.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { DescriptorProps } from './types' +import { BuildInfraDescriptorProps } from './types' import { BreadCrumb, DOCUMENTATION } from '../../../Common' import { BUILD_INFRA_TEXT } from './constants' import { InfoIconTippy } from '../../../Shared' @@ -25,7 +25,7 @@ const Descriptor = ({ children, tippyInfoText, tippyAdditionalContent, -}: DescriptorProps) => ( +}: BuildInfraDescriptorProps) => (
diff --git a/src/Pages/GlobalConfigurations/BuildInfra/types.tsx b/src/Pages/GlobalConfigurations/BuildInfra/types.tsx index 519222311..373deca6f 100644 --- a/src/Pages/GlobalConfigurations/BuildInfra/types.tsx +++ b/src/Pages/GlobalConfigurations/BuildInfra/types.tsx @@ -74,7 +74,7 @@ export enum BuildInfraProfileVariants { CUSTOM = 'CUSTOM', } -export interface DescriptorProps { +export interface BuildInfraDescriptorProps { /** * In case we want to restrict the max-width */ diff --git a/src/Shared/Components/DatePicker/DateTimePicker.tsx b/src/Shared/Components/DatePicker/DateTimePicker.tsx index 515f04975..ccdaf459c 100644 --- a/src/Shared/Components/DatePicker/DateTimePicker.tsx +++ b/src/Shared/Components/DatePicker/DateTimePicker.tsx @@ -69,9 +69,11 @@ const DateTimePicker = ({ return (
- + {label && ( + + )}
{ const [showFeatureDescriptionModal, setShowFeatureDescriptionModal] = useState(false) const onClickInfoIcon = () => { @@ -64,10 +65,14 @@ const FeatureTitleWithInfo = ({
) } - if (breadCrumbs?.length > 0) { + if (breadCrumbs?.length > 0 || showInfoIcon) { return (
- + {showInfoIcon && breadCrumbs?.length === 0 ? ( + {title} + ) : ( + + )}
) diff --git a/src/Shared/Components/FeatureDescription/index.ts b/src/Shared/Components/FeatureDescription/index.ts index 2cb9f1ab1..6ff97bf34 100644 --- a/src/Shared/Components/FeatureDescription/index.ts +++ b/src/Shared/Components/FeatureDescription/index.ts @@ -16,3 +16,4 @@ export * from './FeatureDescriptionModal' export { default as FeatureTitleWithInfo } from './FeatureTitleWithInfo' +export * from './types' diff --git a/src/Shared/Components/FeatureDescription/types.ts b/src/Shared/Components/FeatureDescription/types.ts index d874b3588..d963293a2 100644 --- a/src/Shared/Components/FeatureDescription/types.ts +++ b/src/Shared/Components/FeatureDescription/types.ts @@ -38,4 +38,10 @@ export interface DescriptorProps extends FeatureDescriptionModalProps { docLinkText?: string dataTestId?: string additionalContent?: ReactNode + /** + * If true, the info icon is displayed which when clicked shows the feature description modal + * + * @default false + */ + showInfoIcon?: boolean } diff --git a/src/Shared/Components/FilterChips/FilterChips.component.tsx b/src/Shared/Components/FilterChips/FilterChips.component.tsx index e39a9373b..63ea141eb 100644 --- a/src/Shared/Components/FilterChips/FilterChips.component.tsx +++ b/src/Shared/Components/FilterChips/FilterChips.component.tsx @@ -15,7 +15,7 @@ */ import { ReactComponent as CloseIcon } from '../../../Assets/Icon/ic-close.svg' -import { noop } from '../../../Common' +import { noop, Tooltip } from '../../../Common' import { FilterChipProps, FilterChipsProps } from './types' const FilterChip = ({ @@ -45,7 +45,9 @@ const FilterChip = ({ )} - {valueToDisplay} + + {valueToDisplay} + {showRemoveIcon && (
diff --git a/src/Shared/Components/SelectPicker/common.tsx b/src/Shared/Components/SelectPicker/common.tsx index 83771f797..40213de23 100644 --- a/src/Shared/Components/SelectPicker/common.tsx +++ b/src/Shared/Components/SelectPicker/common.tsx @@ -80,13 +80,12 @@ export const SelectPickerClearIndicator = ( ) -export const SelectPickerControl = ({ - icon, - showSelectedOptionIcon, - ...props -}: ControlProps> & - Pick, 'icon' | 'showSelectedOptionIcon'>) => { - const { children, getValue } = props +export const SelectPickerControl = (props: ControlProps>) => { + const { + children, + getValue, + selectProps: { icon, showSelectedOptionIcon }, + } = props const { startIcon, endIcon } = getValue()?.[0] ?? {} let iconToDisplay: SelectPickerOptionType['startIcon'] = icon @@ -173,7 +172,7 @@ export const SelectPickerOption = ({ )}
{startIcon && ( -
{startIcon}
+
{startIcon}
)}

@@ -192,7 +191,7 @@ export const SelectPickerOption = ({ ))}

{endIcon && ( -
{endIcon}
+
{endIcon}
)}
@@ -228,7 +227,7 @@ export const SelectPickerMultiValueLabel = ['multiSelectProps'], 'getIsOptionValid'>) => { const { data, selectProps } = props const isOptionValid = getIsOptionValid(data) - const iconToDisplay = isOptionValid ? data.startIcon || data.endIcon : + const iconToDisplay = isOptionValid ? ((data.startIcon || data.endIcon) ?? null) : return (
diff --git a/src/Shared/Components/SelectPicker/type.ts b/src/Shared/Components/SelectPicker/type.ts index 8506813c3..1745c3b39 100644 --- a/src/Shared/Components/SelectPicker/type.ts +++ b/src/Shared/Components/SelectPicker/type.ts @@ -73,6 +73,17 @@ declare module 'react-select/base' { * Imp Note: The menu open/close needs to handled by the consumer in this case */ renderCustomOptions?: () => ReactElement + /** + * Icon to be rendered in the control + */ + icon?: ReactElement + /** + * If true, the selected option icon is shown in the container. + * startIcon has higher priority than endIcon. + * + * @default 'true' + */ + showSelectedOptionIcon?: boolean } } @@ -108,13 +119,18 @@ export type SelectPickerProps & Partial< Pick< SelectProps, - 'renderMenuListFooter' | 'shouldRenderCustomOptions' | 'renderCustomOptions' + | 'renderMenuListFooter' + | 'shouldRenderCustomOptions' + | 'renderCustomOptions' + | 'icon' + | 'showSelectedOptionIcon' > > & Required, 'inputId'>> & @@ -128,10 +144,6 @@ export type SelectPickerProps > & { - /** - * Icon to be rendered in the control - */ - icon?: ReactElement /** * Error message for the select */ @@ -144,13 +156,6 @@ export type SelectPickerProps { appliedFilterOptions: SelectPickerOptionType[] diff --git a/src/Shared/Components/SelectPicker/utils.ts b/src/Shared/Components/SelectPicker/utils.ts index 4d39f3164..c8b9b471b 100644 --- a/src/Shared/Components/SelectPicker/utils.ts +++ b/src/Shared/Components/SelectPicker/utils.ts @@ -349,36 +349,28 @@ export const getGroupCheckboxValue = ( * @param optionsList - The list of options or groups of options. * @param value - The value to compare against the options' values. * @param defaultOption - The default option to return if no match is found. + * @param getOptionValue - Override the default value for the option * @returns The matched option or the default option if no match is found. */ export const getSelectPickerOptionByValue = ( optionsList: OptionsOrGroups, GroupBase>>, value: OptionValue, defaultOption: SelectPickerOptionType = { label: '', value: '' as unknown as OptionValue }, + getOptionValue: SelectPickerProps['getOptionValue'] = null, ): SelectPickerOptionType => { if (!Array.isArray(optionsList)) { return defaultOption } - const foundOption = optionsList.reduce( - (acc, curr) => { - if (!acc.notFound) return acc + const flatOptionsList = optionsList.flatMap>((groupOrBaseOption) => + 'options' in groupOrBaseOption ? groupOrBaseOption.options : [groupOrBaseOption], + ) - if ('value' in curr && curr.value === value) { - return { data: curr, notFound: false } - } - - if ('options' in curr && curr.options) { - const nestedOption = curr.options.find(({ value: _value }) => _value === value) - if (nestedOption) { - return { data: nestedOption, notFound: false } - } - } - - return acc - }, - { notFound: true, data: defaultOption }, - ).data + return ( + flatOptionsList.find((option) => { + const optionValue = getOptionValue ? getOptionValue(option) : option.value - return foundOption + return optionValue === value + }) ?? defaultOption + ) } diff --git a/src/Shared/Hooks/useGetResourceKindsOptions/service.ts b/src/Shared/Hooks/useGetResourceKindsOptions/service.ts index c67f29585..e4c9ebf64 100644 --- a/src/Shared/Hooks/useGetResourceKindsOptions/service.ts +++ b/src/Shared/Hooks/useGetResourceKindsOptions/service.ts @@ -18,11 +18,11 @@ import { ResponseType, Teams } from '@Common/Types' import { getTeamListMin } from '@Common/Common.service' import { get } from '@Common/Api' import { ClusterType } from '@Shared/Services' -import { EnvironmentType, EnvListMinDTO } from '@Shared/types' +import { EnvListMinDTO } from '@Shared/types' import { EnvironmentTypeEnum } from '@Shared/constants' import { ROUTES } from '@Common/Constants' import { stringComparatorBySortOrder } from '@Shared/Helpers' -import { AppsGroupedByProjectsType, ClusterDTO } from './types' +import { AppsGroupedByProjectsType, ClusterDTO, EnvironmentsGroupedByClustersType } from './types' export const getAppOptionsGroupedByProjects = async (): Promise => { const { result } = (await get(ROUTES.APP_LIST_MIN)) as ResponseType @@ -70,14 +70,14 @@ export const getClusterOptions = async (): Promise => { .sort((a, b) => stringComparatorBySortOrder(a.name, b.name)) } -export const getEnvironmentOptions = async (): Promise => { +export const getEnvironmentOptionsGroupedByClusters = async (): Promise => { const { result } = (await get(ROUTES.ENVIRONMENT_LIST_MIN)) as ResponseType if (!result) { return [] } - return result + const sortedEnvList = result .map( ({ id, @@ -96,4 +96,23 @@ export const getEnvironmentOptions = async (): Promise => { }), ) .sort((a, b) => stringComparatorBySortOrder(a.name, b.name)) + + const envGroupedByCluster = Object.values( + sortedEnvList.reduce< + Record + >((acc, env) => { + if (!acc[env.cluster]) { + acc[env.cluster] = { + clusterName: env.cluster, + envList: [], + } + } + + acc[env.cluster].envList.push(env) + + return acc + }, {}), + ).sort((a, b) => stringComparatorBySortOrder(a.clusterName, b.clusterName)) + + return envGroupedByCluster } diff --git a/src/Shared/Hooks/useGetResourceKindsOptions/types.ts b/src/Shared/Hooks/useGetResourceKindsOptions/types.ts index 7c72b8492..f87d9db7e 100644 --- a/src/Shared/Hooks/useGetResourceKindsOptions/types.ts +++ b/src/Shared/Hooks/useGetResourceKindsOptions/types.ts @@ -16,9 +16,9 @@ // ====== Service Types: Start ====== // -import { ResourceKindType } from '@Shared/types' +import { EnvironmentType, ResourceKindType } from '@Shared/types' import { ServerErrors } from '@Common/ServerError' -import { getAppOptionsGroupedByProjects, getClusterOptions, getEnvironmentOptions, getProjectOptions } from './service' +import { getAppOptionsGroupedByProjects, getClusterOptions, getProjectOptions } from './service' export interface AppType { name: string @@ -30,6 +30,11 @@ export type AppsGroupedByProjectsType = { appList: AppType[] }[] +export type EnvironmentsGroupedByClustersType = { + clusterName: EnvironmentType['cluster'] + envList: EnvironmentType[] +}[] + export interface ClusterDTO { id: number cluster_name: string @@ -54,7 +59,7 @@ export interface UseGetResourceKindOptionsReturnType { [ResourceKindType.devtronApplication]: Awaited> [ResourceKindType.project]: Awaited> [ResourceKindType.cluster]: Awaited> - [ResourceKindType.environment]: Awaited> + [ResourceKindType.environment]: EnvironmentsGroupedByClustersType } resourcesOptionsError: ServerErrors refetchResourcesOptions: () => void diff --git a/src/Shared/Hooks/useGetResourceKindsOptions/useGetResourceKindsOptions.tsx b/src/Shared/Hooks/useGetResourceKindsOptions/useGetResourceKindsOptions.tsx index 104eaa689..4bd2f3829 100644 --- a/src/Shared/Hooks/useGetResourceKindsOptions/useGetResourceKindsOptions.tsx +++ b/src/Shared/Hooks/useGetResourceKindsOptions/useGetResourceKindsOptions.tsx @@ -17,7 +17,12 @@ import { useMemo } from 'react' import { ResourceKindType } from '@Shared/types' import { useAsync } from '@Common/Helper' -import { getAppOptionsGroupedByProjects, getClusterOptions, getEnvironmentOptions, getProjectOptions } from './service' +import { + getAppOptionsGroupedByProjects, + getClusterOptions, + getEnvironmentOptionsGroupedByClusters, + getProjectOptions, +} from './service' import { UseGetResourceKindOptionsReturnType, UseGetResourceKindsOptionsProps } from './types' import { getResourcesToFetchMap } from './utils' @@ -43,7 +48,7 @@ const useGetResourceKindsOptions = ({ resourcesToFetchMap[ResourceKindType.devtronApplication] ? getAppOptionsGroupedByProjects() : null, resourcesToFetchMap[ResourceKindType.project] ? getProjectOptions() : null, resourcesToFetchMap[ResourceKindType.cluster] ? getClusterOptions() : null, - resourcesToFetchMap[ResourceKindType.environment] ? getEnvironmentOptions() : null, + resourcesToFetchMap[ResourceKindType.environment] ? getEnvironmentOptionsGroupedByClusters() : null, ]), [resourcesToFetchMap], resourcesToFetch.length > 0, diff --git a/src/Shared/Services/ToastManager/toastManager.scss b/src/Shared/Services/ToastManager/toastManager.scss index 141a9a118..deaa36eb2 100644 --- a/src/Shared/Services/ToastManager/toastManager.scss +++ b/src/Shared/Services/ToastManager/toastManager.scss @@ -47,8 +47,9 @@ &__content { // Override the style for the action button - button { + button.button, a.button { color: var(--N0); + width: fit-content; svg *[stroke^="#"] { stroke: var(--N0); diff --git a/src/Shared/types.ts b/src/Shared/types.ts index 42082ea2e..3cd3a1fa8 100644 --- a/src/Shared/types.ts +++ b/src/Shared/types.ts @@ -440,6 +440,7 @@ export enum ResourceKindType { installation = 'installation', environment = 'environment', cdPipeline = 'cd-pipeline', + ciPipeline = 'ci-pipeline', project = 'project', } @@ -462,6 +463,8 @@ export interface SeverityCount { } export enum PolicyKindType { lockConfiguration = 'lock-configuration', + imagePromotion = 'image-promotion', + plugins = 'plugin', } export interface LastExecutionResultType { diff --git a/src/Shared/validations.tsx b/src/Shared/validations.tsx index 4a8454c5f..d6fc0edc7 100644 --- a/src/Shared/validations.tsx +++ b/src/Shared/validations.tsx @@ -32,6 +32,8 @@ export const MESSAGES = { VALID_POSITIVE_INTEGER: 'This field should be a valid positive integer', MAX_SAFE_INTEGER: `Maximum allowed value is ${Number.MAX_SAFE_INTEGER}`, INVALID_SEMANTIC_VERSION: 'Please follow semantic versioning', + INVALID_DATE: 'Please enter a valid date', + DATE_BEFORE_CURRENT_TIME: 'The date & time cannot be before the current time', } const MAX_DESCRIPTION_LENGTH = 350 @@ -342,3 +344,24 @@ export const validateJSON = (json: string): ValidationResponseType => { } } } + +export const validateDateAndTime = (date: Date): ValidationResponseType => { + if (date) { + const currentDate = new Date() + if (currentDate.getTime() > date.getTime()) { + return { + isValid: false, + message: MESSAGES.DATE_BEFORE_CURRENT_TIME, + } + } + } else { + return { + isValid: false, + message: MESSAGES.INVALID_DATE, + } + } + + return { + isValid: true, + } +} diff --git a/src/index.ts b/src/index.ts index a5464716a..c12bae121 100644 --- a/src/index.ts +++ b/src/index.ts @@ -76,6 +76,7 @@ export interface customEnv { SYSTEM_CONTROLLER_LISTING_TIMEOUT?: number FEATURE_STEP_WISE_LOGS_ENABLE?: boolean FEATURE_IMAGE_PROMOTION_ENABLE?: boolean + FEATURE_CD_MANDATORY_PLUGINS_ENABLE?: boolean /** * If true, the direct permissions are hidden for non-super admins in user permissions *