explore.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. import _ from 'lodash';
  2. import { renderUrl } from 'app/core/utils/url';
  3. import kbn from 'app/core/utils/kbn';
  4. import store from 'app/core/store';
  5. import colors from 'app/core/utils/colors';
  6. import { parse as parseDate } from 'app/core/utils/datemath';
  7. import TimeSeries from 'app/core/time_series2';
  8. import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
  9. import { ExploreState, ExploreUrlState, HistoryItem, QueryTransaction } from 'app/types/explore';
  10. import { DataQuery, RawTimeRange, IntervalValues, DataSourceApi } from 'app/types/series';
  11. export const DEFAULT_RANGE = {
  12. from: 'now-6h',
  13. to: 'now',
  14. };
  15. const MAX_HISTORY_ITEMS = 100;
  16. /**
  17. * Returns an Explore-URL that contains a panel's queries and the dashboard time range.
  18. *
  19. * @param panel Origin panel of the jump to Explore
  20. * @param panelTargets The origin panel's query targets
  21. * @param panelDatasource The origin panel's datasource
  22. * @param datasourceSrv Datasource service to query other datasources in case the panel datasource is mixed
  23. * @param timeSrv Time service to get the current dashboard range from
  24. */
  25. export async function getExploreUrl(
  26. panel: any,
  27. panelTargets: any[],
  28. panelDatasource: any,
  29. datasourceSrv: any,
  30. timeSrv: any
  31. ) {
  32. let exploreDatasource = panelDatasource;
  33. let exploreTargets: DataQuery[] = panelTargets;
  34. let url;
  35. // Mixed datasources need to choose only one datasource
  36. if (panelDatasource.meta.id === 'mixed' && panelTargets) {
  37. // Find first explore datasource among targets
  38. let mixedExploreDatasource;
  39. for (const t of panel.targets) {
  40. const datasource = await datasourceSrv.get(t.datasource);
  41. if (datasource && datasource.meta.explore) {
  42. mixedExploreDatasource = datasource;
  43. break;
  44. }
  45. }
  46. // Add all its targets
  47. if (mixedExploreDatasource) {
  48. exploreDatasource = mixedExploreDatasource;
  49. exploreTargets = panelTargets.filter(t => t.datasource === mixedExploreDatasource.name);
  50. }
  51. }
  52. if (panelDatasource) {
  53. const range = timeSrv.timeRangeForUrl();
  54. let state: Partial<ExploreUrlState> = { range };
  55. if (exploreDatasource.getExploreState) {
  56. state = { ...state, ...exploreDatasource.getExploreState(exploreTargets) };
  57. } else {
  58. state = {
  59. ...state,
  60. datasource: panelDatasource.name,
  61. queries: exploreTargets.map(t => ({ ...t, datasource: panelDatasource.name })),
  62. };
  63. }
  64. const exploreState = JSON.stringify(state);
  65. url = renderUrl('/explore', { state: exploreState });
  66. }
  67. return url;
  68. }
  69. const clearQueryKeys: ((query: DataQuery) => object) = ({ key, refId, ...rest }) => rest;
  70. export function parseUrlState(initial: string | undefined): ExploreUrlState {
  71. if (initial) {
  72. try {
  73. const parsed = JSON.parse(decodeURI(initial));
  74. if (Array.isArray(parsed)) {
  75. if (parsed.length <= 3) {
  76. throw new Error('Error parsing compact URL state for Explore.');
  77. }
  78. const range = {
  79. from: parsed[0],
  80. to: parsed[1],
  81. };
  82. const datasource = parsed[2];
  83. const queries = parsed.slice(3);
  84. return { datasource, queries, range };
  85. }
  86. return parsed;
  87. } catch (e) {
  88. console.error(e);
  89. }
  90. }
  91. return { datasource: null, queries: [], range: DEFAULT_RANGE };
  92. }
  93. export function serializeStateToUrlParam(state: ExploreState, compact?: boolean): string {
  94. const urlState: ExploreUrlState = {
  95. datasource: state.datasourceName,
  96. queries: state.initialQueries.map(clearQueryKeys),
  97. range: state.range,
  98. };
  99. if (compact) {
  100. return JSON.stringify([urlState.range.from, urlState.range.to, urlState.datasource, ...urlState.queries]);
  101. }
  102. return JSON.stringify(urlState);
  103. }
  104. export function generateKey(index = 0): string {
  105. return `Q-${Date.now()}-${Math.random()}-${index}`;
  106. }
  107. export function generateRefId(index = 0): string {
  108. return `${index + 1}`;
  109. }
  110. export function generateQueryKeys(index = 0): { refId: string; key: string } {
  111. return { refId: generateRefId(index), key: generateKey(index) };
  112. }
  113. /**
  114. * Ensure at least one target exists and that targets have the necessary keys
  115. */
  116. export function ensureQueries(queries?: DataQuery[]): DataQuery[] {
  117. if (queries && typeof queries === 'object' && queries.length > 0) {
  118. return queries.map((query, i) => ({ ...query, ...generateQueryKeys(i) }));
  119. }
  120. return [{ ...generateQueryKeys() }];
  121. }
  122. /**
  123. * A target is non-empty when it has keys (with non-empty values) other than refId and key.
  124. */
  125. export function hasNonEmptyQuery(queries: DataQuery[]): boolean {
  126. return queries.some(
  127. query =>
  128. Object.keys(query)
  129. .map(k => query[k])
  130. .filter(v => v).length > 2
  131. );
  132. }
  133. export function calculateResultsFromQueryTransactions(
  134. queryTransactions: QueryTransaction[],
  135. datasource: any,
  136. graphInterval: number
  137. ) {
  138. const graphResult = _.flatten(
  139. queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result)
  140. );
  141. const tableResult = mergeTablesIntoModel(
  142. new TableModel(),
  143. ...queryTransactions
  144. .filter(qt => qt.resultType === 'Table' && qt.done && qt.result && qt.result.columns && qt.result.rows)
  145. .map(qt => qt.result)
  146. );
  147. const logsResult =
  148. datasource && datasource.mergeStreams
  149. ? datasource.mergeStreams(
  150. _.flatten(
  151. queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result)
  152. ),
  153. graphInterval
  154. )
  155. : undefined;
  156. return {
  157. graphResult,
  158. tableResult,
  159. logsResult,
  160. };
  161. }
  162. export function getIntervals(range: RawTimeRange, datasource: DataSourceApi, resolution: number): IntervalValues {
  163. if (!datasource || !resolution) {
  164. return { interval: '1s', intervalMs: 1000 };
  165. }
  166. const absoluteRange: RawTimeRange = {
  167. from: parseDate(range.from, false),
  168. to: parseDate(range.to, true),
  169. };
  170. return kbn.calculateInterval(absoluteRange, resolution, datasource.interval);
  171. }
  172. export function makeTimeSeriesList(dataList) {
  173. return dataList.map((seriesData, index) => {
  174. const datapoints = seriesData.datapoints || [];
  175. const alias = seriesData.target;
  176. const colorIndex = index % colors.length;
  177. const color = colors[colorIndex];
  178. const series = new TimeSeries({
  179. datapoints,
  180. alias,
  181. color,
  182. unit: seriesData.unit,
  183. });
  184. return series;
  185. });
  186. }
  187. /**
  188. * Update the query history. Side-effect: store history in local storage
  189. */
  190. export function updateHistory(history: HistoryItem[], datasourceId: string, queries: DataQuery[]): HistoryItem[] {
  191. const ts = Date.now();
  192. queries.forEach(query => {
  193. history = [{ query, ts }, ...history];
  194. });
  195. if (history.length > MAX_HISTORY_ITEMS) {
  196. history = history.slice(0, MAX_HISTORY_ITEMS);
  197. }
  198. // Combine all queries of a datasource type into one history
  199. const historyKey = `grafana.explore.history.${datasourceId}`;
  200. store.setObject(historyKey, history);
  201. return history;
  202. }
  203. export function clearHistory(datasourceId: string) {
  204. const historyKey = `grafana.explore.history.${datasourceId}`;
  205. store.delete(historyKey);
  206. }