From 8c5f256f494792572c26bb1ac28ca8da4b4aacf2 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Fri, 20 Mar 2026 16:21:11 +0100 Subject: [PATCH] fix: simplify normalizeUrlOrigin to use standard URL API and fix lint Use URL() properties directly instead of manual string parsing. Remove real customer name from test data. Fix duplicate imports and prettier formatting. --- .../hooks/useAddLinkToNavigationMenuDraft.ts | 3 +-- .../spreadsheetImportGetUnicityTableHook.ts | 4 +--- .../utils/transform-links-value.util.ts | 10 ++-------- .../services/create-company.service.ts | 5 +---- .../url/__tests__/normalizeUrlOrigin.test.ts | 18 +++++++++--------- .../src/utils/url/normalizeUrlOrigin.ts | 14 ++++++-------- 6 files changed, 20 insertions(+), 34 deletions(-) diff --git a/packages/twenty-front/src/modules/navigation-menu-item/edit/link/hooks/useAddLinkToNavigationMenuDraft.ts b/packages/twenty-front/src/modules/navigation-menu-item/edit/link/hooks/useAddLinkToNavigationMenuDraft.ts index ae5ea3e0895..82d2f43247f 100644 --- a/packages/twenty-front/src/modules/navigation-menu-item/edit/link/hooks/useAddLinkToNavigationMenuDraft.ts +++ b/packages/twenty-front/src/modules/navigation-menu-item/edit/link/hooks/useAddLinkToNavigationMenuDraft.ts @@ -1,12 +1,11 @@ import { NavigationMenuItemType } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; +import { isDefined, normalizeUrl } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import type { NavigationMenuItem } from '~/generated-metadata/graphql'; import { DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK } from '@/navigation-menu-item/common/constants/NavigationMenuItemDefaultColorLink'; import { navigationMenuItemsDraftState } from '@/navigation-menu-item/common/states/navigationMenuItemsDraftState'; import { computeInsertIndexAndPosition } from '@/navigation-menu-item/common/utils/computeInsertIndexAndPosition'; -import { normalizeUrl } from 'twenty-shared/utils'; import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; export const useAddLinkToNavigationMenuDraft = () => { diff --git a/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/spreadsheetImportGetUnicityTableHook.ts b/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/spreadsheetImportGetUnicityTableHook.ts index 4c876344bb4..510d2c14c93 100644 --- a/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/spreadsheetImportGetUnicityTableHook.ts +++ b/packages/twenty-front/src/modules/object-record/spreadsheet-import/utils/spreadsheetImportGetUnicityTableHook.ts @@ -104,9 +104,7 @@ const getUniqueValues = ( .primaryLinkUrl, ) ) { - return normalizeUrlOrigin( - row?.[columnName]?.toString().trim() || '', - ); + return normalizeUrlOrigin(row?.[columnName]?.toString().trim() || ''); } return row?.[columnName]?.toString().trim().toLowerCase(); diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts index 7fc3ed7298f..03a7c8c3d0d 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/utils/transform-links-value.util.ts @@ -1,11 +1,7 @@ import { isNonEmptyString } from '@sniptt/guards'; import isEmpty from 'lodash.isempty'; import { type LinkMetadataNullable } from 'twenty-shared/types'; -import { - isDefined, - normalizeUrlOrigin, - parseJson, -} from 'twenty-shared/utils'; +import { isDefined, normalizeUrlOrigin, parseJson } from 'twenty-shared/utils'; import { removeEmptyLinks } from 'src/engine/core-modules/record-transformer/utils/remove-empty-links'; @@ -44,9 +40,7 @@ export const transformLinksValue = ( const processedSecondaryLinks = secondaryLinks?.map((link) => ({ ...link, - url: isDefined(link.url) - ? normalizeUrlOrigin(link.url) - : link.url, + url: isDefined(link.url) ? normalizeUrlOrigin(link.url) : link.url, })); return { diff --git a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts index ba8bf67e685..f6110770078 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts @@ -7,10 +7,7 @@ import { type ConnectedAccountProvider, type FieldActorSource, } from 'twenty-shared/types'; -import { - isDefined, - normalizeUrlOrigin, -} from 'twenty-shared/utils'; +import { isDefined, normalizeUrlOrigin } from 'twenty-shared/utils'; import { type DeepPartial, ILike } from 'typeorm'; import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service'; diff --git a/packages/twenty-shared/src/utils/url/__tests__/normalizeUrlOrigin.test.ts b/packages/twenty-shared/src/utils/url/__tests__/normalizeUrlOrigin.test.ts index f1e1a9685de..a6b83faf93d 100644 --- a/packages/twenty-shared/src/utils/url/__tests__/normalizeUrlOrigin.test.ts +++ b/packages/twenty-shared/src/utils/url/__tests__/normalizeUrlOrigin.test.ts @@ -39,19 +39,19 @@ describe('normalizeUrlOrigin', () => { expected: 'https://www.example.com/TEST#Hash', }, { - title: 'should preserve special characters in path', - input: 'https://test.test/edouard-ménard-22219837', - expected: 'https://test.test/edouard-ménard-22219837', + title: 'should percent-encode non-ASCII characters in path', + input: 'https://test.test/john-döe-22219837', + expected: 'https://test.test/john-d%C3%B6e-22219837', }, { title: 'should preserve already encoded special characters in path', - input: 'https://test.test/edouard-m%C3%A9nard-22219837', - expected: 'https://test.test/edouard-m%C3%A9nard-22219837', + input: 'https://test.test/john-d%C3%B6e-22219837', + expected: 'https://test.test/john-d%C3%B6e-22219837', }, { - title: 'should preserve special characters in query params', + title: 'should percent-encode non-ASCII characters in query params', input: 'https://example.com/path?name=José', - expected: 'https://example.com/path?name=José', + expected: 'https://example.com/path?name=Jos%C3%A9', }, { title: @@ -66,9 +66,9 @@ describe('normalizeUrlOrigin', () => { expected: 'https://example.com/test%2520name', }, { - title: 'should preserve special characters in hash fragments', + title: 'should percent-encode non-ASCII characters in hash fragments', input: 'https://example.com/path#frédéric', - expected: 'https://example.com/path#frédéric', + expected: 'https://example.com/path#fr%C3%A9d%C3%A9ric', }, { title: 'should keep encoded characters in hash fragments as-is', diff --git a/packages/twenty-shared/src/utils/url/normalizeUrlOrigin.ts b/packages/twenty-shared/src/utils/url/normalizeUrlOrigin.ts index 962573cd0c5..4a31c2ba67c 100644 --- a/packages/twenty-shared/src/utils/url/normalizeUrlOrigin.ts +++ b/packages/twenty-shared/src/utils/url/normalizeUrlOrigin.ts @@ -2,7 +2,8 @@ import { getURLSafely } from '@/utils/getURLSafely'; import { isDefined } from '@/utils/validation'; // Lowercases the URL origin (scheme + host) and removes a trailing slash. -// Preserves the raw path, query, and hash without decoding percent-encoded sequences. +// URL() already lowercases the origin and preserves percent-encoded sequences +// in the path, query, and hash (e.g. %2F stays %2F, %2520 stays %2520). export const normalizeUrlOrigin = (rawUrl: string) => { const url = getURLSafely(rawUrl); @@ -10,11 +11,8 @@ export const normalizeUrlOrigin = (rawUrl: string) => { return rawUrl; } - const lowercaseOrigin = url.origin.toLowerCase(); - const rawOrigin = rawUrl.match(/^[a-zA-Z][a-zA-Z\d+.-]*:\/\/[^/?#]+/)?.[0]; - const path = isDefined(rawOrigin) - ? rawUrl.slice(rawOrigin.length) - : url.pathname + url.search + url.hash; - - return (lowercaseOrigin + path).replace(/\/$/, ''); + return (url.origin + url.pathname + url.search + url.hash).replace( + /\/$/, + '', + ); };