123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- import { clone } 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 path = state.queryModel.getSegmentPathUpTo(fromIndex + 1);
- 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, fromIndex);
- state.segments = state.segments.splice(0, fromIndex);
- addSelectMetricSegment(state);
- }
- } else if (segments[0].expandable) {
- if (state.segments.length === fromIndex) {
- addSelectMetricSegment(state);
- } else {
- await checkOtherSegments(state, fromIndex + 1);
- }
- }
- } catch (err) {
- 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;
- }
- const 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')
- );
- if (state.queryModel.target.target !== 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;
- }
|