From 8440eadec2058045993ab28b0afb43d00cb48be9 Mon Sep 17 00:00:00 2001 From: Andre Pereira Date: Tue, 30 Jan 2024 13:51:31 +0000 Subject: [PATCH] Data trails: Homepage redesign (#81496) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rename trails and tweak styles in homepage * Use design system card and update layout. Add createdAt date to trail * Small style tweaks * Move queryDef state to metricScene * Date format update * More style tweaks * betterer update * Use smaller padding on Card and use Badge istead of Tag * Increase badge max width --------- Co-authored-by: Torkel Ödegaard --- .betterer.results | 4 - .../AutomaticMetricQueries/AutoVizPanel.tsx | 38 +++--- public/app/features/trails/DataTrail.tsx | 2 + public/app/features/trails/DataTrailCard.tsx | 114 ++++++++---------- public/app/features/trails/DataTrailsHome.tsx | 24 ++-- public/app/features/trails/MetricScene.tsx | 22 ++-- .../features/trails/TrailStore/TrailStore.ts | 4 +- 7 files changed, 104 insertions(+), 104 deletions(-) diff --git a/.betterer.results b/.betterer.results index f77ae021038..17ab6ecef9a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4188,10 +4188,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "11"], [0, 0, 0, "Do not use any type assertions.", "12"] ], - "public/app/features/trails/MetricScene.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "Do not use any type assertions.", "1"] - ], "public/app/features/transformers/FilterByValueTransformer/ValueMatchers/BasicMatcherEditor.tsx:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/public/app/features/trails/AutomaticMetricQueries/AutoVizPanel.tsx b/public/app/features/trails/AutomaticMetricQueries/AutoVizPanel.tsx index 3290c23b968..f55a081753b 100644 --- a/public/app/features/trails/AutomaticMetricQueries/AutoVizPanel.tsx +++ b/public/app/features/trails/AutomaticMetricQueries/AutoVizPanel.tsx @@ -1,52 +1,53 @@ import { css } from '@emotion/css'; import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; import { SceneObjectState, SceneObjectBase, SceneComponentProps, VizPanel, SceneQueryRunner } from '@grafana/scenes'; import { Field, RadioButtonGroup, useStyles2, Stack } from '@grafana/ui'; import { trailDS } from '../shared'; -import { getTrailSettings } from '../utils'; +import { getMetricSceneFor, getTrailSettings } from '../utils'; -import { AutoQueryInfo, AutoQueryDef } from './types'; +import { AutoQueryDef } from './types'; export interface AutoVizPanelState extends SceneObjectState { panel?: VizPanel; - autoQuery: AutoQueryInfo; - queryDef?: AutoQueryDef; } export class AutoVizPanel extends SceneObjectBase { constructor(state: AutoVizPanelState) { super(state); - if (!state.panel) { - this.setState({ - panel: this.getVizPanelFor(state.autoQuery.main), - queryDef: state.autoQuery.main, - }); - } + this.addActivationHandler(this.onActivate.bind(this)); + } + + public onActivate() { + const { autoQuery } = getMetricSceneFor(this).state; + this.setState({ + panel: this.getVizPanelFor(autoQuery.main), + }); } private getQuerySelector(def: AutoQueryDef) { - const variants = this.state.autoQuery.variants; + const { autoQuery } = getMetricSceneFor(this).state; - if (variants.length === 0) { + if (autoQuery.variants.length === 0) { return; } - const options = variants.map((q) => ({ label: q.variant, value: q.variant })); + const options = autoQuery.variants.map((q) => ({ label: q.variant, value: q.variant })); return ; } public onChangeQuery = (variant: string) => { - const def = this.state.autoQuery.variants.find((q) => q.variant === variant)!; + const metricScene = getMetricSceneFor(this); + + const def = metricScene.state.autoQuery.variants.find((q) => q.variant === variant)!; this.setState({ panel: this.getVizPanelFor(def), - queryDef: def, }); + metricScene.setState({ queryDef: def }); }; private getVizPanelFor(def: AutoQueryDef) { @@ -64,7 +65,8 @@ export class AutoVizPanel extends SceneObjectBase { } public static Component = ({ model }: SceneComponentProps) => { - const { panel, queryDef } = model.useState(); + const { panel } = model.useState(); + const { queryDef } = getMetricSceneFor(model).state; const { showQuery } = getTrailSettings(model).useState(); const styles = useStyles2(getStyles); @@ -91,7 +93,7 @@ export class AutoVizPanel extends SceneObjectBase { }; } -function getStyles(theme: GrafanaTheme2) { +function getStyles() { return { wrapper: css({ display: 'flex', diff --git a/public/app/features/trails/DataTrail.tsx b/public/app/features/trails/DataTrail.tsx index 0acb9ac4fc3..76661619d30 100644 --- a/public/app/features/trails/DataTrail.tsx +++ b/public/app/features/trails/DataTrail.tsx @@ -37,6 +37,7 @@ export interface DataTrailState extends SceneObjectState { controls: SceneObject[]; history: DataTrailHistory; settings: DataTrailSettings; + createdAt: number; // just for for the starting data source initialDS?: string; @@ -61,6 +62,7 @@ export class DataTrail extends SceneObjectBase { ], history: state.history ?? new DataTrailHistory({}), settings: state.settings ?? new DataTrailSettings({}), + createdAt: state.createdAt ?? new Date().getTime(), ...state, }); diff --git a/public/app/features/trails/DataTrailCard.tsx b/public/app/features/trails/DataTrailCard.tsx index 77753c3c6ea..757e18696fa 100644 --- a/public/app/features/trails/DataTrailCard.tsx +++ b/public/app/features/trails/DataTrailCard.tsx @@ -1,9 +1,9 @@ import { css } from '@emotion/css'; import React from 'react'; -import { GrafanaTheme2 } from '@grafana/data'; +import { dateTimeFormat, GrafanaTheme2 } from '@grafana/data'; import { AdHocFiltersVariable, sceneGraph } from '@grafana/scenes'; -import { useStyles2, Stack, Tooltip, Button } from '@grafana/ui'; +import { useStyles2, Stack, Card, IconButton, Badge } from '@grafana/ui'; import { DataTrail } from './DataTrail'; import { LOGS_METRIC, VAR_FILTERS } from './shared'; @@ -27,31 +27,39 @@ export function DataTrailCard({ trail, onSelect, onDelete }: Props) { const dsValue = getDataSource(trail); return ( - + + +
+ Datasource: {getDataSourceName(dsValue)} +
+ {trail.state.createdAt && ( + + Created: {dateTimeFormat(trail.state.createdAt, { format: 'LL' })} + + )} +
+
+ {onDelete && ( + + + + )} + ); } @@ -69,45 +77,27 @@ function getMetricName(metric?: string) { function getStyles(theme: GrafanaTheme2) { return { - container: css({ + tag: css({ + maxWidth: '260px', + overflow: 'hidden', + textOverflow: 'ellipsis', + }), + card: css({ padding: theme.spacing(1), - flexGrow: 1, - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(2), + }), + secondary: css({ + color: theme.colors.text.secondary, + fontSize: '12px', + }), + description: css({ 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), - }, + gridArea: 'Description', + margin: theme.spacing(1, 0, 0), + color: theme.colors.text.secondary, + lineHeight: theme.typography.body.lineHeight, }), - 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%', + actions: css({ + marginRight: theme.spacing(1), }), }; } diff --git a/public/app/features/trails/DataTrailsHome.tsx b/public/app/features/trails/DataTrailsHome.tsx index 441a45d928f..e2c54e39503 100644 --- a/public/app/features/trails/DataTrailsHome.tsx +++ b/public/app/features/trails/DataTrailsHome.tsx @@ -53,18 +53,18 @@ export class DataTrailsHome extends SceneObjectBase { return (
- - Data trails - Automatically query, explore and navigate your observability data - - - - +
- Recent trails + Recent metrics
{getTrailStore().recent.map((trail, index) => { const resolvedTrail = trail.resolve(); @@ -78,6 +78,7 @@ export class DataTrailsHome extends SceneObjectBase { })}
+
Bookmarks
@@ -114,8 +115,8 @@ function getStyles(theme: GrafanaTheme2) { gap: theme.spacing(3), }), column: css({ - width: 500, display: 'flex', + flexGrow: 1, flexDirection: 'column', gap: theme.spacing(2), }), @@ -130,5 +131,8 @@ function getStyles(theme: GrafanaTheme2) { flexDirection: 'column', gap: theme.spacing(2), }), + verticalLine: css({ + borderLeft: `1px solid ${theme.colors.border.weak}`, + }), }; } diff --git a/public/app/features/trails/MetricScene.tsx b/public/app/features/trails/MetricScene.tsx index 9b1b169e4d5..f21cac2e200 100644 --- a/public/app/features/trails/MetricScene.tsx +++ b/public/app/features/trails/MetricScene.tsx @@ -24,6 +24,7 @@ import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene'; import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene'; import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine'; import { AutoVizPanel } from './AutomaticMetricQueries/AutoVizPanel'; +import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types'; import { ShareTrailButton } from './ShareTrailButton'; import { getTrailStore } from './TrailStore/TrailStore'; import { @@ -42,15 +43,21 @@ export interface MetricSceneState extends SceneObjectState { body: SceneFlexLayout; metric: string; actionView?: string; + + autoQuery: AutoQueryInfo; + queryDef?: AutoQueryDef; } export class MetricScene extends SceneObjectBase { protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['actionView'] }); - public constructor(state: MakeOptional) { + public constructor(state: MakeOptional) { + const autoQuery = state.autoQuery ?? getAutoQueriesForMetric(state.metric); super({ $variables: state.$variables ?? getVariableSet(state.metric), - body: state.body ?? buildGraphScene(state.metric), + body: state.body ?? buildGraphScene(), + autoQuery, + queryDef: state.queryDef ?? autoQuery.main, ...state, }); @@ -121,10 +128,8 @@ export class MetricActionBar extends SceneObjectBase { const trail = getTrailFor(this); const dsValue = getDataSource(trail); - const flexItem = metricScene.state.body.state.children[0] as SceneFlexItem; - const autoVizPanel = flexItem.state.body as AutoVizPanel; - const queries = autoVizPanel.state.queryDef?.queries || []; - const timeRange = sceneGraph.getTimeRange(autoVizPanel); + const queries = metricScene.state.queryDef?.queries || []; + const timeRange = sceneGraph.getTimeRange(this); return getExploreUrl({ queries, @@ -235,9 +240,8 @@ function getVariableSet(metric: string) { const MAIN_PANEL_MIN_HEIGHT = 280; const MAIN_PANEL_MAX_HEIGHT = '40%'; -function buildGraphScene(metric: string) { - const autoQuery = getAutoQueriesForMetric(metric); - const bodyAutoVizPanel = new AutoVizPanel({ autoQuery }); +function buildGraphScene() { + const bodyAutoVizPanel = new AutoVizPanel({}); return new SceneFlexLayout({ direction: 'column', diff --git a/public/app/features/trails/TrailStore/TrailStore.ts b/public/app/features/trails/TrailStore/TrailStore.ts index 400cfaa0e83..9c81ec50160 100644 --- a/public/app/features/trails/TrailStore/TrailStore.ts +++ b/public/app/features/trails/TrailStore/TrailStore.ts @@ -16,6 +16,7 @@ export interface SerializedTrail { parentIndex: number; }>; currentStep: number; + createdAt?: number; } export class TrailStore { @@ -53,7 +54,7 @@ export class TrailStore { private _deserializeTrail(t: SerializedTrail): DataTrail { // reconstruct the trail based on the the serialized history - const trail = new DataTrail({}); + const trail = new DataTrail({ createdAt: t.createdAt }); t.history.map((step) => { this._loadFromUrl(trail, step.urlValues); @@ -82,6 +83,7 @@ export class TrailStore { return { history, currentStep: trail.state.history.state.currentStep, + createdAt: trail.state.createdAt, }; }