diff --git a/src/integrations/datadog/datadog.ts b/src/integrations/datadog/datadog.ts index 82720be95..dfebb0766 100644 --- a/src/integrations/datadog/datadog.ts +++ b/src/integrations/datadog/datadog.ts @@ -1,12 +1,12 @@ import { isLocalStudio } from '@/config/constants'; import { useOverallAuth } from '@/hooks/useAuth'; +import { translateUrlForDatadog } from '@/integrations/datadog/translateUrlForDatadog'; import { excludeFalsy } from '@/lib/arrays/excludeFalsy'; import { isLocalUser } from '@/lib/types/isLocalUser'; -import { currentUrlAfterHash } from '@/lib/urls/currentUrlAfterHash'; import { datadogRum } from '@datadog/browser-rum'; import { reactPlugin } from '@datadog/browser-rum-react'; -import { useLocation } from '@tanstack/react-router'; -import { useEffect } from 'react'; +import { useLocation, useParams } from '@tanstack/react-router'; +import { useEffect, useMemo } from 'react'; let initialized = false; const enabled = !import.meta.env.DEV && !isLocalStudio; @@ -42,6 +42,8 @@ export function useDatadog() { export function useOnRouteLoadTracker() { const location = useLocation(); const { user } = useOverallAuth(); + const params = useParams({ strict: false }); + const name = useMemo(() => translateUrlForDatadog(location.href, params), [location.href, params]); useEffect(() => { if (!enabled) { @@ -50,9 +52,9 @@ export function useOnRouteLoadTracker() { datadogRum.startView({ service: 'studio', version: import.meta.env.VITE_STUDIO_VERSION, - name: currentUrlAfterHash(), + name, }); - }, [location.href]); + }, [name]); useEffect(() => { if (!enabled) { diff --git a/src/integrations/datadog/translateUrlForDatadog.test.ts b/src/integrations/datadog/translateUrlForDatadog.test.ts new file mode 100644 index 000000000..7139a78ef --- /dev/null +++ b/src/integrations/datadog/translateUrlForDatadog.test.ts @@ -0,0 +1,76 @@ +import { describe, expect, it } from 'vitest'; +import { translateUrlForDatadog } from './translateUrlForDatadog'; + +describe('translateUrlForDatadog', () => { + it('removes query string parameters and ensures trailing slash', () => { + const href = '/orgs/acme/clu-123/browse/data/Users?like=this&and=that'; + const params = { + organizationId: 'acme', + clusterId: 'clu-123', + databaseName: 'data', + tableName: 'Users', + }; + const result = translateUrlForDatadog(href, params); + expect(result).toBe('/orgs/$organizationId/$clusterId/browse/$databaseName/$tableName/'); + }); + + it('adds a trailing slash if missing', () => { + const href = '/orgs/acme/'; + const result = translateUrlForDatadog(href, {}); + expect(result).toBe('/orgs/acme/'); + + const href2 = '/orgs/acme'; + const result2 = translateUrlForDatadog(href2, {}); + expect(result2).toBe('/orgs/acme/'); + }); + + it('replaces multiple parameters in the path', () => { + const href = '/orgs/org-1/clu-2/instances/ins-3/'; + const params = { + organizationId: 'org-1', + clusterId: 'clu-2', + instanceId: 'ins-3', + }; + const result = translateUrlForDatadog(href, params); + expect(result).toBe('/orgs/$organizationId/$clusterId/instances/$instanceId/'); + }); + + it('does not replace when param value not present', () => { + const href = '/orgs/org-1/other/'; + const params = { + organizationId: 'org-2', + }; + const result = translateUrlForDatadog(href, params); + expect(result).toBe('/orgs/org-1/other/'); + }); + + it('only replaces whole path segments bounded by slashes', () => { + const href = '/files/my-org-1-file/'; + const params = { + organizationId: 'org-1', + }; + const result = translateUrlForDatadog(href, params); + expect(result).toBe('/files/my-org-1-file/'); + }); + + it('handles URL without trailing slash but parameters at end', () => { + const href = '/dbs/data/tables/Users'; + const params = { + databaseName: 'data', + tableName: 'Users', + }; + const result = translateUrlForDatadog(href, params); + expect(result).toBe('/dbs/$databaseName/tables/$tableName/'); + }); + + it('works with hash fragments and query strings in full href', () => { + const href = 'https://example.com/app#/orgs/org-1/clu-2?x=1&y=2'; + const params = { + organizationId: 'org-1', + clusterId: 'clu-2', + }; + // The function simply splits on '?', so the query is dropped; the rest remains intact, including protocol and host. + const result = translateUrlForDatadog(href, params); + expect(result).toBe('https://example.com/app#/orgs/$organizationId/$clusterId/'); + }); +}); diff --git a/src/integrations/datadog/translateUrlForDatadog.ts b/src/integrations/datadog/translateUrlForDatadog.ts new file mode 100644 index 000000000..a386549a8 --- /dev/null +++ b/src/integrations/datadog/translateUrlForDatadog.ts @@ -0,0 +1,10 @@ +export function translateUrlForDatadog(href: string, params: Record): string { + let translated = href.split('?')[0]; + if (!translated.endsWith('/')) { + translated = translated + '/'; + } + for (const key in params) { + translated = translated.replace(`/${params[key]}/`, `/$${key}/`); + } + return translated; +}