diff --git a/public/app/features/trails/ActionTabs/BreakdownScene.tsx b/public/app/features/trails/ActionTabs/BreakdownScene.tsx index 28a9dc7579e..26be042792f 100644 --- a/public/app/features/trails/ActionTabs/BreakdownScene.tsx +++ b/public/app/features/trails/ActionTabs/BreakdownScene.tsx @@ -26,6 +26,7 @@ import { getAutoQueriesForMetric } from '../AutomaticMetricQueries/AutoQueryEngi import { AutoQueryDef } from '../AutomaticMetricQueries/types'; import { BreakdownLabelSelector } from '../BreakdownLabelSelector'; import { MetricScene } from '../MetricScene'; +import { StatusWrapper } from '../StatusWrapper'; import { trailDS, VAR_FILTERS, VAR_GROUP_BY, VAR_GROUP_BY_EXP } from '../shared'; import { getColorByIndex } from '../utils'; @@ -39,6 +40,8 @@ export interface BreakdownSceneState extends SceneObjectState { labels: Array>; value?: string; loading?: boolean; + error?: string; + blockingMessage?: string; } export class BreakdownScene extends SceneObjectBase { @@ -99,12 +102,17 @@ export class BreakdownScene extends SceneObjectBase { loading: variable.state.loading, value: String(variable.state.value), labels: options, + error: variable.state.error, + blockingMessage: undefined, }; - if (!variable.state.loading) { + if (!variable.state.loading && variable.state.options.length) { stateUpdate.body = variable.hasAllValue() ? buildAllLayout(options, this._query!) : buildNormalLayout(this._query!); + } else if (!variable.state.loading) { + stateUpdate.body = undefined; + stateUpdate.blockingMessage = 'Unable to retrieve label options for currently selected metric.'; } this.setState(stateUpdate); @@ -117,37 +125,32 @@ export class BreakdownScene extends SceneObjectBase { const variable = this.getVariable(); - if (value === ALL_VARIABLE_VALUE) { - this.setState({ body: buildAllLayout(this.state.labels, this._query!) }); - } else if (variable.hasAllValue()) { - this.setState({ body: buildNormalLayout(this._query!) }); - } - variable.changeValueTo(value); }; public static Component = ({ model }: SceneComponentProps) => { - const { labels, body, loading, value } = model.useState(); + const { labels, body, loading, value, blockingMessage } = model.useState(); const styles = useStyles2(getStyles); return (
- {loading &&
Loading...
} -
- {!loading && ( -
- - - -
- )} - {body instanceof LayoutSwitcher && ( -
- -
- )} -
-
{body && }
+ +
+ {!loading && labels.length && ( +
+ + + +
+ )} + {body instanceof LayoutSwitcher && ( +
+ +
+ )} +
+
{body && }
+
); }; diff --git a/public/app/features/trails/ActionTabs/MetricOverviewScene.tsx b/public/app/features/trails/ActionTabs/MetricOverviewScene.tsx index c3efde7d7b1..5adc3d83bc9 100644 --- a/public/app/features/trails/ActionTabs/MetricOverviewScene.tsx +++ b/public/app/features/trails/ActionTabs/MetricOverviewScene.tsx @@ -15,6 +15,7 @@ import PrometheusLanguageProvider from '../../../plugins/datasource/prometheus/l import { PromMetricsMetadataItem } from '../../../plugins/datasource/prometheus/types'; import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { ALL_VARIABLE_VALUE } from '../../variables/constants'; +import { StatusWrapper } from '../StatusWrapper'; import { TRAILS_ROUTE, VAR_DATASOURCE_EXPR, VAR_GROUP_BY } from '../shared'; import { getMetricSceneFor } from '../utils'; @@ -22,7 +23,7 @@ import { getLabelOptions } from './utils'; export interface MetricOverviewSceneState extends SceneObjectState { metadata?: PromMetricsMetadataItem; - loading?: boolean; + metadataLoading?: boolean; } export class MetricOverviewScene extends SceneObjectBase { @@ -57,6 +58,7 @@ export class MetricOverviewScene extends SceneObjectBase) => { - const { metadata } = model.useState(); + const { metadata, metadataLoading } = model.useState(); const variable = model.getVariable(); - const { loading } = variable.useState(); + const { loading: labelsLoading } = variable.useState(); const labelOptions = getLabelOptions(model, variable).filter((l) => l.value !== ALL_VARIABLE_VALUE); return ( - - {loading ? ( -
Loading...
- ) : ( + + <> Description @@ -106,6 +106,7 @@ export class MetricOverviewScene extends SceneObjectBase Labels + {labelOptions.length === 0 && 'Unable to fetch labels.'} {labelOptions.map((l) => ( - )} - + + ); }; } diff --git a/public/app/features/trails/MetricSelectScene.tsx b/public/app/features/trails/MetricSelectScene.tsx index 73b856e5331..ee42c0ed54e 100644 --- a/public/app/features/trails/MetricSelectScene.tsx +++ b/public/app/features/trails/MetricSelectScene.tsx @@ -20,12 +20,13 @@ import { VariableDependencyConfig, } from '@grafana/scenes'; import { VariableHide } from '@grafana/schema'; -import { Input, useStyles2, InlineSwitch, Field, Alert, Icon, LoadingPlaceholder } from '@grafana/ui'; +import { Input, InlineSwitch, Field, Alert, Icon, useStyles2 } from '@grafana/ui'; import { getPreviewPanelFor } from './AutomaticMetricQueries/previewPanel'; import { MetricCategoryCascader } from './MetricCategory/MetricCategoryCascader'; import { MetricScene } from './MetricScene'; import { SelectMetricAction } from './SelectMetricAction'; +import { StatusWrapper } from './StatusWrapper'; import { sortRelatedMetrics } from './relatedMetrics'; import { getVariablesWithMetricConstant, trailDS, VAR_DATASOURCE, VAR_FILTERS_EXPR, VAR_METRIC_NAMES } from './shared'; import { getFilters, getTrailFor } from './utils'; @@ -326,14 +327,13 @@ export class MetricSelectScene extends SceneObjectBase { const tooStrict = children.length === 0 && (searchQuery || prefixFilter); const noMetrics = !metricNamesStatus.isLoading && model.currentMetricNames.size === 0; - const status = - (metricNamesStatus.isLoading && children.length === 0 && ( - - )) || - (noMetrics && 'There are no results found. Try a different time range or a different data source.') || - (tooStrict && 'There are no results found. Try adjusting your search or filters.'); + const isLoading = metricNamesStatus.isLoading && children.length === 0; - const showStatus = status &&
{status}
; + const blockingMessage = isLoading + ? undefined + : (noMetrics && 'There are no results found. Try a different time range or a different data source.') || + (tooStrict && 'There are no results found. Try adjusting your search or filters.') || + undefined; const prefixError = prefixFilter && metricsAfterSearch != null && !metricsAfterFilter?.length @@ -378,8 +378,9 @@ export class MetricSelectScene extends SceneObjectBase {
({metricNamesStatus.error})
)} - {showStatus} - + + + ); }; @@ -427,11 +428,6 @@ function getStyles(theme: GrafanaTheme2) { marginBottom: theme.spacing(1), alignItems: 'flex-end', }), - statusMessage: css({ - fontStyle: 'italic', - marginTop: theme.spacing(7), - textAlign: 'center', - }), searchField: css({ flexGrow: 1, marginBottom: 0, @@ -463,7 +459,7 @@ function createSearchRegExp(spaceSeparatedMetricNames?: string) { } function useVariableStatus(name: string, sceneObject: SceneObject) { - const variable = sceneGraph.lookupVariable(VAR_METRIC_NAMES, sceneObject); + const variable = sceneGraph.lookupVariable(name, sceneObject); const useVariableState = useCallback(() => { if (variable) { diff --git a/public/app/features/trails/StatusWrapper.tsx b/public/app/features/trails/StatusWrapper.tsx new file mode 100644 index 00000000000..372a558bed1 --- /dev/null +++ b/public/app/features/trails/StatusWrapper.tsx @@ -0,0 +1,39 @@ +import { css } from '@emotion/css'; +import React, { ReactNode } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { LoadingPlaceholder, useStyles2 } from '@grafana/ui'; + +type Props = { + blockingMessage?: string; + isLoading?: boolean; + children?: ReactNode; +}; + +export function StatusWrapper({ blockingMessage, isLoading, children }: Props) { + const styles = useStyles2(getStyles); + + if (isLoading && !blockingMessage) { + blockingMessage = 'Loading...'; + } + + if (isLoading) { + return ; + } + + if (!blockingMessage) { + return children; + } + + return
{blockingMessage}
; +} + +function getStyles(theme: GrafanaTheme2) { + return { + statusMessage: css({ + fontStyle: 'italic', + marginTop: theme.spacing(7), + textAlign: 'center', + }), + }; +}