import { take } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { DataLinkBuiltInVars, DateTime, InterpolateFunction, PanelProps, textUtil, UrlQueryValue, urlUtil, } from '@grafana/data'; import { CustomScrollbar, useStyles2, IconButton } from '@grafana/ui'; import { getConfig } from 'app/core/config'; import { appEvents } from 'app/core/core'; import { useBusEvent } from 'app/core/hooks/useBusEvent'; import { setStarred } from 'app/core/reducers/navBarTree'; import { getBackendSrv } from 'app/core/services/backend_srv'; import impressionSrv from 'app/core/services/impression_srv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardSearchItem } from 'app/features/search/types'; import { VariablesChanged } from 'app/features/variables/types'; import { useDispatch } from 'app/types'; import { Options } from './panelcfg.gen'; import { getStyles } from './styles'; type Dashboard = DashboardSearchItem & { id?: number; isSearchResult?: boolean; isRecent?: boolean }; interface DashboardGroup { show: boolean; header: string; dashboards: Dashboard[]; } async function fetchDashboards(options: Options, replaceVars: InterpolateFunction) { let starredDashboards: Promise = Promise.resolve([]); if (options.showStarred) { const params = { limit: options.maxItems, starred: 'true' }; starredDashboards = getBackendSrv().search(params); } let recentDashboards: Promise = Promise.resolve([]); let dashUIDs: string[] = []; if (options.showRecentlyViewed) { let uids = await impressionSrv.getDashboardOpened(); dashUIDs = take(uids, options.maxItems); recentDashboards = getBackendSrv().search({ dashboardUIDs: dashUIDs, limit: options.maxItems }); } let searchedDashboards: Promise = Promise.resolve([]); if (options.showSearch) { const uid = options.folderUID === '' ? 'general' : options.folderUID; const params = { limit: options.maxItems, query: replaceVars(options.query, {}, 'text'), folderUIDs: uid, tag: options.tags.map((tag: string) => replaceVars(tag, {}, 'text')), type: 'dash-db', }; searchedDashboards = getBackendSrv().search(params); } const [starred, searched, recent] = await Promise.all([starredDashboards, searchedDashboards, recentDashboards]); // We deliberately deal with recent dashboards first so that the order of dash IDs is preserved let dashMap = new Map(); for (const dashUID of dashUIDs) { const dash = recent.find((d) => d.uid === dashUID); if (dash) { dashMap.set(dashUID, { ...dash, isRecent: true }); } } searched.forEach((dash) => { if (!dash.uid) { return; } if (dashMap.has(dash.uid)) { dashMap.get(dash.uid)!.isSearchResult = true; } else { dashMap.set(dash.uid, { ...dash, isSearchResult: true }); } }); starred.forEach((dash) => { if (!dash.uid) { return; } if (dashMap.has(dash.uid)) { dashMap.get(dash.uid)!.isStarred = true; } else { dashMap.set(dash.uid, { ...dash, isStarred: true }); } }); return dashMap; } export function DashList(props: PanelProps) { const [dashboards, setDashboards] = useState(new Map()); const dispatch = useDispatch(); useEffect(() => { fetchDashboards(props.options, props.replaceVariables).then((dashes) => { setDashboards(dashes); }); }, [props.options, props.replaceVariables, props.renderCounter]); const toggleDashboardStar = async (e: React.SyntheticEvent, dash: Dashboard) => { const { uid, title, url } = dash; e.preventDefault(); e.stopPropagation(); const isStarred = await getDashboardSrv().starDashboard(dash.uid, dash.isStarred); const updatedDashboards = new Map(dashboards); updatedDashboards.set(dash?.uid ?? '', { ...dash, isStarred }); setDashboards(updatedDashboards); dispatch(setStarred({ id: uid ?? '', title, url, isStarred })); }; const [starredDashboards, recentDashboards, searchedDashboards] = useMemo(() => { const dashboardList = [...dashboards.values()]; return [ dashboardList.filter((dash) => dash.isStarred).sort((a, b) => a.title.localeCompare(b.title)), dashboardList.filter((dash) => dash.isRecent), dashboardList.filter((dash) => dash.isSearchResult).sort((a, b) => a.title.localeCompare(b.title)), ]; }, [dashboards]); const { showStarred, showRecentlyViewed, showHeadings, showSearch } = props.options; const dashboardGroups: DashboardGroup[] = [ { header: 'Starred dashboards', dashboards: starredDashboards, show: showStarred, }, { header: 'Recently viewed dashboards', dashboards: recentDashboards, show: showRecentlyViewed, }, { header: 'Search', dashboards: searchedDashboards, show: showSearch, }, ]; const css = useStyles2(getStyles); const urlParams = useDashListUrlParams(props); const renderList = (dashboards: Dashboard[]) => (
    {dashboards.map((dash) => { let url = dash.url; url = urlUtil.appendQueryToUrl(url, urlParams); url = getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url); return (
  • {dash.title} {dash.folderTitle &&
    {dash.folderTitle}
    }
    toggleDashboardStar(e, dash)} />
  • ); })}
); return ( {dashboardGroups.map( ({ show, header, dashboards }, i) => show && (
{showHeadings &&
{header}
} {renderList(dashboards)}
) )}
); } function useDashListUrlParams(props: PanelProps) { // We don't care about the payload just want to get re-render when this event is published useBusEvent(appEvents, VariablesChanged); let params: { [key: string]: string | DateTime | UrlQueryValue } = {}; if (props.options.keepTime) { params[`\$${DataLinkBuiltInVars.keepTime}`] = true; } if (props.options.includeVars) { params[`\$${DataLinkBuiltInVars.includeVars}`] = true; } const urlParms = props.replaceVariables(urlUtil.toUrlParams(params)); return urlParms; }