utils.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import { isEmpty, isObject, mapValues, omitBy } from 'lodash';
  2. import {
  3. AbsoluteTimeRange,
  4. DataSourceApi,
  5. EventBusExtended,
  6. ExploreUrlState,
  7. getDefaultTimeRange,
  8. HistoryItem,
  9. LoadingState,
  10. PanelData,
  11. } from '@grafana/data';
  12. import { ExplorePanelData } from 'app/types';
  13. import { ExploreGraphStyle, ExploreItemState } from 'app/types/explore';
  14. import store from '../../../core/store';
  15. import { clearQueryKeys, lastUsedDatasourceKeyForOrgId, toGraphStyle } from '../../../core/utils/explore';
  16. import { getDatasourceSrv } from '../../plugins/datasource_srv';
  17. import { toRawTimeRange } from '../utils/time';
  18. export const DEFAULT_RANGE = {
  19. from: 'now-6h',
  20. to: 'now',
  21. };
  22. const GRAPH_STYLE_KEY = 'grafana.explore.style.graph';
  23. export const storeGraphStyle = (graphStyle: string): void => {
  24. store.set(GRAPH_STYLE_KEY, graphStyle);
  25. };
  26. const loadGraphStyle = (): ExploreGraphStyle => {
  27. const data = store.get(GRAPH_STYLE_KEY);
  28. return toGraphStyle(data);
  29. };
  30. /**
  31. * Returns a fresh Explore area state
  32. */
  33. export const makeExplorePaneState = (): ExploreItemState => ({
  34. containerWidth: 0,
  35. datasourceInstance: null,
  36. datasourceMissing: false,
  37. history: [],
  38. queries: [],
  39. initialized: false,
  40. range: {
  41. from: null,
  42. to: null,
  43. raw: DEFAULT_RANGE,
  44. } as any,
  45. absoluteRange: {
  46. from: null,
  47. to: null,
  48. } as any,
  49. scanning: false,
  50. loading: false,
  51. queryKeys: [],
  52. isLive: false,
  53. isPaused: false,
  54. queryResponse: createEmptyQueryResponse(),
  55. tableResult: null,
  56. graphResult: null,
  57. logsResult: null,
  58. eventBridge: null as unknown as EventBusExtended,
  59. cache: [],
  60. richHistory: [],
  61. logsVolumeDataProvider: undefined,
  62. logsVolumeData: undefined,
  63. graphStyle: loadGraphStyle(),
  64. panelsState: {},
  65. });
  66. export const createEmptyQueryResponse = (): ExplorePanelData => ({
  67. state: LoadingState.NotStarted,
  68. series: [],
  69. timeRange: getDefaultTimeRange(),
  70. graphFrames: [],
  71. logsFrames: [],
  72. traceFrames: [],
  73. nodeGraphFrames: [],
  74. tableFrames: [],
  75. graphResult: null,
  76. logsResult: null,
  77. tableResult: null,
  78. });
  79. export async function loadAndInitDatasource(
  80. orgId: number,
  81. datasourceUid?: string
  82. ): Promise<{ history: HistoryItem[]; instance: DataSourceApi }> {
  83. let instance;
  84. try {
  85. instance = await getDatasourceSrv().get(datasourceUid);
  86. } catch (error) {
  87. // Falling back to the default data source in case the provided data source was not found.
  88. // It may happen if last used data source or the data source provided in the URL has been
  89. // removed or it is not provisioned anymore.
  90. instance = await getDatasourceSrv().get();
  91. }
  92. if (instance.init) {
  93. try {
  94. instance.init();
  95. } catch (err) {
  96. // TODO: should probably be handled better
  97. console.error(err);
  98. }
  99. }
  100. const historyKey = `grafana.explore.history.${instance.meta?.id}`;
  101. const history = store.getObject<HistoryItem[]>(historyKey, []);
  102. // Save last-used datasource
  103. store.set(lastUsedDatasourceKeyForOrgId(orgId), instance.uid);
  104. return { history, instance };
  105. }
  106. // recursively walks an object, removing keys where the value is undefined
  107. // if the resulting object is empty, returns undefined
  108. function pruneObject(obj: object): object | undefined {
  109. let pruned = mapValues(obj, (value) => (isObject(value) ? pruneObject(value) : value));
  110. pruned = omitBy<typeof pruned>(pruned, isEmpty);
  111. if (isEmpty(pruned)) {
  112. return undefined;
  113. }
  114. return pruned;
  115. }
  116. export function getUrlStateFromPaneState(pane: ExploreItemState): ExploreUrlState {
  117. return {
  118. // datasourceInstance should not be undefined anymore here but in case there is some path for it to be undefined
  119. // lets just fallback instead of crashing.
  120. datasource: pane.datasourceInstance?.name || '',
  121. queries: pane.queries.map(clearQueryKeys),
  122. range: toRawTimeRange(pane.range),
  123. // don't include panelsState in the url unless a piece of state is actually set
  124. panelsState: pruneObject(pane.panelsState),
  125. };
  126. }
  127. export function createCacheKey(absRange: AbsoluteTimeRange) {
  128. const params = {
  129. from: absRange.from,
  130. to: absRange.to,
  131. };
  132. const cacheKey = Object.entries(params)
  133. .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v.toString())}`)
  134. .join('&');
  135. return cacheKey;
  136. }
  137. export function getResultsFromCache(
  138. cache: Array<{ key: string; value: PanelData }>,
  139. absoluteRange: AbsoluteTimeRange
  140. ): PanelData | undefined {
  141. const cacheKey = createCacheKey(absoluteRange);
  142. const cacheIdx = cache.findIndex((c) => c.key === cacheKey);
  143. const cacheValue = cacheIdx >= 0 ? cache[cacheIdx].value : undefined;
  144. return cacheValue;
  145. }