From 0ab7488305bca6833f2a8801cf1a5720fc4cadb8 Mon Sep 17 00:00:00 2001 From: Ezequiel Victorero Date: Thu, 18 Sep 2025 12:37:21 -0300 Subject: [PATCH] ShortURL: Use RTK Query with the new k8s api (#111261) --- public/app/core/utils/shortLinks.test.ts | 29 +++++++++- public/app/core/utils/shortLinks.ts | 70 +++++++++++++++--------- 2 files changed, 73 insertions(+), 26 deletions(-) diff --git a/public/app/core/utils/shortLinks.test.ts b/public/app/core/utils/shortLinks.test.ts index d07c4940680..a37c55ba5e6 100644 --- a/public/app/core/utils/shortLinks.test.ts +++ b/public/app/core/utils/shortLinks.test.ts @@ -19,6 +19,20 @@ jest.mock('@grafana/runtime', () => ({ }, })); +jest.mock('app/store/store', () => ({ + dispatch: jest.fn((action) => { + // Return the mocked result directly + return Promise.resolve({ + data: { + metadata: { + name: 'bewyw48durgu8d', + namespace: '1', + }, + }, + }); + }), +})); + beforeEach(() => { Object.assign(navigator, { clipboard: { @@ -29,6 +43,9 @@ beforeEach(() => { document.execCommand = jest.fn(); config.featureToggles.useKubernetesShortURLsAPI = false; + + // Clear any caches between tests + jest.clearAllMocks(); }); describe('createShortLink', () => { @@ -40,9 +57,19 @@ describe('createShortLink', () => { describe('createShortLink using k8s API', () => { it('creates short link', async () => { + // Mock window.location for k8s API test + const mockLocation = { + protocol: 'https:', + host: 'www.test.grafana.com', + }; + Object.defineProperty(window, 'location', { + value: mockLocation, + writable: true, + }); + config.featureToggles.useKubernetesShortURLsAPI = true; const shortUrl = await createShortLink('d/edhmipji89b0gb/welcome?orgId=1&from=now-6h&to=now&timezone=browser'); - expect(shortUrl).toBe('www.test.grafana.com/goto/bewyw48durgu8d?orgId=1'); + expect(shortUrl).toBe('https://www.test.grafana.com/goto/bewyw48durgu8d?orgId=1'); }); }); diff --git a/public/app/core/utils/shortLinks.ts b/public/app/core/utils/shortLinks.ts index 573cfff2627..b9187f7ca4c 100644 --- a/public/app/core/utils/shortLinks.ts +++ b/public/app/core/utils/shortLinks.ts @@ -11,7 +11,8 @@ import { getDashboardUrl } from 'app/features/dashboard-scene/utils/getDashboard import { dispatch } from 'app/store/store'; import { ShortURL } from '../../../../apps/shorturl/plugin/src/generated/shorturl/v1alpha1/shorturl_object_gen'; -import { BASE_URL as k8sShortURLBaseAPI } from '../../api/clients/shorturl/v1alpha1/baseAPI'; +import { generatedAPI } from '../../api/clients/shorturl/v1alpha1/endpoints.gen'; +import { extractErrorMessage } from '../../api/utils'; import { ShareLinkConfiguration } from '../../features/dashboard-scene/sharing/ShareButton/utils'; import { copyStringToClipboard } from './explore'; @@ -32,30 +33,48 @@ function getRelativeURLPath(url: string) { return path.startsWith('/') ? path.substring(1, path.length) : path; } -export const createShortLink = memoizeOne(async function (path: string) { +// Memoized legacy API call - preserves original behavior +const createShortLinkLegacy = memoizeOne(async (path: string): Promise => { + const shortLink = await getBackendSrv().post(`/api/short-urls`, { + path: getRelativeURLPath(path), + }); + return shortLink.url; +}); + +export const createShortLink = async function (path: string) { try { if (config.featureToggles.useKubernetesShortURLsAPI) { - // TODO: this is not ideal, we should use the RTK API but we can't call a hook from here and - // this util function is being called from several places, will require a bigger refactor including some code that - // is deprecated. - const k8sShortUrl: ShortURL = await getBackendSrv().post(`${k8sShortURLBaseAPI}/shorturls`, { - spec: { - path: getRelativeURLPath(path), - }, - }); - return buildShortUrl(k8sShortUrl); + // Use RTK API - it handles caching/failures/retries automatically + const result = await dispatch( + generatedAPI.endpoints.createShortUrl.initiate({ + shortUrl: { + spec: { + path: getRelativeURLPath(path), + }, + }, + }) + ); + + if ('data' in result && result.data) { + return buildShortUrl(result.data); + } + + if ('error' in result) { + const errorMessage = extractErrorMessage(result.error); + throw new Error(errorMessage || 'Failed to create short URL'); + } + + throw new Error('Failed to create short URL'); } else { - // Old short URL API - const shortLink = await getBackendSrv().post(`/api/short-urls`, { - path: getRelativeURLPath(path), - }); - return shortLink.url; + // Old API - use memoized function (preserves original behavior) + return await createShortLinkLegacy(path); } } catch (err) { console.error('Error when creating shortened link: ', err); dispatch(notifyApp(createErrorNotification('Error generating shortened link'))); + throw err; // Re-throw so callers know it failed } -}); +}; /** * Creates a ClipboardItem for the shortened link. This is used due to clipboard issues in Safari after making async calls. @@ -70,17 +89,18 @@ const createShortLinkClipboardItem = (path: string) => { }; export const createAndCopyShortLink = async (path: string) => { - if (typeof ClipboardItem !== 'undefined' && navigator.clipboard.write) { - navigator.clipboard.write([createShortLinkClipboardItem(path)]); - dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard'))); - } else { - const shortLink = await createShortLink(path); - if (shortLink) { - copyStringToClipboard(shortLink); + try { + if (typeof ClipboardItem !== 'undefined' && navigator.clipboard.write) { + await navigator.clipboard.write([createShortLinkClipboardItem(path)]); dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard'))); } else { - dispatch(notifyApp(createErrorNotification('Error generating shortened link'))); + const shortLink = await createShortLink(path); + copyStringToClipboard(shortLink); + dispatch(notifyApp(createSuccessNotification('Shortened link copied to clipboard'))); } + } catch (error) { + // createShortLink already handles error notifications, just log + console.error('Error in createAndCopyShortLink:', error); } };