D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
dashboard
/
components
/
SubMenu
/
Filename :
DashboardLinksDashboard.tsx
back
Copy
import { css, cx } from '@emotion/css'; import React, { useRef, useState, useLayoutEffect } from 'react'; import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { sanitize, sanitizeUrl } from '@grafana/data/src/text/sanitize'; import { selectors } from '@grafana/e2e-selectors'; import { Icon, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; import { getBackendSrv } from 'app/core/services/backend_srv'; import { DashboardSearchItem } from 'app/features/search/types'; import { getLinkSrv } from '../../../panel/panellinks/link_srv'; import { DashboardLink } from '../../state/DashboardModel'; interface Props { link: DashboardLink; linkInfo: { title: string; href: string }; dashboardUID: string; } export const DashboardLinksDashboard = (props: Props) => { const { link, linkInfo } = props; const listRef = useRef<HTMLUListElement>(null); const [dropdownCssClass, setDropdownCssClass] = useState('invisible'); const [opened, setOpened] = useState(0); const resolvedLinks = useResolvedLinks(props, opened); const styles = useStyles2(getStyles); useLayoutEffect(() => { setDropdownCssClass(getDropdownLocationCssClass(listRef.current)); }, [resolvedLinks]); if (link.asDropdown) { return ( <LinkElement link={link} key="dashlinks-dropdown" data-testid={selectors.components.DashboardLinks.dropDown}> <> <ToolbarButton onClick={() => setOpened(Date.now())} className={cx('gf-form-label gf-form-label--dashlink', styles.button)} data-placement="bottom" data-toggle="dropdown" aria-expanded={!!opened} aria-controls="dropdown-list" aria-haspopup="menu" > <Icon aria-hidden name="bars" className={styles.iconMargin} /> <span>{linkInfo.title}</span> </ToolbarButton> <ul id="dropdown-list" className={`dropdown-menu ${styles.dropdown} ${dropdownCssClass}`} role="menu" ref={listRef} > {resolvedLinks.length > 0 && resolvedLinks.map((resolvedLink, index) => { return ( <li role="none" key={`dashlinks-dropdown-item-${resolvedLink.uid}-${index}`}> <a role="menuitem" href={resolvedLink.url} target={link.targetBlank ? '_blank' : undefined} rel="noreferrer" data-testid={selectors.components.DashboardLinks.link} aria-label={`${resolvedLink.title} dashboard`} > {resolvedLink.title} </a> </li> ); })} </ul> </> </LinkElement> ); } return ( <> {resolvedLinks.length > 0 && resolvedLinks.map((resolvedLink, index) => { return ( <LinkElement link={link} key={`dashlinks-list-item-${resolvedLink.uid}-${index}`} data-testid={selectors.components.DashboardLinks.container} > <a className="gf-form-label gf-form-label--dashlink" href={resolvedLink.url} target={link.targetBlank ? '_blank' : undefined} rel="noreferrer" data-testid={selectors.components.DashboardLinks.link} aria-label={`${resolvedLink.title} dashboard`} > <Icon aria-hidden name="apps" style={{ marginRight: '4px' }} /> <span>{resolvedLink.title}</span> </a> </LinkElement> ); })} </> ); }; interface LinkElementProps { link: DashboardLink; key: string; children: JSX.Element; } const LinkElement = (props: LinkElementProps) => { const { link, children, ...rest } = props; return ( <div {...rest} className="gf-form"> {link.tooltip && <Tooltip content={link.tooltip}>{children}</Tooltip>} {!link.tooltip && <>{children}</>} </div> ); }; const useResolvedLinks = ({ link, dashboardUID }: Props, opened: number): ResolvedLinkDTO[] => { const { tags } = link; const result = useAsync(() => searchForTags(tags), [tags, opened]); if (!result.value) { return []; } return resolveLinks(dashboardUID, link, result.value); }; interface ResolvedLinkDTO { uid: string; url: string; title: string; } export async function searchForTags( tags: string[], dependencies: { getBackendSrv: typeof getBackendSrv } = { getBackendSrv } ): Promise<DashboardSearchItem[]> { const limit = 100; const searchHits: DashboardSearchItem[] = await dependencies.getBackendSrv().search({ tag: tags, limit }); return searchHits; } export function resolveLinks( dashboardUID: string, link: DashboardLink, searchHits: DashboardSearchItem[], dependencies: { getLinkSrv: typeof getLinkSrv; sanitize: typeof sanitize; sanitizeUrl: typeof sanitizeUrl } = { getLinkSrv, sanitize, sanitizeUrl, } ): ResolvedLinkDTO[] { return searchHits .filter((searchHit) => searchHit.uid !== dashboardUID) .map((searchHit) => { const uid = searchHit.uid; const title = dependencies.sanitize(searchHit.title); const resolvedLink = dependencies.getLinkSrv().getLinkUrl({ ...link, url: searchHit.url }); const url = dependencies.sanitizeUrl(resolvedLink); return { uid, title, url }; }); } function getDropdownLocationCssClass(element: HTMLElement | null) { if (!element) { return 'invisible'; } const wrapperPos = element.parentElement!.getBoundingClientRect(); const pos = element.getBoundingClientRect(); if (pos.width === 0) { return 'invisible'; } if (wrapperPos.left + pos.width + 10 > window.innerWidth) { return 'pull-left'; } else { return 'pull-right'; } } function getStyles(theme: GrafanaTheme2) { return { iconMargin: css({ marginRight: theme.spacing(0.5), }), dropdown: css({ maxWidth: 'max(30vw, 300px)', maxHeight: '70vh', overflowY: 'auto', a: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }, }), button: css({ color: theme.colors.text.primary, }), }; }