123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- import { css, cx } from '@emotion/css';
- import React, { useRef, useState, useLayoutEffect } from 'react';
- import { useAsync } from 'react-use';
- 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 { DashboardSearchHit } 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 };
- dashboardId: any;
- }
- export const DashboardLinksDashboard: React.FC<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 buttonStyle = useStyles2(
- (theme) =>
- css`
- color: ${theme.colors.text.primary};
- `
- );
- 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', buttonStyle)}
- data-placement="bottom"
- data-toggle="dropdown"
- aria-expanded={!!opened}
- aria-controls="dropdown-list"
- aria-haspopup="menu"
- >
- <Icon aria-hidden name="bars" style={{ marginRight: '4px' }} />
- <span>{linkInfo.title}</span>
- </ToolbarButton>
- <ul id="dropdown-list" className={`dropdown-menu ${dropdownCssClass}`} role="menu" ref={listRef}>
- {resolvedLinks.length > 0 &&
- resolvedLinks.map((resolvedLink, index) => {
- return (
- <li role="none" key={`dashlinks-dropdown-item-${resolvedLink.id}-${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.id}-${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: React.FC<LinkElementProps> = (props) => {
- 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, dashboardId }: Props, opened: number): ResolvedLinkDTO[] => {
- const { tags } = link;
- const result = useAsync(() => searchForTags(tags), [tags, opened]);
- if (!result.value) {
- return [];
- }
- return resolveLinks(dashboardId, link, result.value);
- };
- interface ResolvedLinkDTO {
- id: any;
- url: string;
- title: string;
- }
- export async function searchForTags(
- tags: any[],
- dependencies: { getBackendSrv: typeof getBackendSrv } = { getBackendSrv }
- ): Promise<DashboardSearchHit[]> {
- const limit = 100;
- const searchHits: DashboardSearchHit[] = await dependencies.getBackendSrv().search({ tag: tags, limit });
- return searchHits;
- }
- export function resolveLinks(
- dashboardId: any,
- link: DashboardLink,
- searchHits: DashboardSearchHit[],
- dependencies: { getLinkSrv: typeof getLinkSrv; sanitize: typeof sanitize; sanitizeUrl: typeof sanitizeUrl } = {
- getLinkSrv,
- sanitize,
- sanitizeUrl,
- }
- ): ResolvedLinkDTO[] {
- return searchHits
- .filter((searchHit) => searchHit.id !== dashboardId)
- .map((searchHit) => {
- const id = searchHit.id;
- const title = dependencies.sanitize(searchHit.title);
- const resolvedLink = dependencies.getLinkSrv().getLinkUrl({ ...link, url: searchHit.url });
- const url = dependencies.sanitizeUrl(resolvedLink);
- return { id, 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';
- }
- }
|