grafana/public/app/features/trails/DataTrailCard.tsx
Andre Pereira 01ad2918d6
Data trails: Store recent and bookmarked trails in local storage (#78508)
* WIP

* Restore trail using history and updateFromUrl()

* Limit stored recent trails to 20

* Rename and refactor

* Bookmark and store trails

* No export

* Remove unused event

* Organise

* Address feedback

* Added button to remove bookmark. Added trail to home card

* Added tests for trail store

* Update

* remove import

* Fix home not updating after removing bookmark. Remove trail for home card

* Remove button no longer absolute

---------

Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
2023-11-28 18:00:08 +00:00

117 lines
3.2 KiB
TypeScript

import { css } from '@emotion/css';
import React from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes';
import { useStyles2, Stack, Tooltip, Button } from '@grafana/ui';
import { DataTrail } from './DataTrail';
import { LOGS_METRIC, VAR_DATASOURCE_EXPR, VAR_FILTERS } from './shared';
export interface Props {
trail: DataTrail;
onSelect: (trail: DataTrail) => void;
onDelete?: () => void;
}
export function DataTrailCard({ trail, onSelect, onDelete }: Props) {
const styles = useStyles2(getStyles);
const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, trail)!;
if (!(filtersVariable instanceof AdHocFiltersVariable)) {
return null;
}
const filters = filtersVariable.state.set.state.filters;
const dsValue = getDataSource(trail);
return (
<button className={styles.container} onClick={() => onSelect(trail)}>
<div className={styles.wrapper}>
<div className={styles.heading}>{getMetricName(trail.state.metric)}</div>
{onDelete && (
<Tooltip content={'Remove bookmark'}>
<Button size="sm" icon="trash-alt" variant="destructive" fill="text" onClick={onDelete} />
</Tooltip>
)}
</div>
<Stack gap={1.5}>
{dsValue && (
<Stack direction="column" gap={0.5}>
<div className={styles.label}>Datasource</div>
<div className={styles.value}>{getDataSource(trail)}</div>
</Stack>
)}
{filters.map((filter, index) => (
<Stack key={index} direction="column" gap={0.5}>
<div className={styles.label}>{filter.key}</div>
<div className={styles.value}>{filter.value}</div>
</Stack>
))}
</Stack>
</button>
);
}
function getMetricName(metric?: string) {
if (!metric) {
return 'Select metric';
}
if (metric === LOGS_METRIC) {
return 'Logs';
}
return metric;
}
function getDataSource(trail: DataTrail) {
return sceneGraph.interpolate(trail, VAR_DATASOURCE_EXPR);
}
function getStyles(theme: GrafanaTheme2) {
return {
container: css({
padding: theme.spacing(1),
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(2),
width: '100%',
border: `1px solid ${theme.colors.border.weak}`,
borderRadius: theme.shape.radius.default,
cursor: 'pointer',
boxShadow: 'none',
background: 'transparent',
textAlign: 'left',
'&:hover': {
background: theme.colors.emphasize(theme.colors.background.primary, 0.03),
},
}),
label: css({
fontWeight: theme.typography.fontWeightMedium,
fontSize: theme.typography.bodySmall.fontSize,
}),
value: css({
fontSize: theme.typography.bodySmall.fontSize,
}),
heading: css({
padding: theme.spacing(0),
display: 'flex',
fontWeight: theme.typography.fontWeightMedium,
overflowX: 'hidden',
}),
body: css({
padding: theme.spacing(0),
}),
wrapper: css({
position: 'relative',
display: 'flex',
gap: theme.spacing.x1,
justifyContent: 'space-between',
width: '100%',
}),
};
}