operators.ts 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import { from, of, OperatorFunction } from 'rxjs';
  2. import { map, mergeMap } from 'rxjs/operators';
  3. import { FieldType, getFieldDisplayName, isDataFrame, MetricFindValue, PanelData } from '@grafana/data';
  4. import { getProcessedDataFrames } from 'app/features/query/state/runRequest';
  5. import { ThunkDispatch } from '../../../types';
  6. import { validateVariableSelectionState } from '../state/actions';
  7. import { toKeyedAction } from '../state/keyedVariablesReducer';
  8. import { QueryVariableModel } from '../types';
  9. import { getTemplatedRegex, toKeyedVariableIdentifier, toVariablePayload } from '../utils';
  10. import { updateVariableOptions } from './reducer';
  11. export function toMetricFindValues(): OperatorFunction<PanelData, MetricFindValue[]> {
  12. return (source) =>
  13. source.pipe(
  14. map((panelData) => {
  15. const frames = panelData.series;
  16. if (!frames || !frames.length) {
  17. return [];
  18. }
  19. if (areMetricFindValues(frames)) {
  20. return frames;
  21. }
  22. const processedDataFrames = getProcessedDataFrames(frames);
  23. const metrics: MetricFindValue[] = [];
  24. let valueIndex = -1;
  25. let textIndex = -1;
  26. let stringIndex = -1;
  27. let expandableIndex = -1;
  28. for (const frame of processedDataFrames) {
  29. for (let index = 0; index < frame.fields.length; index++) {
  30. const field = frame.fields[index];
  31. const fieldName = getFieldDisplayName(field, frame, frames).toLowerCase();
  32. if (field.type === FieldType.string && stringIndex === -1) {
  33. stringIndex = index;
  34. }
  35. if (fieldName === 'text' && field.type === FieldType.string && textIndex === -1) {
  36. textIndex = index;
  37. }
  38. if (fieldName === 'value' && field.type === FieldType.string && valueIndex === -1) {
  39. valueIndex = index;
  40. }
  41. if (
  42. fieldName === 'expandable' &&
  43. (field.type === FieldType.boolean || field.type === FieldType.number) &&
  44. expandableIndex === -1
  45. ) {
  46. expandableIndex = index;
  47. }
  48. }
  49. }
  50. if (stringIndex === -1) {
  51. throw new Error("Couldn't find any field of type string in the results.");
  52. }
  53. for (const frame of frames) {
  54. for (let index = 0; index < frame.length; index++) {
  55. const expandable = expandableIndex !== -1 ? frame.fields[expandableIndex].values.get(index) : undefined;
  56. const string = frame.fields[stringIndex].values.get(index);
  57. const text = textIndex !== -1 ? frame.fields[textIndex].values.get(index) : null;
  58. const value = valueIndex !== -1 ? frame.fields[valueIndex].values.get(index) : null;
  59. if (valueIndex === -1 && textIndex === -1) {
  60. metrics.push({ text: string, value: string, expandable });
  61. continue;
  62. }
  63. if (valueIndex === -1 && textIndex !== -1) {
  64. metrics.push({ text, value: text, expandable });
  65. continue;
  66. }
  67. if (valueIndex !== -1 && textIndex === -1) {
  68. metrics.push({ text: value, value, expandable });
  69. continue;
  70. }
  71. metrics.push({ text, value, expandable });
  72. }
  73. }
  74. return metrics;
  75. })
  76. );
  77. }
  78. export function updateOptionsState(args: {
  79. variable: QueryVariableModel;
  80. dispatch: ThunkDispatch;
  81. getTemplatedRegexFunc: typeof getTemplatedRegex;
  82. }): OperatorFunction<MetricFindValue[], void> {
  83. return (source) =>
  84. source.pipe(
  85. map((results) => {
  86. const { variable, dispatch, getTemplatedRegexFunc } = args;
  87. if (!variable.rootStateKey) {
  88. console.error('updateOptionsState: variable.rootStateKey is not defined');
  89. return;
  90. }
  91. const templatedRegex = getTemplatedRegexFunc(variable);
  92. const payload = toVariablePayload(variable, { results, templatedRegex });
  93. dispatch(toKeyedAction(variable.rootStateKey, updateVariableOptions(payload)));
  94. })
  95. );
  96. }
  97. export function validateVariableSelection(args: {
  98. variable: QueryVariableModel;
  99. dispatch: ThunkDispatch;
  100. searchFilter?: string;
  101. }): OperatorFunction<void, void> {
  102. return (source) =>
  103. source.pipe(
  104. mergeMap(() => {
  105. const { dispatch, variable, searchFilter } = args;
  106. // If we are searching options there is no need to validate selection state
  107. // This condition was added to as validateVariableSelectionState will update the current value of the variable
  108. // So after search and selection the current value is already update so no setValue, refresh and URL update is performed
  109. // The if statement below fixes https://github.com/grafana/grafana/issues/25671
  110. if (!searchFilter) {
  111. return from(dispatch(validateVariableSelectionState(toKeyedVariableIdentifier(variable))));
  112. }
  113. return of<void>();
  114. })
  115. );
  116. }
  117. export function areMetricFindValues(data: any[]): data is MetricFindValue[] {
  118. if (!data) {
  119. return false;
  120. }
  121. if (!data.length) {
  122. return true;
  123. }
  124. const firstValue: any = data[0];
  125. if (isDataFrame(firstValue)) {
  126. return false;
  127. }
  128. for (const firstValueKey in firstValue) {
  129. if (!firstValue.hasOwnProperty(firstValueKey)) {
  130. continue;
  131. }
  132. if (
  133. firstValue[firstValueKey] !== null &&
  134. typeof firstValue[firstValueKey] !== 'string' &&
  135. typeof firstValue[firstValueKey] !== 'number'
  136. ) {
  137. continue;
  138. }
  139. const key = firstValueKey.toLowerCase();
  140. if (key === 'text' || key === 'value') {
  141. return true;
  142. }
  143. }
  144. return false;
  145. }