|
|
|
@@ -1,8 +1,8 @@
|
|
|
|
|
import { css } from '@emotion/css';
|
|
|
|
|
import leven from 'leven';
|
|
|
|
|
import React from 'react';
|
|
|
|
|
import React, { useCallback } from 'react';
|
|
|
|
|
|
|
|
|
|
import { GrafanaTheme2 } from '@grafana/data';
|
|
|
|
|
import { GrafanaTheme2, VariableRefresh } from '@grafana/data';
|
|
|
|
|
import {
|
|
|
|
|
PanelBuilders,
|
|
|
|
|
QueryVariable,
|
|
|
|
@@ -11,22 +11,24 @@ import {
|
|
|
|
|
SceneCSSGridLayout,
|
|
|
|
|
SceneFlexItem,
|
|
|
|
|
sceneGraph,
|
|
|
|
|
SceneObject,
|
|
|
|
|
SceneObjectBase,
|
|
|
|
|
SceneObjectRef,
|
|
|
|
|
SceneObjectState,
|
|
|
|
|
SceneQueryRunner,
|
|
|
|
|
SceneVariable,
|
|
|
|
|
SceneVariableSet,
|
|
|
|
|
VariableDependencyConfig,
|
|
|
|
|
} from '@grafana/scenes';
|
|
|
|
|
import { VariableHide } from '@grafana/schema';
|
|
|
|
|
import { Field, Icon, InlineSwitch, Input, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
|
|
|
|
|
import { Input, useStyles2, InlineSwitch, Field, Alert, Icon, LoadingPlaceholder } from '@grafana/ui';
|
|
|
|
|
|
|
|
|
|
import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine';
|
|
|
|
|
import { MetricCategoryCascader } from './MetricCategory/MetricCategoryCascader';
|
|
|
|
|
import { MetricScene } from './MetricScene';
|
|
|
|
|
import { SelectMetricAction } from './SelectMetricAction';
|
|
|
|
|
import { hideEmptyPreviews } from './hideEmptyPreviews';
|
|
|
|
|
import { getVariablesWithMetricConstant, trailDS, VAR_FILTERS_EXPR, VAR_METRIC_NAMES } from './shared';
|
|
|
|
|
import { getVariablesWithMetricConstant, trailDS, VAR_DATASOURCE, VAR_FILTERS_EXPR, VAR_METRIC_NAMES } from './shared';
|
|
|
|
|
import { getColorByIndex, getTrailFor } from './utils';
|
|
|
|
|
|
|
|
|
|
interface MetricPanel {
|
|
|
|
@@ -72,15 +74,24 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
this.addActivationHandler(this._onActivate.bind(this));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected _variableDependency = new VariableDependencyConfig(this, {
|
|
|
|
|
variableNames: [VAR_METRIC_NAMES],
|
|
|
|
|
onVariableUpdateCompleted: this.onVariableUpdateCompleted.bind(this),
|
|
|
|
|
});
|
|
|
|
|
// private justChangedTimeRange = false;
|
|
|
|
|
|
|
|
|
|
private onVariableUpdateCompleted(): void {
|
|
|
|
|
this.updateMetrics(); // Entire pipeline must be performed
|
|
|
|
|
this.buildLayout();
|
|
|
|
|
}
|
|
|
|
|
protected _variableDependency = new VariableDependencyConfig(this, {
|
|
|
|
|
variableNames: [VAR_METRIC_NAMES, VAR_DATASOURCE],
|
|
|
|
|
onReferencedVariableValueChanged: (variable: SceneVariable) => {
|
|
|
|
|
const { name } = variable.state;
|
|
|
|
|
|
|
|
|
|
if (name === VAR_DATASOURCE) {
|
|
|
|
|
// Clear all panels for the previous data source
|
|
|
|
|
this.state.body.setState({ children: [] });
|
|
|
|
|
} else if (name === VAR_METRIC_NAMES) {
|
|
|
|
|
this.onMetricNamesChange();
|
|
|
|
|
// Entire pipeline must be performed
|
|
|
|
|
this.updateMetrics();
|
|
|
|
|
this.buildLayout();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
private _onActivate() {
|
|
|
|
|
if (this.state.body.state.children.length === 0) {
|
|
|
|
@@ -106,25 +117,36 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getAllMetricNames() {
|
|
|
|
|
private currentMetricNames = new Set<string>();
|
|
|
|
|
|
|
|
|
|
private onMetricNamesChange() {
|
|
|
|
|
// Get the datasource metrics list from the VAR_METRIC_NAMES variable
|
|
|
|
|
const variable = sceneGraph.lookupVariable(VAR_METRIC_NAMES, this);
|
|
|
|
|
|
|
|
|
|
if (!(variable instanceof QueryVariable)) {
|
|
|
|
|
return null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (variable.state.loading) {
|
|
|
|
|
return null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const metricNames = variable.state.options.map((option) => option.value.toString());
|
|
|
|
|
return metricNames;
|
|
|
|
|
const nameList = variable.state.options.map((option) => option.value.toString());
|
|
|
|
|
const nameSet = new Set(nameList);
|
|
|
|
|
|
|
|
|
|
Object.values(this.previewCache).forEach((panel) => {
|
|
|
|
|
if (!nameSet.has(panel.name)) {
|
|
|
|
|
panel.isEmpty = true;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.currentMetricNames = nameSet;
|
|
|
|
|
this.buildLayout();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private applyMetricSearch() {
|
|
|
|
|
// This should only occur when the `searchQuery` changes, of if the `metricNames` change
|
|
|
|
|
const metricNames = this.getAllMetricNames();
|
|
|
|
|
const metricNames = Array.from(this.currentMetricNames);
|
|
|
|
|
if (metricNames == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
@@ -139,6 +161,7 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private applyMetricPrefixFilter() {
|
|
|
|
|
// This should occur after an `applyMetricSearch`, or if the prefix filter has changed
|
|
|
|
|
const { metricsAfterSearch, prefixFilter } = this.state;
|
|
|
|
|
|
|
|
|
|
if (!prefixFilter || !metricsAfterSearch) {
|
|
|
|
@@ -169,6 +192,13 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
const metricsMap: Record<string, MetricPanel> = {};
|
|
|
|
|
const metricsLimit = 120;
|
|
|
|
|
|
|
|
|
|
// Clear absent metrics from cache
|
|
|
|
|
Object.keys(this.previewCache).forEach((metric) => {
|
|
|
|
|
if (!this.currentMetricNames.has(metric)) {
|
|
|
|
|
delete this.previewCache[metric];
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (let index = 0; index < sortedMetricNames.length; index++) {
|
|
|
|
|
const metricName = sortedMetricNames[index];
|
|
|
|
|
|
|
|
|
@@ -176,7 +206,11 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
metricsMap[metricName] = { name: metricName, index, loaded: false };
|
|
|
|
|
const oldPanel = this.previewCache[metricName];
|
|
|
|
|
|
|
|
|
|
const panel = oldPanel || { name: metricName, index, loaded: false };
|
|
|
|
|
|
|
|
|
|
metricsMap[metricName] = panel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
@@ -213,7 +247,7 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
|
|
|
|
|
const children: SceneFlexItem[] = [];
|
|
|
|
|
|
|
|
|
|
const metricsList = this.sortedPreviewMetrics();
|
|
|
|
|
const metricsList = this.sortedPreviewMetrics(); //!this.justChangedTimeRange ? this.sortedPreviewMetrics() : Object.values(this.previewCache);
|
|
|
|
|
for (let index = 0; index < metricsList.length; index++) {
|
|
|
|
|
const metric = metricsList[index];
|
|
|
|
|
|
|
|
|
@@ -277,12 +311,15 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
const { children } = body.useState();
|
|
|
|
|
const styles = useStyles2(getStyles);
|
|
|
|
|
|
|
|
|
|
const notLoaded = metricsAfterSearch === undefined && metricsAfterFilter === undefined && children.length === 0;
|
|
|
|
|
|
|
|
|
|
const metricNamesStatus = useVariableStatus(VAR_METRIC_NAMES, model);
|
|
|
|
|
const tooStrict = children.length === 0 && (searchQuery || prefixFilter);
|
|
|
|
|
const noMetrics = !metricNamesStatus.isLoading && model.currentMetricNames.size === 0;
|
|
|
|
|
|
|
|
|
|
let status =
|
|
|
|
|
(notLoaded && <LoadingPlaceholder className={styles.statusMessage} text="Loading..." />) ||
|
|
|
|
|
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 showStatus = status && <div className={styles.statusMessage}>{status}</div>;
|
|
|
|
@@ -292,6 +329,8 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
? 'The current prefix filter is not available with the current search terms.'
|
|
|
|
|
: undefined;
|
|
|
|
|
|
|
|
|
|
const disableSearch = metricNamesStatus.error || metricNamesStatus.isLoading;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className={styles.container}>
|
|
|
|
|
<div className={styles.header}>
|
|
|
|
@@ -301,20 +340,33 @@ export class MetricSelectScene extends SceneObjectBase<MetricSelectSceneState> {
|
|
|
|
|
prefix={<Icon name={'search'} />}
|
|
|
|
|
value={searchQuery}
|
|
|
|
|
onChange={model.onSearchChange}
|
|
|
|
|
disabled={disableSearch}
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
<InlineSwitch showLabel={true} label="Show previews" value={showPreviews} onChange={model.onTogglePreviews} />
|
|
|
|
|
<InlineSwitch
|
|
|
|
|
showLabel={true}
|
|
|
|
|
label="Show previews"
|
|
|
|
|
value={showPreviews}
|
|
|
|
|
onChange={model.onTogglePreviews}
|
|
|
|
|
disabled={disableSearch}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className={styles.header}>
|
|
|
|
|
<Field label="Filter by prefix" error={prefixError} invalid={true}>
|
|
|
|
|
<Field label="Filter by prefix" error={prefixError} invalid={!!prefixError}>
|
|
|
|
|
<MetricCategoryCascader
|
|
|
|
|
metricNames={metricsAfterSearch || []}
|
|
|
|
|
onSelect={model.onPrefixFilterChange}
|
|
|
|
|
disabled={metricsAfterSearch == null}
|
|
|
|
|
disabled={disableSearch}
|
|
|
|
|
initialValue={prefixFilter}
|
|
|
|
|
/>
|
|
|
|
|
</Field>
|
|
|
|
|
</div>
|
|
|
|
|
{metricNamesStatus.error && (
|
|
|
|
|
<Alert title="Unable to retrieve metric names" severity="error">
|
|
|
|
|
<div>We are unable to connect to your data source. Double check your data source URL and credentials.</div>
|
|
|
|
|
<div>({metricNamesStatus.error})</div>
|
|
|
|
|
</Alert>
|
|
|
|
|
)}
|
|
|
|
|
{showStatus}
|
|
|
|
|
<model.state.body.Component model={model.state.body} />
|
|
|
|
|
</div>
|
|
|
|
@@ -332,6 +384,7 @@ function getMetricNamesVariableSet() {
|
|
|
|
|
includeAll: true,
|
|
|
|
|
defaultToAll: true,
|
|
|
|
|
skipUrlSync: true,
|
|
|
|
|
refresh: VariableRefresh.onTimeRangeChanged,
|
|
|
|
|
query: { query: `label_values(${VAR_FILTERS_EXPR},__name__)`, refId: 'A' },
|
|
|
|
|
}),
|
|
|
|
|
],
|
|
|
|
@@ -436,3 +489,18 @@ function createSearchRegExp(spaceSeparatedMetricNames?: string) {
|
|
|
|
|
// The ?=(...) lookahead allows us to match these in any order.
|
|
|
|
|
return new RegExp(regex, 'igy');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function useVariableStatus(name: string, sceneObject: SceneObject) {
|
|
|
|
|
const variable = sceneGraph.lookupVariable(VAR_METRIC_NAMES, sceneObject);
|
|
|
|
|
|
|
|
|
|
const useVariableState = useCallback(() => {
|
|
|
|
|
if (variable) {
|
|
|
|
|
return variable.useState();
|
|
|
|
|
}
|
|
|
|
|
return undefined;
|
|
|
|
|
}, [variable]);
|
|
|
|
|
|
|
|
|
|
const { error, loading } = useVariableState() || {};
|
|
|
|
|
|
|
|
|
|
return { isLoading: !!loading, error };
|
|
|
|
|
}
|
|
|
|
|