diff --git a/ui/apps/pmm-compat/src/lib/utils/variables.test.ts b/ui/apps/pmm-compat/src/lib/utils/variables.test.ts new file mode 100644 index 00000000000..75067e2ad4f --- /dev/null +++ b/ui/apps/pmm-compat/src/lib/utils/variables.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeEach, jest } from '@jest/globals'; +import { getLinkWithVariables, shouldIncludeVars } from './variables'; + +const prefixes = { + grafana: '/graph', + pmm: '/pmm-ui/next', +}; + +const dashboards = { + pg: '/d/postgresql-instance-overview/postgresql-instances-overview', + pgSummary: '/d/postgresql-instance-summary/postgresql-instance-summary', + mysql: '/d/mysql-instance-summary/mysql-instance-summary', + node: '/d/node-overview/node-overview', +}; + +const mockLocation = (pathname: string) => { + Object.defineProperty(window, 'location', { + value: { + pathname, + origin: 'https://percona.com', + }, + writable: true, + }); +}; + +describe('getLinkWithVariables', () => { + beforeEach(() => { + mockLocation('/percona.com'); + }); + + it('should return the same url if it is not a dashboard url', () => { + const url = 'https://percona.com'; + const result = getLinkWithVariables(url); + expect(result).toBe(url); + }); +}); + +describe('shouldIncludeVars', () => { + it('should handle different prefixes', () => { + const urls = [ + dashboards.pg, + `${prefixes.grafana}${dashboards.pg}`, + `${prefixes.pmm}${prefixes.grafana}${dashboards.pg}`, + ]; + + urls.forEach((url) => { + mockLocation(dashboards.pg); + const result = shouldIncludeVars(url); + expect(result).toBe(true); + }); + }); + + it('should return true if the db type matches the current one', () => { + mockLocation(dashboards.pg); + const result = shouldIncludeVars(dashboards.pgSummary); + expect(result).toBe(true); + }); + + it('should return true if the target db type is node', () => { + mockLocation(dashboards.pg); + const result = shouldIncludeVars(dashboards.node); + expect(result).toBe(true); + }); + + it('should return false if the target db type is not the same as the current one', () => { + mockLocation(dashboards.pg); + const result = shouldIncludeVars(dashboards.mysql); + expect(result).toBe(false); + }); + + it('should return false if current db type is node and target db type is not node', () => { + mockLocation(dashboards.node); + const result = shouldIncludeVars(dashboards.pg); + expect(result).toBe(false); + }); +}); diff --git a/ui/apps/pmm-compat/src/lib/utils/variables.ts b/ui/apps/pmm-compat/src/lib/utils/variables.ts index 28441337cbb..f77a35efaac 100644 --- a/ui/apps/pmm-compat/src/lib/utils/variables.ts +++ b/ui/apps/pmm-compat/src/lib/utils/variables.ts @@ -25,7 +25,7 @@ export const getLinkWithVariables = (url?: string): string => { url: url, keepTime: true, // Check if the DB type matches the current one used - includeVars: checkDbType(url), + includeVars: shouldIncludeVars(url), asDropdown: false, icon: '', tags: [], @@ -42,12 +42,39 @@ export const getLinkWithVariables = (url?: string): string => { const isDashboardUrl = (url?: string) => url?.includes('/d/'); -const checkDbType = (url: string): boolean => { - const currentDB = window.location.pathname?.split('/')[3]?.split('-')[0]; - const targetDB = url?.split('/')[3]?.split('-')[0]; +export const shouldIncludeVars = (url: string): boolean => { + const currentDB = getDbType(window.location.pathname); + const targetDB = getDbType(url); + + if (currentDB === undefined || targetDB === undefined) { + return false; + } // enable variable sharing between same db types and db type -> os/node - return (currentDB !== undefined && currentDB === targetDB) || targetDB === 'node'; + return currentDB === targetDB || targetDB === 'node'; +}; + +const getDbType = (url: string): string | undefined => { + const pathname = new URL(url, window.location.origin).pathname; + // normalize to the dashboard uid + const pathParts = pathname + .replace('/pmm-ui', '') + .replace('/next', '') + .replace('/graph', '') + .replace('/d/', '') + .split('/'); + + if (pathParts.length < 1 || !pathParts[0]) { + return undefined; + } + + const dashboardUid = pathParts[0]; + + if (dashboardUid.includes('-')) { + return dashboardUid.split('-')[0]; + } + + return dashboardUid; }; const cleanupVariables = (urlWithLinks: string) => { diff --git a/ui/apps/pmm/src/components/sidebar/nav-item/NavItem.tsx b/ui/apps/pmm/src/components/sidebar/nav-item/NavItem.tsx index 9099e9e159e..56c71706d6c 100644 --- a/ui/apps/pmm/src/components/sidebar/nav-item/NavItem.tsx +++ b/ui/apps/pmm/src/components/sidebar/nav-item/NavItem.tsx @@ -1,6 +1,5 @@ import { useLinkWithVariables } from 'hooks/utils/useLinkWithVariables'; import { FC, useCallback, useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; import { NavItemProps } from './NavItem.types'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import { getLinkProps, hasChildMatch, shouldShowBadge } from './NavItem.utils'; @@ -33,13 +32,13 @@ const NavItem: FC = ({ [activeItem, item] ); const [open, setIsOpen] = useState(active); - const url = useLinkWithVariables(item.url); + const url = useLinkWithVariables( + item.children?.length ? item.children[0].url : item.url + ); const linkProps = getLinkProps(item, url); const theme = useTheme(); const styles = getStyles(theme, active, drawerOpen, level); - const children = item.children?.filter((i) => !i.hidden); const dataTestid = `navitem-${item.id}`; - const navigate = useNavigate(); const showBadge = shouldShowBadge(item, open); useEffect(() => { @@ -59,19 +58,13 @@ const NavItem: FC = ({ }, [drawerOpen]); const handleOpenCollapsible = () => { - const firstChild = (item.children || [])[0]; - // prevent opening when sidebar collapsed if (drawerOpen) { setIsOpen(true); } - - if (firstChild?.url) { - navigate(firstChild.url); - } }; - if (children?.length) { + if (item.children?.length) { return ( <> = ({ level === 0 && styles.navItemRootCollapsible, ]} onClick={handleOpenCollapsible} + {...linkProps} data-testid={dataTestid} data-navlevel={level} > @@ -144,7 +138,7 @@ const NavItem: FC = ({ data-testid={`${dataTestid}-collapse`} > - {children.map((item) => ( + {item.children.map((item) => (