actions.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import { cloneDeep } from 'lodash';
  2. import { DataSourceRef, getDataSourceRef } from '@grafana/data';
  3. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  4. import { AdHocVariableFilter, AdHocVariableModel } from 'app/features/variables/types';
  5. import { StoreState, ThunkResult } from 'app/types';
  6. import { changeVariableEditorExtended } from '../editor/reducer';
  7. import { getAdhocVariableEditorState } from '../editor/selectors';
  8. import { isAdHoc } from '../guard';
  9. import { variableUpdated } from '../state/actions';
  10. import { toKeyedAction } from '../state/keyedVariablesReducer';
  11. import { getLastKey, getNewVariableIndex, getVariable, getVariablesState } from '../state/selectors';
  12. import { addVariable, changeVariableProp } from '../state/sharedReducer';
  13. import { AddVariable, KeyedVariableIdentifier } from '../state/types';
  14. import { toKeyedVariableIdentifier, toVariablePayload } from '../utils';
  15. import {
  16. AdHocVariabelFilterUpdate,
  17. filterAdded,
  18. filterRemoved,
  19. filtersRestored,
  20. filterUpdated,
  21. initialAdHocVariableModelState,
  22. } from './reducer';
  23. export interface AdHocTableOptions {
  24. datasource: DataSourceRef;
  25. key: string;
  26. value: string;
  27. operator: string;
  28. }
  29. const filterTableName = 'Filters';
  30. export const applyFilterFromTable = (options: AdHocTableOptions): ThunkResult<void> => {
  31. return async (dispatch, getState) => {
  32. let variable = getVariableByOptions(options, getState());
  33. if (!variable) {
  34. dispatch(createAdHocVariable(options));
  35. variable = getVariableByOptions(options, getState());
  36. if (!variable) {
  37. return;
  38. }
  39. }
  40. const index = variable.filters.findIndex((f) => f.key === options.key && f.value === options.value);
  41. if (index === -1) {
  42. const { value, key, operator } = options;
  43. const filter = { value, key, operator, condition: '' };
  44. return await dispatch(addFilter(toKeyedVariableIdentifier(variable), filter));
  45. }
  46. const filter = { ...variable.filters[index], operator: options.operator };
  47. return await dispatch(changeFilter(toKeyedVariableIdentifier(variable), { index, filter }));
  48. };
  49. };
  50. export const changeFilter = (
  51. identifier: KeyedVariableIdentifier,
  52. update: AdHocVariabelFilterUpdate
  53. ): ThunkResult<void> => {
  54. return async (dispatch, getState) => {
  55. const variable = getVariable(identifier, getState());
  56. dispatch(toKeyedAction(identifier.rootStateKey, filterUpdated(toVariablePayload(variable, update))));
  57. await dispatch(variableUpdated(toKeyedVariableIdentifier(variable), true));
  58. };
  59. };
  60. export const removeFilter = (identifier: KeyedVariableIdentifier, index: number): ThunkResult<void> => {
  61. return async (dispatch, getState) => {
  62. const variable = getVariable(identifier, getState());
  63. dispatch(toKeyedAction(identifier.rootStateKey, filterRemoved(toVariablePayload(variable, index))));
  64. await dispatch(variableUpdated(toKeyedVariableIdentifier(variable), true));
  65. };
  66. };
  67. export const addFilter = (identifier: KeyedVariableIdentifier, filter: AdHocVariableFilter): ThunkResult<void> => {
  68. return async (dispatch, getState) => {
  69. const variable = getVariable(identifier, getState());
  70. dispatch(toKeyedAction(identifier.rootStateKey, filterAdded(toVariablePayload(variable, filter))));
  71. await dispatch(variableUpdated(toKeyedVariableIdentifier(variable), true));
  72. };
  73. };
  74. export const setFiltersFromUrl = (
  75. identifier: KeyedVariableIdentifier,
  76. filters: AdHocVariableFilter[]
  77. ): ThunkResult<void> => {
  78. return async (dispatch, getState) => {
  79. const variable = getVariable(identifier, getState());
  80. dispatch(toKeyedAction(identifier.rootStateKey, filtersRestored(toVariablePayload(variable, filters))));
  81. await dispatch(variableUpdated(toKeyedVariableIdentifier(variable), true));
  82. };
  83. };
  84. export const changeVariableDatasource = (
  85. identifier: KeyedVariableIdentifier,
  86. datasource?: DataSourceRef
  87. ): ThunkResult<void> => {
  88. return async (dispatch, getState) => {
  89. const { editor } = getVariablesState(identifier.rootStateKey, getState());
  90. const extended = getAdhocVariableEditorState(editor);
  91. const variable = getVariable(identifier, getState());
  92. dispatch(
  93. toKeyedAction(
  94. identifier.rootStateKey,
  95. changeVariableProp(toVariablePayload(variable, { propName: 'datasource', propValue: datasource }))
  96. )
  97. );
  98. const ds = await getDatasourceSrv().get(datasource);
  99. // TS TODO: ds is not typed to be optional - is this check unnecessary or is the type incorrect?
  100. const message = ds?.getTagKeys
  101. ? 'Ad hoc filters are applied automatically to all queries that target this data source'
  102. : 'This data source does not support ad hoc filters yet.';
  103. dispatch(
  104. toKeyedAction(
  105. identifier.rootStateKey,
  106. changeVariableEditorExtended({
  107. infoText: message,
  108. dataSources: extended?.dataSources ?? [],
  109. })
  110. )
  111. );
  112. };
  113. };
  114. export const initAdHocVariableEditor =
  115. (key: string): ThunkResult<void> =>
  116. (dispatch) => {
  117. const dataSources = getDatasourceSrv().getList({ metrics: true, variables: true });
  118. const selectable = dataSources.reduce(
  119. (all: Array<{ text: string; value: DataSourceRef | null }>, ds) => {
  120. if (ds.meta.mixed) {
  121. return all;
  122. }
  123. const text = ds.isDefault ? `${ds.name} (default)` : ds.name;
  124. const value = getDataSourceRef(ds);
  125. all.push({ text, value });
  126. return all;
  127. },
  128. [{ text: '', value: {} }]
  129. );
  130. dispatch(
  131. toKeyedAction(
  132. key,
  133. changeVariableEditorExtended({
  134. dataSources: selectable,
  135. })
  136. )
  137. );
  138. };
  139. const createAdHocVariable = (options: AdHocTableOptions): ThunkResult<void> => {
  140. return (dispatch, getState) => {
  141. const key = getLastKey(getState());
  142. const model: AdHocVariableModel = {
  143. ...cloneDeep(initialAdHocVariableModelState),
  144. datasource: options.datasource,
  145. name: filterTableName,
  146. id: filterTableName,
  147. rootStateKey: key,
  148. };
  149. const global = false;
  150. const index = getNewVariableIndex(key, getState());
  151. const identifier: KeyedVariableIdentifier = { type: 'adhoc', id: model.id, rootStateKey: key };
  152. dispatch(toKeyedAction(key, addVariable(toVariablePayload<AddVariable>(identifier, { global, model, index }))));
  153. };
  154. };
  155. const getVariableByOptions = (options: AdHocTableOptions, state: StoreState): AdHocVariableModel | undefined => {
  156. const key = getLastKey(state);
  157. const templatingState = getVariablesState(key, state);
  158. return Object.values(templatingState.variables).find(
  159. (v) => isAdHoc(v) && v.datasource?.uid === options.datasource.uid
  160. ) as AdHocVariableModel;
  161. };