fix: unable to edit panels when dashboard path has trailing slash (#9816)

* fix: unable to edit panels when dashboard path has trailing slash

* fix: name better

* fix: add tests

* fix: resolve comment

* fix: itch - variable rename

* fix: typo
This commit is contained in:
Ashwin Bhatkal
2025-12-18 09:42:43 +05:30
committed by GitHub
parent bb4d6117ac
commit 89b188f73d
3 changed files with 161 additions and 1 deletions

View File

@@ -35,6 +35,7 @@ import { SuccessResponse, Warning } from 'types/api';
import { Widgets } from 'types/api/dashboard/getAll';
import APIError from 'types/api/error';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { buildAbsolutePath } from 'utils/app';
import { errorTooltipPosition } from './config';
import { MENUITEM_KEYS_VS_LABELS, MenuItemKeys } from './contants';
@@ -87,7 +88,10 @@ function WidgetHeader({
QueryParams.compositeQuery,
encodeURIComponent(JSON.stringify(widget.query)),
);
const generatedUrl = `${window.location.pathname}/new?${urlQuery}`;
const generatedUrl = buildAbsolutePath({
relativePath: 'new',
urlQueryString: urlQuery.toString(),
});
safeNavigate(generatedUrl);
}, [safeNavigate, urlQuery, widget.id, widget.panelTypes, widget.query]);

View File

@@ -0,0 +1,126 @@
import { buildAbsolutePath } from '../app';
const BASE_PATH = '/some-base-path';
describe('buildAbsolutePath', () => {
const originalLocation = window.location;
afterEach(() => {
Object.defineProperty(window, 'location', {
writable: true,
value: originalLocation,
});
});
const mockLocation = (pathname: string): void => {
Object.defineProperty(window, 'location', {
writable: true,
value: {
pathname,
href: `http://localhost:8080${pathname}`,
origin: 'http://localhost:8080',
protocol: 'http:',
host: 'localhost',
hostname: 'localhost',
port: '',
search: '',
hash: '',
},
});
};
describe('when base path ends with a forward slash', () => {
beforeEach(() => {
mockLocation(`${BASE_PATH}/`);
});
it('should build absolute path without query string', () => {
const result = buildAbsolutePath({ relativePath: 'users' });
expect(result).toBe(`${BASE_PATH}/users`);
});
it('should build absolute path with query string', () => {
const result = buildAbsolutePath({
relativePath: 'users',
urlQueryString: 'id=123&sort=name',
});
expect(result).toBe(`${BASE_PATH}/users?id=123&sort=name`);
});
it('should handle nested relative paths', () => {
const result = buildAbsolutePath({ relativePath: 'users/profile/settings' });
expect(result).toBe(`${BASE_PATH}/users/profile/settings`);
});
});
describe('when base path does not end with a forward slash', () => {
beforeEach(() => {
mockLocation(`${BASE_PATH}`);
});
it('should append forward slash and build absolute path', () => {
const result = buildAbsolutePath({ relativePath: 'users' });
expect(result).toBe(`${BASE_PATH}/users`);
});
it('should append forward slash and build absolute path with query string', () => {
const result = buildAbsolutePath({
relativePath: 'users',
urlQueryString: 'filter=active',
});
expect(result).toBe(`${BASE_PATH}/users?filter=active`);
});
});
describe('edge cases', () => {
it('should handle empty relative path', () => {
mockLocation(`${BASE_PATH}/`);
const result = buildAbsolutePath({ relativePath: '' });
expect(result).toBe(`${BASE_PATH}/`);
});
it('should handle query string with empty relative path', () => {
mockLocation(`${BASE_PATH}/`);
const result = buildAbsolutePath({
relativePath: '',
urlQueryString: 'search=test',
});
expect(result).toBe(`${BASE_PATH}/?search=test`);
});
it('should handle relative path starting with forward slash', () => {
mockLocation(`${BASE_PATH}/`);
const result = buildAbsolutePath({ relativePath: '/users' });
expect(result).toBe(`${BASE_PATH}/users`);
});
it('should handle complex query strings', () => {
mockLocation(`${BASE_PATH}/dashboard`);
const result = buildAbsolutePath({
relativePath: 'reports',
urlQueryString: 'date=2024-01-01&type=summary&format=pdf',
});
expect(result).toBe(
`${BASE_PATH}/dashboard/reports?date=2024-01-01&type=summary&format=pdf`,
);
});
it('should handle undefined query string', () => {
mockLocation(`${BASE_PATH}/`);
const result = buildAbsolutePath({
relativePath: 'users',
urlQueryString: undefined,
});
expect(result).toBe(`${BASE_PATH}/users`);
});
it('should handle empty query string', () => {
mockLocation(`${BASE_PATH}/`);
const result = buildAbsolutePath({
relativePath: 'users',
urlQueryString: '',
});
expect(result).toBe(`${BASE_PATH}/users`);
});
});
});

View File

@@ -38,3 +38,33 @@ export function isIngestionActive(data: any): boolean {
return parseInt(value, 10) > 0;
}
/**
* Builds an absolute path by combining the current page's pathname with a relative path.
*
* @param {Object} params - The parameters for building the absolute path
* @param {string} params.relativePath - The relative path to append to the current pathname
* @param {string} [params.urlQueryString] - Optional query string to append to the final path (without leading '?')
*
* @returns {string} The constructed absolute path, optionally with query string
*/
export function buildAbsolutePath({
relativePath,
urlQueryString,
}: {
relativePath: string;
urlQueryString?: string;
}): string {
const { pathname } = window.location;
// ensure base path always ends with a forward slash
const basePath = pathname.endsWith('/') ? pathname : `${pathname}/`;
// handle relative path starting with a forward slash
const normalizedRelativePath = relativePath.startsWith('/')
? relativePath.slice(1)
: relativePath;
const absolutePath = basePath + normalizedRelativePath;
return urlQueryString ? `${absolutePath}?${urlQueryString}` : absolutePath;
}