Skip to content

Commit 26da464

Browse files
authored
fix(entities-*): allow UTF-8 chars in some fields (#1282)
1 parent eb528d0 commit 26da464

File tree

9 files changed

+90
-29
lines changed

9 files changed

+90
-29
lines changed

packages/entities/entities-consumer-groups/src/components/ConsumerGroupForm.vue

+3-6
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import {
8383
EntityBaseFormType,
8484
EntityFormSection,
8585
useAxios,
86-
useDebouncedFilter, useErrors,
86+
useDebouncedFilter, useErrors, useValidators,
8787
} from '@kong-ui-public/entities-shared'
8888
import '@kong-ui-public/entities-shared/dist/style.css'
8989
import composables from '../composables'
@@ -164,6 +164,7 @@ const { axiosInstance } = useAxios({
164164
headers: props.config?.requestHeaders,
165165
})
166166
const { getMessageFromError } = useErrors()
167+
const validators = useValidators()
167168
168169
const displayedConsumers = computed((): MultiselectItem[] => {
169170
return results.value.map((record: Record<string, any>) => ({
@@ -236,11 +237,7 @@ const updateFormValues = async (data: Record<string, any>): Promise<void> => {
236237
Object.assign(originalFields, state.fields)
237238
}
238239
239-
const preValidateErrorMessage = computed(() => {
240-
const namePattern = /^[0-9a-zA-Z.\-_~]*$/
241-
242-
return namePattern.test(state.fields.name) ? '' : t('consumer_groups.form.validation_errors.name')
243-
})
240+
const preValidateErrorMessage = computed(() => validators.utf8Name(state.fields.name))
244241
245242
const getPayload = computed((): ConsumerGroupPayload => {
246243
return {

packages/entities/entities-consumer-groups/src/locales/en.json

-3
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,6 @@
8686
"label": "Consumers",
8787
"placeholder": "Select one or more consumers"
8888
}
89-
},
90-
"validation_errors": {
91-
"name": "The name can be any string containing letters, numbers, or the following characters: ., -, _, or ~. Do not use spaces."
9289
}
9390
}
9491
}

packages/entities/entities-gateway-services/src/components/GatewayServiceForm.cy.ts

+58
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,35 @@ describe('<GatewayServiceForm />', { viewportHeight: 800, viewportWidth: 700 },
8383
cy.getTestId('form-submit').should('be.disabled')
8484
})
8585

86+
it("should check for name's validity", () => {
87+
cy.mount(GatewayServiceForm, {
88+
props: {
89+
config: baseConfigKonnect,
90+
},
91+
})
92+
93+
cy.get('.kong-ui-entities-gateway-service-form').should('be.visible')
94+
cy.get('.kong-ui-entities-gateway-service-form form').should('be.visible')
95+
96+
cy.getTestId('gateway-service-name-input').should('be.visible')
97+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
98+
.should('not.exist')
99+
100+
cy.getTestId('gateway-service-name-input').type('service')
101+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
102+
.should('not.exist')
103+
104+
cy.getTestId('gateway-service-name-input').clear()
105+
cy.getTestId('gateway-service-name-input').type('service abc') // with a space
106+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
107+
.first().find('.help-text').should('be.visible')
108+
109+
cy.getTestId('gateway-service-name-input').clear()
110+
cy.getTestId('gateway-service-name-input').type('Hello-ÆBČÐẼF-你好-妳好-こんにちは-안녕하세요-𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯') // UTF-8
111+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
112+
.should('not.exist')
113+
})
114+
86115
it('should enable Save button if Upstream URL is selected and Upstream URL field is filled in', () => {
87116
cy.mount(GatewayServiceForm, {
88117
props: {
@@ -436,6 +465,35 @@ describe('<GatewayServiceForm />', { viewportHeight: 800, viewportWidth: 700 },
436465
cy.getTestId('form-submit').should('be.disabled')
437466
})
438467

468+
it("should check for name's validity", () => {
469+
cy.mount(GatewayServiceForm, {
470+
props: {
471+
config: baseConfigKonnect,
472+
},
473+
})
474+
475+
cy.get('.kong-ui-entities-gateway-service-form').should('be.visible')
476+
cy.get('.kong-ui-entities-gateway-service-form form').should('be.visible')
477+
478+
cy.getTestId('gateway-service-name-input').should('be.visible')
479+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
480+
.should('not.exist')
481+
482+
cy.getTestId('gateway-service-name-input').type('service')
483+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
484+
.should('not.exist')
485+
486+
cy.getTestId('gateway-service-name-input').clear()
487+
cy.getTestId('gateway-service-name-input').type('service abc') // with a space
488+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
489+
.first().find('.help-text').should('be.visible')
490+
491+
cy.getTestId('gateway-service-name-input').clear()
492+
cy.getTestId('gateway-service-name-input').type('Hello-ÆBČÐẼF-你好-妳好-こんにちは-안녕하세요-𑁦𑁧𑁨𑁩𑁪𑁫𑁬𑁭𑁮𑁯') // UTF-8
493+
cy.getTestId('gateway-service-name-input').parents('.k-input-wrapper.input-error')
494+
.should('not.exist')
495+
})
496+
439497
it('should enable Save button if Upstream URL is selected and Upstream URL field is filled in', () => {
440498
cy.mount(GatewayServiceForm, {
441499
props: {

packages/entities/entities-gateway-services/src/components/GatewayServiceForm.vue

+5-16
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
:placeholder="t('gateway_services.form.fields.name.placeholder')"
3232
:readonly="form.isReadonly"
3333
type="text"
34-
@input="preValidate"
34+
@input="validateName"
3535
/>
3636

3737
<KInput
@@ -361,6 +361,7 @@ import {
361361
EntityFormSection,
362362
EntityBaseForm,
363363
EntityBaseFormType,
364+
useValidators,
364365
} from '@kong-ui-public/entities-shared'
365366
import '@kong-ui-public/entities-shared/dist/style.css'
366367
@@ -408,6 +409,7 @@ const { getMessageFromError } = useErrors()
408409
const { axiosInstance } = useAxios({
409410
headers: props.config?.requestHeaders,
410411
})
412+
const validators = useValidators()
411413
412414
const fetchUrl = computed<string>(() => endpoints.form[props.config.app].edit)
413415
const formType = computed((): EntityBaseFormType => props.gatewayServiceId ? EntityBaseFormType.Edit : EntityBaseFormType.Create)
@@ -621,21 +623,8 @@ const showTlsVerify = computed((): boolean => {
621623
return checkedGroup.value === 'protocol' && isValidProtocol
622624
})
623625
624-
/**
625-
* Check whether the given string matches name formats
626-
* and returns an error message (if invalid) or empty string (if valid)
627-
* @param {String} str the str to test
628-
* @returns {String} an error message
629-
*/
630-
const validateName = (str: string): string => {
631-
// eslint-disable-next-line prefer-regex-literals
632-
const namePattern = new RegExp('^[0-9a-zA-Z.\\-_~]*$')
633-
634-
return namePattern.test(str) ? '' : t('errors.validationNameError')
635-
}
636-
637-
const preValidate = (input: string): void => {
638-
preValidateErrorMessage.value = validateName(input)
626+
const validateName = (input: string): void => {
627+
preValidateErrorMessage.value = validators.utf8Name(input)
639628
}
640629
641630
/**

packages/entities/entities-gateway-services/src/locales/en.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,7 @@
167167
"general": "Gateway Services could not be retrieved",
168168
"delete": "The gateway service could not be deleted at this time.",
169169
"copy": "Failed to copy to clipboard",
170-
"urlErrorMessage": "Error: invalid URL",
171-
"validationNameError": "The name can be any string containing letters, numbers, or the following characters: ., -, _, or ~. Do not use spaces."
170+
"urlErrorMessage": "Error: invalid URL"
172171
},
173172
"copy": {
174173
"success": "Copied {val} to clipboard",

packages/entities/entities-shared/src/composables/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import useStringHelpers from './useStringHelpers'
1010
import useI18n from './useI18n'
1111
import useGatewayFeatureSupported from './useGatewayFeatureSupported'
1212
import useTruncationDetector from './useTruncationDetector'
13+
import useValidators from './useValidators'
1314

1415
// All composables must be exported as part of the default object for Cypress test stubs
1516
export default {
@@ -25,4 +26,5 @@ export default {
2526
useI18n,
2627
useGatewayFeatureSupported,
2728
useTruncationDetector,
29+
useValidators,
2830
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import useI18n from './useI18n'
2+
3+
/**
4+
* Provides a collection of validator functions for entity fields.
5+
* Each validator function returns an error message if the field is invalid, or an empty string if it is valid.
6+
*/
7+
export default function useValidators() {
8+
const { i18n: { t } } = useI18n()
9+
10+
const validateUTF8Name = (name:string) =>
11+
/^[\p{N}\p{L}.\-_~]*$/u.test(name) ? '' : t('validationErrors.utf8Name')
12+
13+
return {
14+
utf8Name: validateUTF8Name,
15+
}
16+
}

packages/entities/entities-shared/src/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import YamlCodeBlock from './components/common/YamlCodeBlock.vue'
1515
import composables from './composables'
1616

1717
// Extract specific composables to export
18-
const { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector } = composables
18+
const { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators } = composables
1919

2020
// Components
2121
export { EntityBaseConfigCard, ConfigCardItem, ConfigCardDisplay, InternalLinkItem, EntityBaseForm, EntityBaseTable, EntityDeleteModal, EntityFilter, EntityToggleModal, PermissionsWrapper, EntityFormSection, EntityLink, JsonCodeBlock, YamlCodeBlock }
2222

2323
// Composables
24-
export { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector }
24+
export { useAxios, useDeleteUrlBuilder, useErrors, useExternalLinkCreator, useFetchUrlBuilder, useFetcher, useDebouncedFilter, useStringHelpers, useHelpers, useGatewayFeatureSupported, useTruncationDetector, useValidators }
2525

2626
// Types
2727
export * from './types'

packages/entities/entities-shared/src/locales/en.json

+3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@
9090
"unexpected": "An unexpected error has occurred",
9191
"dataKeyUndefined": "The data key \"{dataKey}\" does not exist in the response."
9292
},
93+
"validationErrors": {
94+
"utf8Name": "The name can be any string containing characters, letters, numbers, or the following characters: ., -, _, or ~. Do not use spaces."
95+
},
9396
"toggleModal": {
9497
"enable": {
9598
"title": "Enable {entityType}",

0 commit comments

Comments
 (0)