From 3d8aad97fbeefc09a0cab53f3652d7c0fe1e0a4b Mon Sep 17 00:00:00 2001 From: Andrew Koreykin Date: Fri, 28 Feb 2025 19:53:03 +0400 Subject: [PATCH] fix create pipeline form validation --- .../views/pipeline-list/pipeline-list.tsx | 1 + .../pipeline/create-pipeline-dialog.tsx | 2 + packages/ui/locales/en/views.json | 8 +++ packages/ui/locales/es/views.json | 10 ++- packages/ui/locales/fr/views.json | 10 ++- .../create-pipeline-dialog.tsx | 72 ++++++++++++------- .../pipelines/create-pipeline-dialog/types.ts | 14 ---- 7 files changed, 77 insertions(+), 40 deletions(-) diff --git a/apps/design-system/src/subjects/views/pipeline-list/pipeline-list.tsx b/apps/design-system/src/subjects/views/pipeline-list/pipeline-list.tsx index b5033eb6dd..34cbed97ee 100644 --- a/apps/design-system/src/subjects/views/pipeline-list/pipeline-list.tsx +++ b/apps/design-system/src/subjects/views/pipeline-list/pipeline-list.tsx @@ -24,6 +24,7 @@ const PipelineListWrapper: FC> = () => { /> setCreatePipelineOpen(false)} onSubmit={() => { setCreatePipelineOpen(false) diff --git a/apps/gitness/src/pages-v2/pipeline/create-pipeline-dialog.tsx b/apps/gitness/src/pages-v2/pipeline/create-pipeline-dialog.tsx index 5b8e88d26b..83d52fb995 100644 --- a/apps/gitness/src/pages-v2/pipeline/create-pipeline-dialog.tsx +++ b/apps/gitness/src/pages-v2/pipeline/create-pipeline-dialog.tsx @@ -6,6 +6,7 @@ import { CreatePipelineDialog as CreatePipelineDialogView, CreatePipelineFormTyp import { useRoutes } from '../../framework/context/NavigationContext' import { useGetRepoRef } from '../../framework/hooks/useGetRepoPath' +import { useTranslationStore } from '../../i18n/stores/i18n-store' import { PathParams } from '../../RouteDefinitions' import { apiBranches2BranchNames, apiBranches2DefaultBranchName } from '../repo/transform-utils/branch-transform' import { useCreatePipelineStore } from './stores/create-pipeline-dialog.store' @@ -69,6 +70,7 @@ export default function CreatePipelineDialog({ open, onClose }: CreatePipelineDi onSubmit={onSubmit} onCancel={onCloseInternal} useCreatePipelineStore={useCreatePipelineStore} + useTranslationStore={useTranslationStore} /> ) } diff --git a/packages/ui/locales/en/views.json b/packages/ui/locales/en/views.json index 9b6497bd2d..16546b2a3d 100644 --- a/packages/ui/locales/en/views.json +++ b/packages/ui/locales/en/views.json @@ -361,6 +361,14 @@ "description": "The requested page is not found.", "button": "Reload page" }, + "pipelines": { + "createPipelineValidation": { + "nameMin": "Pipeline name is required", + "nameMax": "Pipeline name must be no longer than 100 characters", + "nameRegex": "Pipeline name must contain only letters, numbers, and the characters: - _ .", + "noSpaces": "Pipeline name cannot contain spaces" + } + }, "profileSettings": { "newSshKey": "New SSH key", "enterNamePlaceholder": "Enter the name", diff --git a/packages/ui/locales/es/views.json b/packages/ui/locales/es/views.json index c0a2180474..81d3f6e117 100644 --- a/packages/ui/locales/es/views.json +++ b/packages/ui/locales/es/views.json @@ -352,6 +352,14 @@ "description": "La página solicitada no se encontró.", "button": "Recargar página" }, + "pipelines": { + "createPipelineValidation": { + "nameMin": "El nombre del pipeline es obligatorio", + "nameMax": "El nombre del pipeline no debe superar los 100 caracteres", + "nameRegex": "El nombre del pipeline debe contener solo letras, números y los caracteres: - _ .", + "noSpaces": "El nombre del pipeline no puede contener espacios" + } + }, "profileSettings": { "newSshKey": "Nueva clave SSH", "enterNamePlaceholder": "Ingresa el nombre", @@ -480,4 +488,4 @@ "secrets": { "secretsTitle": "Secrets" } -} +} \ No newline at end of file diff --git a/packages/ui/locales/fr/views.json b/packages/ui/locales/fr/views.json index d179383333..f0f0c9ce88 100644 --- a/packages/ui/locales/fr/views.json +++ b/packages/ui/locales/fr/views.json @@ -357,6 +357,14 @@ "description": "La page demandée est introuvable.", "button": "Recharger la page" }, + "pipelines": { + "createPipelineValidation": { + "nameMin": "Le nom du pipeline est requis", + "nameMax": "Le nom du pipeline ne doit pas dépasser 100 caractères", + "nameRegex": "Le nom du pipeline doit contenir uniquement des lettres, des chiffres et les caractères : - _ .", + "noSpaces": "Le nom du pipeline ne peut pas contenir d'espaces" + } + }, "profileSettings": { "newSshKey": "Nouvelle clé SSH", "enterNamePlaceholder": "Entrez le nom", @@ -485,4 +493,4 @@ "secrets": { "secretsTitle": "Secrets" } -} +} \ No newline at end of file diff --git a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx index e9aa15ee3e..cbe9046750 100644 --- a/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx +++ b/packages/ui/src/views/pipelines/create-pipeline-dialog/create-pipeline-dialog.tsx @@ -2,19 +2,51 @@ import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import { Alert, Button, ControlGroup, Dialog, Fieldset, FormWrapper, Input, Select } from '@/components' +import { TranslationStore } from '@/views' import { zodResolver } from '@hookform/resolvers/zod' import { z } from 'zod' -import { CreatePipelineDialogProps, CreatePipelineFormType } from './types' +import { ICreatePipelineStore } from './types' + +export const makeCreatePipelineSchema = (t: TranslationStore['t']) => + z.object({ + name: z + .string() + .trim() + .min(1, { message: t('views:pipelines.createPipelineValidation.nameMin', 'Pipeline name is required') }) + .max(100, { + message: t( + 'views:pipelines.createPipelineValidation.nameMax', + 'Pipeline name must be no longer than 100 characters' + ) + }) + .regex(/^[a-zA-Z0-9._-\s]+$/, { + message: t( + 'views:pipelines.createPipelineValidation.nameRegex', + 'Pipeline name must contain only letters, numbers, and the characters: - _ .' + ) + }) + .refine(data => !data.includes(' '), { + message: t('views:pipelines.createPipelineValidation.noSpaces', 'Pipeline name cannot contain spaces') + }), + branch: z.string().min(1, { message: 'Branch name is required' }), + yamlPath: z.string().min(1, { message: 'YAML path is required' }) + }) + +export type CreatePipelineFormType = z.infer> -const createPipelineSchema = z.object({ - name: z.string().min(1, { message: 'Pipeline name is required' }), - branch: z.string().min(1, { message: 'Branch name is required' }), - yamlPath: z.string().min(1, { message: 'YAML path is required' }) -}) +interface CreatePipelineDialogProps { + useCreatePipelineStore: () => ICreatePipelineStore + isOpen: boolean + onClose: () => void + onCancel: () => void + onSubmit: (formValues: CreatePipelineFormType) => Promise + useTranslationStore: () => TranslationStore +} export function CreatePipelineDialog(props: CreatePipelineDialogProps) { - const { onCancel, onSubmit, isOpen, onClose, useCreatePipelineStore } = props + const { onCancel, onSubmit, isOpen, onClose, useCreatePipelineStore, useTranslationStore } = props + const { t } = useTranslationStore() const { isLoadingBranchNames, branchNames, defaultBranch, error } = useCreatePipelineStore() @@ -35,7 +67,7 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { trigger, formState: { errors, isValid } } = useForm({ - resolver: zodResolver(createPipelineSchema), + resolver: zodResolver(makeCreatePipelineSchema(t)), mode: 'onChange', defaultValues: { name: '', @@ -70,14 +102,14 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { setValue(fieldName, value, { shouldValidate: true }) } + const handleClose = () => { + onCancel() + onClose() + reset() + } + return ( - { - onClose() - reset() - }} - > + Create Pipeline @@ -131,15 +163,7 @@ export function CreatePipelineDialog(props: CreatePipelineDialogProps) { )} -