mirror of
https://github.com/grafana/grafana.git
synced 2026-08-07 11:55:03 -05:00
datatrails: standardize loading and blocking message indicators (#83560)
fix: standardize loading and blocking message indicators
This commit is contained in:
@@ -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<SelectableValue<string>>;
|
||||
value?: string;
|
||||
loading?: boolean;
|
||||
error?: string;
|
||||
blockingMessage?: string;
|
||||
}
|
||||
|
||||
export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
@@ -99,12 +102,17 @@ export class BreakdownScene extends SceneObjectBase<BreakdownSceneState> {
|
||||
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<BreakdownSceneState> {
|
||||
|
||||
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<BreakdownScene>) => {
|
||||
const { labels, body, loading, value } = model.useState();
|
||||
const { labels, body, loading, value, blockingMessage } = model.useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{loading && <div>Loading...</div>}
|
||||
<div className={styles.controls}>
|
||||
{!loading && (
|
||||
<div className={styles.controlsLeft}>
|
||||
<Field label="By label">
|
||||
<BreakdownLabelSelector options={labels} value={value} onChange={model.onChange} />
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
{body instanceof LayoutSwitcher && (
|
||||
<div className={styles.controlsRight}>
|
||||
<body.Selector model={body} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>{body && <body.Component model={body} />}</div>
|
||||
<StatusWrapper {...{ isLoading: loading, blockingMessage }}>
|
||||
<div className={styles.controls}>
|
||||
{!loading && labels.length && (
|
||||
<div className={styles.controlsLeft}>
|
||||
<Field label="By label">
|
||||
<BreakdownLabelSelector options={labels} value={value} onChange={model.onChange} />
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
{body instanceof LayoutSwitcher && (
|
||||
<div className={styles.controlsRight}>
|
||||
<body.Selector model={body} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>{body && <body.Component model={body} />}</div>
|
||||
</StatusWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<MetricOverviewSceneState> {
|
||||
@@ -57,6 +58,7 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
}
|
||||
|
||||
private async updateMetadata() {
|
||||
this.setState({ metadataLoading: true, metadata: undefined });
|
||||
const ds = await getDatasourceSrv().get(VAR_DATASOURCE_EXPR, { __sceneObject: { value: this } });
|
||||
|
||||
const languageProvider: PrometheusLanguageProvider = ds.languageProvider;
|
||||
@@ -69,26 +71,24 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
const metric = metricScene.state.metric;
|
||||
|
||||
if (languageProvider.metricsMetadata) {
|
||||
this.setState({ metadata: languageProvider.metricsMetadata[metric] });
|
||||
this.setState({ metadata: languageProvider.metricsMetadata[metric], metadataLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
await languageProvider.start();
|
||||
|
||||
this.setState({ metadata: languageProvider.metricsMetadata?.[metric] });
|
||||
this.setState({ metadata: languageProvider.metricsMetadata?.[metric], metadataLoading: false });
|
||||
}
|
||||
|
||||
public static Component = ({ model }: SceneComponentProps<MetricOverviewScene>) => {
|
||||
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 (
|
||||
<Stack gap={6}>
|
||||
{loading ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<StatusWrapper isLoading={labelsLoading || metadataLoading}>
|
||||
<Stack gap={6}>
|
||||
<>
|
||||
<Stack direction="column" gap={0.5}>
|
||||
<Text weight={'medium'}>Description</Text>
|
||||
@@ -106,6 +106,7 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
</Stack>
|
||||
<Stack direction="column" gap={0.5}>
|
||||
<Text weight={'medium'}>Labels</Text>
|
||||
{labelOptions.length === 0 && 'Unable to fetch labels.'}
|
||||
{labelOptions.map((l) => (
|
||||
<TextLink
|
||||
key={l.label}
|
||||
@@ -122,8 +123,8 @@ export class MetricOverviewScene extends SceneObjectBase<MetricOverviewSceneStat
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
</StatusWrapper>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<MetricSelectSceneState> {
|
||||
const tooStrict = children.length === 0 && (searchQuery || prefixFilter);
|
||||
const noMetrics = !metricNamesStatus.isLoading && model.currentMetricNames.size === 0;
|
||||
|
||||
const status =
|
||||
(metricNamesStatus.isLoading && children.length === 0 && (
|
||||
<LoadingPlaceholder className={styles.statusMessage} text="Loading..." />
|
||||
)) ||
|
||||
(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 && <div className={styles.statusMessage}>{status}</div>;
|
||||
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<MetricSelectSceneState> {
|
||||
<div>({metricNamesStatus.error})</div>
|
||||
</Alert>
|
||||
)}
|
||||
{showStatus}
|
||||
<model.state.body.Component model={model.state.body} />
|
||||
<StatusWrapper {...{ isLoading, blockingMessage }}>
|
||||
<model.state.body.Component model={model.state.body} />
|
||||
</StatusWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <LoadingPlaceholder className={styles.statusMessage} text={blockingMessage} />;
|
||||
}
|
||||
|
||||
if (!blockingMessage) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <div className={styles.statusMessage}>{blockingMessage}</div>;
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
statusMessage: css({
|
||||
fontStyle: 'italic',
|
||||
marginTop: theme.spacing(7),
|
||||
textAlign: 'center',
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user