123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- import { Subscription } from 'rxjs';
- import { DataSourceRef } from '@grafana/data';
- import { getDataSourceSrv, toDataQueryError } from '@grafana/runtime';
- import { ThunkResult } from '../../../types';
- import { getVariableQueryEditor } from '../editor/getVariableQueryEditor';
- import { addVariableEditorError, changeVariableEditorExtended, removeVariableEditorError } from '../editor/reducer';
- import { getQueryVariableEditorState } from '../editor/selectors';
- import { updateOptions } from '../state/actions';
- import { toKeyedAction } from '../state/keyedVariablesReducer';
- import { getVariable, getVariablesState } from '../state/selectors';
- import { changeVariableProp } from '../state/sharedReducer';
- import { KeyedVariableIdentifier } from '../state/types';
- import { QueryVariableModel } from '../types';
- import { hasOngoingTransaction, toKeyedVariableIdentifier, toVariablePayload } from '../utils';
- import { getVariableQueryRunner } from './VariableQueryRunner';
- import { variableQueryObserver } from './variableQueryObserver';
- export const updateQueryVariableOptions = (
- identifier: KeyedVariableIdentifier,
- searchFilter?: string
- ): ThunkResult<void> => {
- return async (dispatch, getState) => {
- try {
- const { rootStateKey } = identifier;
- if (!hasOngoingTransaction(rootStateKey, getState())) {
- // we might have cancelled a batch so then variable state is removed
- return;
- }
- const variableInState = getVariable<QueryVariableModel>(identifier, getState());
- if (getVariablesState(rootStateKey, getState()).editor.id === variableInState.id) {
- dispatch(toKeyedAction(rootStateKey, removeVariableEditorError({ errorProp: 'update' })));
- }
- const datasource = await getDataSourceSrv().get(variableInState.datasource ?? '');
- // We need to await the result from variableQueryRunner before moving on otherwise variables dependent on this
- // variable will have the wrong current value as input
- await new Promise((resolve, reject) => {
- const subscription: Subscription = new Subscription();
- const observer = variableQueryObserver(resolve, reject, subscription);
- const responseSubscription = getVariableQueryRunner().getResponse(identifier).subscribe(observer);
- subscription.add(responseSubscription);
- getVariableQueryRunner().queueRequest({ identifier, datasource, searchFilter });
- });
- } catch (err) {
- const error = toDataQueryError(err);
- const { rootStateKey } = identifier;
- if (getVariablesState(rootStateKey, getState()).editor.id === identifier.id) {
- dispatch(
- toKeyedAction(rootStateKey, addVariableEditorError({ errorProp: 'update', errorText: error.message }))
- );
- }
- throw error;
- }
- };
- };
- export const initQueryVariableEditor =
- (identifier: KeyedVariableIdentifier): ThunkResult<void> =>
- async (dispatch, getState) => {
- const variable = getVariable<QueryVariableModel>(identifier, getState());
- await dispatch(changeQueryVariableDataSource(toKeyedVariableIdentifier(variable), variable.datasource));
- };
- export const changeQueryVariableDataSource = (
- identifier: KeyedVariableIdentifier,
- name: DataSourceRef | null
- ): ThunkResult<void> => {
- return async (dispatch, getState) => {
- try {
- const { rootStateKey } = identifier;
- const { editor } = getVariablesState(rootStateKey, getState());
- const extendedEditorState = getQueryVariableEditorState(editor);
- const previousDatasource = extendedEditorState?.dataSource;
- const dataSource = await getDataSourceSrv().get(name ?? '');
- if (previousDatasource && previousDatasource.type !== dataSource?.type) {
- dispatch(
- toKeyedAction(
- rootStateKey,
- changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: '' }))
- )
- );
- }
- const VariableQueryEditor = await getVariableQueryEditor(dataSource);
- dispatch(
- toKeyedAction(
- rootStateKey,
- changeVariableEditorExtended({
- dataSource,
- VariableQueryEditor,
- })
- )
- );
- } catch (err) {
- console.error(err);
- }
- };
- };
- export const changeQueryVariableQuery =
- (identifier: KeyedVariableIdentifier, query: any, definition?: string): ThunkResult<void> =>
- async (dispatch, getState) => {
- const { rootStateKey } = identifier;
- const variableInState = getVariable<QueryVariableModel>(identifier, getState());
- if (hasSelfReferencingQuery(variableInState.name, query)) {
- const errorText = 'Query cannot contain a reference to itself. Variable: $' + variableInState.name;
- dispatch(toKeyedAction(rootStateKey, addVariableEditorError({ errorProp: 'query', errorText })));
- return;
- }
- dispatch(toKeyedAction(rootStateKey, removeVariableEditorError({ errorProp: 'query' })));
- dispatch(
- toKeyedAction(
- rootStateKey,
- changeVariableProp(toVariablePayload(identifier, { propName: 'query', propValue: query }))
- )
- );
- if (definition) {
- dispatch(
- toKeyedAction(
- rootStateKey,
- changeVariableProp(toVariablePayload(identifier, { propName: 'definition', propValue: definition }))
- )
- );
- } else if (typeof query === 'string') {
- dispatch(
- toKeyedAction(
- rootStateKey,
- changeVariableProp(toVariablePayload(identifier, { propName: 'definition', propValue: query }))
- )
- );
- }
- await dispatch(updateOptions(identifier));
- };
- export function hasSelfReferencingQuery(name: string, query: any): boolean {
- if (typeof query === 'string' && query.match(new RegExp('\\$' + name + '(/| |$)'))) {
- return true;
- }
- const flattened = flattenQuery(query);
- for (let prop in flattened) {
- if (flattened.hasOwnProperty(prop)) {
- const value = flattened[prop];
- if (typeof value === 'string' && value.match(new RegExp('\\$' + name + '(/| |$)'))) {
- return true;
- }
- }
- }
- return false;
- }
- /*
- * Function that takes any object and flattens all props into one level deep object
- * */
- export function flattenQuery(query: any): any {
- if (typeof query !== 'object') {
- return { query };
- }
- const keys = Object.keys(query);
- const flattened = keys.reduce((all, key) => {
- const value = query[key];
- if (typeof value !== 'object') {
- all[key] = value;
- return all;
- }
- const result = flattenQuery(value);
- for (let childProp in result) {
- if (result.hasOwnProperty(childProp)) {
- all[`${key}_${childProp}`] = result[childProp];
- }
- }
- return all;
- }, {} as Record<string, any>);
- return flattened;
- }
|