DashListItem: Added DashListItem shared component (#115384)
* DashListItem: Add DashListItem shared component and shared with DashList and RecentlyViewedDashboards
This commit is contained in:
@@ -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() {
|
||||
<Text>{t('browse-dashboards.recently-viewed.empty', 'Nothing viewed yet')}</Text>
|
||||
)}
|
||||
|
||||
{/* TODO: implement actual card content */}
|
||||
{!loading && recentDashboards.length > 0 && (
|
||||
<>
|
||||
{recentDashboards.map((dash) => (
|
||||
<div key={dash.uid}>
|
||||
<Link href={dash.url}>{dash.name}</Link>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
<ul className={styles.list}>
|
||||
<Grid columns={{ xs: 1, sm: 2, md: 3, lg: 5 }} gap={2}>
|
||||
{recentDashboards.map((dash) => (
|
||||
<li key={dash.uid} className={styles.listItem}>
|
||||
<DashListItem
|
||||
key={dash.uid}
|
||||
dashboard={dash}
|
||||
url={dash.url}
|
||||
showFolderNames={true}
|
||||
locationInfo={foldersByUid[dash.location]}
|
||||
layoutMode="card"
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</Grid>
|
||||
</ul>
|
||||
)}
|
||||
</CollapsableSection>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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<Record<string, LocationInfo>> => {
|
||||
if (!enabled) {
|
||||
return {};
|
||||
}
|
||||
return searcher.getLocationInfo();
|
||||
}, [enabled, searcher]);
|
||||
|
||||
return {
|
||||
foldersByUid: foldersByUid ?? {},
|
||||
loading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
@@ -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<Options>) {
|
||||
const [dashboards, setDashboards] = useState(new Map<string, Dashboard>());
|
||||
const [foldersTitleMap, setFoldersTitleMap] = useState<Record<string, LocationInfo>>({});
|
||||
|
||||
const throttledRenderCount = useThrottle(props.renderCounter, 5000);
|
||||
|
||||
@@ -125,13 +120,7 @@ export function DashList(props: PanelProps<Options>) {
|
||||
});
|
||||
}, [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<Options>) {
|
||||
setDashboards(updatedDashboards);
|
||||
};
|
||||
|
||||
const css = useStyles2(getStyles);
|
||||
const urlParams = useDashListUrlParams(props);
|
||||
|
||||
const renderList = (dashboards: Dashboard[]) => (
|
||||
@@ -194,26 +182,17 @@ export function DashList(props: PanelProps<Options>) {
|
||||
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 (
|
||||
<li key={`dash-${dash.uid}`}>
|
||||
<div className={css.dashlistLink}>
|
||||
<Box flex={1}>
|
||||
<Link href={url}>{dash.name}</Link>
|
||||
{showFolderNames && locationInfo && (
|
||||
<Text color="secondary" variant="bodySmall" element="p">
|
||||
{locationInfo?.name}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<StarToolbarButton
|
||||
title={dash.name}
|
||||
group="dashboard.grafana.app"
|
||||
kind="Dashboard"
|
||||
id={dash.uid}
|
||||
onStarChange={handleStarChange}
|
||||
/>
|
||||
</div>
|
||||
<DashListItem
|
||||
dashboard={dash}
|
||||
url={url}
|
||||
showFolderNames={showFolderNames}
|
||||
locationInfo={locationInfo}
|
||||
layoutMode="list"
|
||||
onStarChange={handleStarChange}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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' ? (
|
||||
<div className={css.dashlistLink}>
|
||||
<Box flex={1}>
|
||||
<Link href={url}>{dashboard.name}</Link>
|
||||
{showFolderNames && locationInfo && (
|
||||
<Text color="secondary" variant="bodySmall" element="p">
|
||||
{locationInfo?.name}
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
<StarToolbarButton
|
||||
title={dashboard.name}
|
||||
group="dashboard.grafana.app"
|
||||
kind="Dashboard"
|
||||
id={dashboard.uid}
|
||||
onStarChange={onStarChange}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Card className={css.dashlistCard} noMargin>
|
||||
<Stack justifyContent="space-between" alignItems="center">
|
||||
<Link href={url}>{dashboard.name}</Link>
|
||||
<StarToolbarButton
|
||||
title={dashboard.name}
|
||||
group="dashboard.grafana.app"
|
||||
kind="Dashboard"
|
||||
id={dashboard.uid}
|
||||
onStarChange={onStarChange}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
{showFolderNames && locationInfo && (
|
||||
<Stack alignItems="center" direction="row" gap={0}>
|
||||
<Icon name="folder" size="sm" className={css.dashlistCardIcon} aria-hidden="true" />
|
||||
<Text color="secondary" variant="bodySmall" element="p">
|
||||
{locationInfo?.name}
|
||||
</Text>
|
||||
</Stack>
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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),
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user