The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/trails/DataTrailCard.tsx

189 lines
5.7 KiB

import { css } from '@emotion/css';
import { useMemo } from 'react';
import { dateTimeFormat, GrafanaTheme2 } from '@grafana/data';
import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes';
import { Card, IconButton, useStyles2 } from '@grafana/ui';
import { Trans } from 'app/core/internationalization';
import { DataTrail } from './DataTrail';
import { getTrailStore, DataTrailBookmark } from './TrailStore/TrailStore';
import { VAR_FILTERS } from './shared';
import { getMetricName } from './utils';
export type Props = {
trail?: DataTrail;
bookmark?: DataTrailBookmark;
onSelect: () => void;
onDelete?: () => void;
};
// Helper function to truncate the value for a single key:value pair
const truncateValue = (key: string, value: string, maxLength: number) => {
const combinedLength = key.length + 2 + value.length; // 2 for ": "
if (combinedLength > maxLength) {
return value.substring(0, maxLength - key.length - 5) + '...'; // 5 for ": " and "..."
}
return value;
};
export function DataTrailCard(props: Props) {
const { onSelect, onDelete, bookmark } = props;
const styles = useStyles2(getStyles);
const values = useMemo(() => {
let trail = props.trail || (bookmark && getTrailStore().getTrailForBookmark(bookmark));
if (!trail) {
return null;
}
const filtersVariable = sceneGraph.lookupVariable(VAR_FILTERS, trail)!;
if (!(filtersVariable instanceof AdHocFiltersVariable)) {
return null;
}
const createdAt = bookmark?.createdAt || trail.state.createdAt;
return {
filters: filtersVariable.state.filters,
metric: trail.state.metric,
createdAt,
};
}, [props.trail, bookmark]);
if (!values) {
return null;
}
const { filters, metric, createdAt } = values;
return (
<div>
<Card onClick={onSelect} className={styles.card}>
<Card.Heading>
<div className={styles.metricValue}>{truncateValue('', getMetricName(metric), 39)}</div>
</Card.Heading>
<Card.Meta className={styles.meta}>
{filters.map((f) => (
<span key={f.key}>
<div className={styles.secondaryFont}>{f.key}: </div>
<div className={styles.primaryFont}>{truncateValue(f.key, f.value, 44)}</div>
</span>
))}
</Card.Meta>
<div className={styles.deleteButton}>
{onDelete && (
<Card.SecondaryActions>
<IconButton
key="delete"
name="trash-alt"
className={styles.secondary}
tooltip="Remove bookmark"
onClick={onDelete}
data-testid="deleteButton"
/>
</Card.SecondaryActions>
)}
</div>
</Card>
<div className={styles.date}>
<div className={styles.secondaryFont}>
<Trans i18nKey="trails.card.date-created">Date created: </Trans>
</div>
<div className={styles.primaryFont}>{createdAt && dateTimeFormat(createdAt, { format: 'YYYY-MM-DD' })}</div>
</div>
</div>
);
}
export function getStyles(theme: GrafanaTheme2) {
return {
metricLabel: css({
display: 'inline',
color: theme.colors.text.primary,
fontFamily: 'Inter',
fontSize: '14px',
fontStyle: 'normal',
fontWeight: 400,
}),
metricValue: css({
display: 'inline',
color: theme.colors.text.primary,
fontFamily: 'Inter',
fontSize: '14px',
fontStyle: 'normal',
fontWeight: 500,
wordBreak: 'break-all',
}),
tag: css({
maxWidth: '260px',
overflow: 'hidden',
textOverflow: 'ellipsis',
}),
card: css({
position: 'relative',
width: '318px',
padding: `12px ${theme.spacing(2)} ${theme.spacing(1)} ${theme.spacing(2)}`,
height: '110px',
alignItems: 'start',
marginBottom: 0,
borderTop: `1px solid ${theme.colors.border.weak}`,
borderRight: `1px solid ${theme.colors.border.weak}`,
borderLeft: `1px solid ${theme.colors.border.weak}`,
borderBottom: 'none', // Remove the bottom border
// eslint-disable-next-line @grafana/no-border-radius-literal
borderRadius: '2px 2px 0 0', // Top-left and top-right corners are 2px, bottom-left and bottom-right are 0; cannot use theme.shape.radius.default because need bottom corners to be 0
}),
secondary: css({
color: theme.colors.text.secondary,
fontSize: '12px',
}),
date: css({
border: `1px solid ${theme.colors.border.weak}`,
// eslint-disable-next-line @grafana/no-border-radius-literal
borderRadius: '0 0 2px 2px',
padding: `${theme.spacing(1)} ${theme.spacing(2)}`,
backgroundColor: theme.colors.background.primary,
}),
meta: css({
flexWrap: 'wrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
maxHeight: '36px', // 2 lines * 18px line-height
margin: 0,
gridArea: 'Meta',
color: theme.colors.text.secondary,
whiteSpace: 'nowrap',
}),
primaryFont: css({
display: 'inline',
color: theme.colors.text.primary,
fontFamily: 'Inter',
fontSize: '12px',
fontStyle: 'normal',
fontWeight: '500',
lineHeight: '18px' /* 150% */,
letterSpacing: '0.018px',
}),
secondaryFont: css({
display: 'inline',
color: theme.colors.text.secondary,
fontFamily: 'Inter',
fontSize: '12px',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: '18px' /* 150% */,
letterSpacing: '0.018px',
}),
deleteButton: css({
position: 'absolute',
bottom: theme.spacing(1),
right: theme.spacing(1),
}),
wordwrap: css({
overflow: 'hidden',
overflowWrap: 'anywhere',
}),
};
}