diff --git a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx index 29a4dce6557..ebfbd1d0899 100644 --- a/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx +++ b/public/app/features/browse-dashboards/components/RecentlyViewedDashboards.tsx @@ -4,7 +4,9 @@ import { useAsync } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { t, Trans } from '@grafana/i18n'; import { evaluateBooleanFlag } from '@grafana/runtime/internal'; -import { CollapsableSection, Link, Spinner, Text, useStyles2 } from '@grafana/ui'; +import { CollapsableSection, Grid, Spinner, Text, useStyles2 } from '@grafana/ui'; +import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo'; +import { DashListItem } from 'app/plugins/panel/dashlist/DashListItem'; import { getRecentlyViewedDashboards } from './utils'; @@ -19,6 +21,7 @@ export function RecentlyViewedDashboards() { } return getRecentlyViewedDashboards(MAX_RECENT); }, []); + const { foldersByUid } = useDashboardLocationInfo(recentDashboards.length > 0); if (!evaluateBooleanFlag('recentlyViewedDashboards', false)) { return null; @@ -43,35 +46,53 @@ export function RecentlyViewedDashboards() { {t('browse-dashboards.recently-viewed.empty', 'Nothing viewed yet')} )} - {/* TODO: implement actual card content */} {!loading && recentDashboards.length > 0 && ( - <> - {recentDashboards.map((dash) => ( -
- {dash.name} -
- ))} - + )} ); } const getStyles = (theme: GrafanaTheme2) => { - const accent = theme.visualization.getColorByName('purple'); // or your own hex - return { title: css({ - background: `linear-gradient(90deg, ${accent} 0%, #e478eaff 100%)`, - WebkitTextFillColor: 'transparent', - backgroundClip: 'text', - color: 'transparent', '& button svg': { - color: accent, + color: theme.colors.primary.text, + }, + h3: { + background: `linear-gradient(90deg, ${theme.colors.primary.text} 0%, ${theme.colors.secondary.text} 100%)`, + WebkitTextFillColor: 'transparent', + backgroundClip: 'text', + color: 'transparent', }, }), content: css({ paddingTop: theme.spacing(0), }), + list: css({ + listStyle: 'none', + margin: 0, + padding: 0, + display: 'grid', + gap: theme.spacing(2), + }), + listItem: css({ + margin: 0, + }), }; }; diff --git a/public/app/features/search/hooks/useDashboardLocationInfo.tsx b/public/app/features/search/hooks/useDashboardLocationInfo.tsx new file mode 100644 index 00000000000..10c4f80c62e --- /dev/null +++ b/public/app/features/search/hooks/useDashboardLocationInfo.tsx @@ -0,0 +1,29 @@ +import { useAsync } from 'react-use'; + +import { getGrafanaSearcher } from 'app/features/search/service/searcher'; +import { LocationInfo } from 'app/features/search/service/types'; + +/** + * + * @description Hook to fetch dashboard location info (folders). + * @returns An object containing a mapping of folder UIDs to LocationInfo, loading state, and error state. + */ +export function useDashboardLocationInfo(enabled: boolean) { + const searcher = getGrafanaSearcher(); + const { + value: foldersByUid, + loading, + error, + } = useAsync(async (): Promise> => { + if (!enabled) { + return {}; + } + return searcher.getLocationInfo(); + }, [enabled, searcher]); + + return { + foldersByUid: foldersByUid ?? {}, + loading, + error, + }; +} diff --git a/public/app/plugins/panel/dashlist/DashList.tsx b/public/app/plugins/panel/dashlist/DashList.tsx index dab66730379..ff62a9ffd0a 100644 --- a/public/app/plugins/panel/dashlist/DashList.tsx +++ b/public/app/plugins/panel/dashlist/DashList.tsx @@ -4,18 +4,18 @@ import { useThrottle } from 'react-use'; import { InterpolateFunction, PanelProps, textUtil } from '@grafana/data'; import { t } from '@grafana/i18n'; -import { useStyles2, ScrollContainer, Box, Text, EmptyState, Link } from '@grafana/ui'; +import { ScrollContainer, Box, Text, EmptyState } from '@grafana/ui'; import { getConfig } from 'app/core/config'; import impressionSrv from 'app/core/services/impression_srv'; +import { useDashboardLocationInfo } from 'app/features/search/hooks/useDashboardLocationInfo'; import { getGrafanaSearcher } from 'app/features/search/service/searcher'; -import { DashboardQueryResult, LocationInfo, QueryResponse, SearchQuery } from 'app/features/search/service/types'; -import { StarToolbarButton } from 'app/features/stars/StarToolbarButton'; +import { DashboardQueryResult, QueryResponse, SearchQuery } from 'app/features/search/service/types'; +import { DashListItem } from './DashListItem'; import { Options } from './panelcfg.gen'; -import { getStyles } from './styles'; import { useDashListUrlParams } from './utils'; -type Dashboard = DashboardQueryResult & { +export type Dashboard = DashboardQueryResult & { isSearchResult?: boolean; isRecent?: boolean; isStarred?: boolean; @@ -107,15 +107,10 @@ async function fetchDashboards(options: Options, replaceVars: InterpolateFunctio return dashMap; } -async function fetchDashboardFolders() { - return getGrafanaSearcher().getLocationInfo(); -} - const collator = new Intl.Collator(); export function DashList(props: PanelProps) { const [dashboards, setDashboards] = useState(new Map()); - const [foldersTitleMap, setFoldersTitleMap] = useState>({}); const throttledRenderCount = useThrottle(props.renderCounter, 5000); @@ -125,13 +120,7 @@ export function DashList(props: PanelProps) { }); }, [props.options, props.replaceVariables, throttledRenderCount]); - useEffect(() => { - if (props.options.showFolderNames && dashboards.size > 0) { - fetchDashboardFolders().then((locationInfo) => { - setFoldersTitleMap(locationInfo); - }); - } - }, [props.options.showFolderNames, dashboards]); + const { foldersByUid } = useDashboardLocationInfo(props.options.showFolderNames && dashboards.size > 0); const [starredDashboards, recentDashboards, searchedDashboards] = useMemo(() => { const dashboardList = [...dashboards.values()]; @@ -185,7 +174,6 @@ export function DashList(props: PanelProps) { setDashboards(updatedDashboards); }; - const css = useStyles2(getStyles); const urlParams = useDashListUrlParams(props); const renderList = (dashboards: Dashboard[]) => ( @@ -194,26 +182,17 @@ export function DashList(props: PanelProps) { let url = dash.url + urlParams; url = getConfig().disableSanitizeHtml ? url : textUtil.sanitizeUrl(url); - const locationInfo = showFolderNames && dash.location ? foldersTitleMap[dash.location] : undefined; + const locationInfo = showFolderNames && dash.location ? foldersByUid[dash.location] : undefined; return (
  • -
    - - {dash.name} - {showFolderNames && locationInfo && ( - - {locationInfo?.name} - - )} - - -
    +
  • ); })} diff --git a/public/app/plugins/panel/dashlist/DashListItem.tsx b/public/app/plugins/panel/dashlist/DashListItem.tsx new file mode 100644 index 00000000000..9ef0ca64c3d --- /dev/null +++ b/public/app/plugins/panel/dashlist/DashListItem.tsx @@ -0,0 +1,64 @@ +import { Box, Card, Icon, Link, Stack, Text, useStyles2 } from '@grafana/ui'; +import { LocationInfo } from 'app/features/search/service/types'; +import { StarToolbarButton } from 'app/features/stars/StarToolbarButton'; + +import { Dashboard } from './DashList'; +import { getStyles } from './styles'; + +interface Props { + dashboard: Dashboard; + url: string; + showFolderNames: boolean; + locationInfo?: LocationInfo; + layoutMode: 'list' | 'card'; + onStarChange?: (id: string, isStarred: boolean) => void; +} +export function DashListItem({ dashboard, url, showFolderNames, locationInfo, layoutMode, onStarChange }: Props) { + const css = useStyles2(getStyles); + + return ( + <> + {layoutMode === 'list' ? ( +
    + + {dashboard.name} + {showFolderNames && locationInfo && ( + + {locationInfo?.name} + + )} + + +
    + ) : ( + + + {dashboard.name} + + + + {showFolderNames && locationInfo && ( + + + )} + + )} + + ); +} diff --git a/public/app/plugins/panel/dashlist/styles.ts b/public/app/plugins/panel/dashlist/styles.ts index 56becdd0684..5e725daaf2d 100644 --- a/public/app/plugins/panel/dashlist/styles.ts +++ b/public/app/plugins/panel/dashlist/styles.ts @@ -1,8 +1,13 @@ import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { GrafanaTheme2, colorManipulator } from '@grafana/data'; export const getStyles = (theme: GrafanaTheme2) => { + const gradient = `linear-gradient( + 90deg, + ${colorManipulator.alpha(theme.colors.primary.text, 0.1)} 0%, + ${colorManipulator.alpha(theme.colors.secondary.text, 0.1)} 100% + )`; return { dashlistLink: css({ display: 'flex', @@ -19,5 +24,22 @@ export const getStyles = (theme: GrafanaTheme2) => { }, }, }), + dashlistCard: css({ + display: 'flex', + flexDirection: 'column', + '&:hover a': { + color: theme.colors.text.link, + textDecoration: 'underline', + }, + height: '100%', + + '&:hover': { + backgroundImage: gradient, + color: theme.colors.text.primary, + }, + }), + dashlistCardIcon: css({ + marginRight: theme.spacing(0.5), + }), }; };