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/plugins/datasource/graphite/state/helpers.ts

202 lines
6.6 KiB

import { clone, some } from 'lodash';
import { createErrorNotification } from '../../../../core/copy/appNotification';
import { notifyApp } from '../../../../core/reducers/appNotification';
import { dispatch } from '../../../../store/store';
import { FuncInstance } from '../gfunc';
import { GraphiteQuery, GraphiteTagOperator } from '../types';
import { GraphiteQueryEditorState } from './store';
/**
* Helpers used by reducers and providers. They modify state object directly so should operate on a copy of the state.
*/
export const GRAPHITE_TAG_OPERATORS: GraphiteTagOperator[] = ['=', '!=', '=~', '!=~'];
/**
* Tag names and metric names are displayed in a single dropdown. This prefix is used to
* distinguish both in the UI.
*/
export const TAG_PREFIX = 'tag: ';
/**
* Create new AST based on new query.
* Build segments from parsed metric name and functions.
*/
export async function parseTarget(state: GraphiteQueryEditorState): Promise<void> {
state.queryModel.parseTarget();
await buildSegments(state);
}
/**
* Create segments out of the current metric path + add "select metrics" if it's possible to add more to the path
*/
export async function buildSegments(state: GraphiteQueryEditorState, modifyLastSegment = true): Promise<void> {
// Start with a shallow copy from the model, then check if "select metric" segment should be added at the end
state.segments = clone(state.queryModel.segments);
const checkOtherSegmentsIndex = state.queryModel.checkOtherSegmentsIndex || 0;
await checkOtherSegments(state, checkOtherSegmentsIndex, modifyLastSegment);
}
/**
* Add "select metric" segment at the end
*/
export function addSelectMetricSegment(state: GraphiteQueryEditorState): void {
state.queryModel.addSelectMetricSegment();
state.segments.push({ value: 'select metric', fake: true });
}
/**
* Validates the state after adding or changing a segment:
* - adds "select metric" only when more segments can be added to the metric name
* - check if subsequent segments are still valid if in-between segment changes and
* removes invalid segments.
*/
export async function checkOtherSegments(
state: GraphiteQueryEditorState,
fromIndex: number,
modifyLastSegment = true
): Promise<void> {
if (state.queryModel.segments.length === 1 && state.queryModel.segments[0].type === 'series-ref') {
return;
}
if (fromIndex === 0) {
addSelectMetricSegment(state);
return;
}
const currentFromIndex = fromIndex + 1;
const path = state.queryModel.getSegmentPathUpTo(currentFromIndex);
if (path === '') {
return;
}
try {
const segments = await state.datasource.metricFindQuery(path);
if (segments.length === 0) {
if (path !== '' && modifyLastSegment) {
state.queryModel.segments = state.queryModel.segments.splice(0, currentFromIndex);
state.segments = state.segments.splice(0, currentFromIndex);
if (!some(state.segments, { fake: true })) {
addSelectMetricSegment(state);
}
}
} else if (segments[0].expandable) {
if (state.segments.length === fromIndex) {
addSelectMetricSegment(state);
} else {
await checkOtherSegments(state, currentFromIndex);
}
}
} catch (err) {
if (err instanceof Error) {
handleMetricsAutoCompleteError(state, err);
}
}
}
export function spliceSegments(state: GraphiteQueryEditorState, index: number): void {
state.segments = state.segments.splice(0, index);
state.queryModel.segments = state.queryModel.segments.splice(0, index);
}
export function emptySegments(state: GraphiteQueryEditorState): void {
state.queryModel.segments = [];
state.segments = [];
}
/**
* When seriesByTag function is added the UI changes it's state and only tags can be added from now.
*/
export async function addSeriesByTagFunc(state: GraphiteQueryEditorState, tag: string): Promise<void> {
const newFunc = state.datasource.createFuncInstance('seriesByTag', {
withDefaultParams: false,
});
const tagParam = `${tag}=`;
newFunc.params = [tagParam];
state.queryModel.addFunction(newFunc);
newFunc.added = true;
emptySegments(state);
handleTargetChanged(state);
await parseTarget(state);
}
export function smartlyHandleNewAliasByNode(state: GraphiteQueryEditorState, func: FuncInstance): void {
if (func.def.name !== 'aliasByNode') {
return;
}
for (let i = 0; i < state.segments.length; i++) {
if (state.segments[i].value.indexOf('*') >= 0) {
func.params[0] = i;
func.added = false;
handleTargetChanged(state);
return;
}
}
}
/**
* Pauses running the query to allow selecting tag value. This is to prevent getting errors if the query is run
* for a tag with no selected value.
*/
export function pause(state: GraphiteQueryEditorState): void {
state.paused = true;
}
export function removeTagPrefix(value: string): string {
return value.replace(TAG_PREFIX, '');
}
export function handleTargetChanged(state: GraphiteQueryEditorState): void {
if (state.queryModel.error) {
return;
}
let oldTarget = state.queryModel.target.target;
// Interpolate from other queries:
// Because of mixed data sources the list may contain queries for non-Graphite data sources. To ensure a valid query
// is used for interpolation we should check required properties are passed though in theory it allows to interpolate
// with queries that contain "target" property as well.
state.queryModel.updateModelTarget(
(state.queries || []).filter((query) => 'target' in query && typeof (query as GraphiteQuery).target === 'string')
);
// remove spaces from old and new targets
const newTarget = state.queryModel.target.target.replace(/\s+/g, '');
oldTarget = oldTarget.replace(/\s+/g, '');
if (newTarget !== oldTarget && !state.paused) {
state.refresh();
}
}
/**
* When metrics autocomplete fails - the error is shown, but only once per page view
*/
export function handleMetricsAutoCompleteError(
state: GraphiteQueryEditorState,
error: Error
): GraphiteQueryEditorState {
if (!state.metricAutoCompleteErrorShown) {
state.metricAutoCompleteErrorShown = true;
dispatch(notifyApp(createErrorNotification(`Fetching metrics failed: ${error.message}.`)));
}
return state;
}
/**
* When tags autocomplete fails - the error is shown, but only once per page view
*/
export function handleTagsAutoCompleteError(state: GraphiteQueryEditorState, error: Error): GraphiteQueryEditorState {
if (!state.tagsAutoCompleteErrorShown) {
state.tagsAutoCompleteErrorShown = true;
dispatch(notifyApp(createErrorNotification(`Fetching tags failed: ${error.message}.`)));
}
return state;
}