actions.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758
  1. // Libraries
  2. // Services & Utils
  3. import store from 'app/core/store';
  4. import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
  5. import { Emitter } from 'app/core/core';
  6. import {
  7. ensureQueries,
  8. generateEmptyQuery,
  9. parseUrlState,
  10. getTimeRange,
  11. getTimeRangeFromUrl,
  12. generateNewKeyAndAddRefIdIfMissing,
  13. lastUsedDatasourceKeyForOrgId,
  14. hasNonEmptyQuery,
  15. buildQueryTransaction,
  16. clearQueryKeys,
  17. serializeStateToUrlParam,
  18. stopQueryState,
  19. updateHistory,
  20. } from 'app/core/utils/explore';
  21. // Types
  22. import { ThunkResult, ExploreUrlState, ExploreItemState } from 'app/types';
  23. import { DataSourceApi, DataQuery, DataSourceSelectItem, QueryFixAction, PanelData } from '@grafana/ui';
  24. import {
  25. RawTimeRange,
  26. LogsDedupStrategy,
  27. AbsoluteTimeRange,
  28. LoadingState,
  29. TimeRange,
  30. isDateTime,
  31. dateTimeForTimeZone,
  32. } from '@grafana/data';
  33. import { ExploreId, ExploreUIState, ExploreMode } from 'app/types/explore';
  34. import {
  35. updateDatasourceInstanceAction,
  36. changeQueryAction,
  37. changeRefreshIntervalAction,
  38. ChangeRefreshIntervalPayload,
  39. changeSizeAction,
  40. ChangeSizePayload,
  41. clearQueriesAction,
  42. initializeExploreAction,
  43. loadDatasourceMissingAction,
  44. loadDatasourcePendingAction,
  45. queriesImportedAction,
  46. LoadDatasourceReadyPayload,
  47. loadDatasourceReadyAction,
  48. modifyQueriesAction,
  49. scanStartAction,
  50. setQueriesAction,
  51. splitCloseAction,
  52. splitOpenAction,
  53. addQueryRowAction,
  54. toggleGraphAction,
  55. toggleTableAction,
  56. ToggleGraphPayload,
  57. ToggleTablePayload,
  58. updateUIStateAction,
  59. testDataSourcePendingAction,
  60. testDataSourceSuccessAction,
  61. testDataSourceFailureAction,
  62. loadExploreDatasources,
  63. changeModeAction,
  64. scanStopAction,
  65. queryStartAction,
  66. setUrlReplacedAction,
  67. changeRangeAction,
  68. historyUpdatedAction,
  69. queryEndedAction,
  70. queryStreamUpdatedAction,
  71. } from './actionTypes';
  72. import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
  73. import { getTimeZone } from 'app/features/profile/state/selectors';
  74. import { offOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
  75. import { getShiftedTimeRange } from 'app/core/utils/timePicker';
  76. import _ from 'lodash';
  77. import { updateLocation } from '../../../core/actions';
  78. import { getTimeSrv } from '../../dashboard/services/TimeSrv';
  79. /**
  80. * Updates UI state and save it to the URL
  81. */
  82. const updateExploreUIState = (exploreId: ExploreId, uiStateFragment: Partial<ExploreUIState>): ThunkResult<void> => {
  83. return dispatch => {
  84. dispatch(updateUIStateAction({ exploreId, ...uiStateFragment }));
  85. dispatch(stateSave());
  86. };
  87. };
  88. /**
  89. * Adds a query row after the row with the given index.
  90. */
  91. export function addQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
  92. return (dispatch, getState) => {
  93. const queries = getState().explore[exploreId].queries;
  94. const query = generateEmptyQuery(queries, index);
  95. dispatch(addQueryRowAction({ exploreId, index, query }));
  96. };
  97. }
  98. /**
  99. * Loads a new datasource identified by the given name.
  100. */
  101. export function changeDatasource(exploreId: ExploreId, datasource: string): ThunkResult<void> {
  102. return async (dispatch, getState) => {
  103. let newDataSourceInstance: DataSourceApi = null;
  104. if (!datasource) {
  105. newDataSourceInstance = await getDatasourceSrv().get();
  106. } else {
  107. newDataSourceInstance = await getDatasourceSrv().get(datasource);
  108. }
  109. const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
  110. const queries = getState().explore[exploreId].queries;
  111. const orgId = getState().user.orgId;
  112. dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
  113. await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
  114. if (getState().explore[exploreId].isLive) {
  115. dispatch(changeRefreshInterval(exploreId, offOption.value));
  116. }
  117. await dispatch(loadDatasource(exploreId, newDataSourceInstance, orgId));
  118. dispatch(runQueries(exploreId));
  119. };
  120. }
  121. /**
  122. * Change the display mode in Explore.
  123. */
  124. export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult<void> {
  125. return dispatch => {
  126. dispatch(clearQueriesAction({ exploreId }));
  127. dispatch(changeModeAction({ exploreId, mode }));
  128. };
  129. }
  130. /**
  131. * Query change handler for the query row with the given index.
  132. * If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
  133. */
  134. export function changeQuery(
  135. exploreId: ExploreId,
  136. query: DataQuery,
  137. index: number,
  138. override: boolean
  139. ): ThunkResult<void> {
  140. return (dispatch, getState) => {
  141. // Null query means reset
  142. if (query === null) {
  143. const queries = getState().explore[exploreId].queries;
  144. const { refId, key } = queries[index];
  145. query = generateNewKeyAndAddRefIdIfMissing({ refId, key }, queries, index);
  146. }
  147. dispatch(changeQueryAction({ exploreId, query, index, override }));
  148. if (override) {
  149. dispatch(runQueries(exploreId));
  150. }
  151. };
  152. }
  153. /**
  154. * Keep track of the Explore container size, in particular the width.
  155. * The width will be used to calculate graph intervals (number of datapoints).
  156. */
  157. export function changeSize(
  158. exploreId: ExploreId,
  159. { height, width }: { height: number; width: number }
  160. ): ActionOf<ChangeSizePayload> {
  161. return changeSizeAction({ exploreId, height, width });
  162. }
  163. export const updateTimeRange = (options: {
  164. exploreId: ExploreId;
  165. rawRange?: RawTimeRange;
  166. absoluteRange?: AbsoluteTimeRange;
  167. }): ThunkResult<void> => {
  168. return dispatch => {
  169. dispatch(updateTime({ ...options }));
  170. dispatch(runQueries(options.exploreId));
  171. };
  172. };
  173. /**
  174. * Change the refresh interval of Explore. Called from the Refresh picker.
  175. */
  176. export function changeRefreshInterval(
  177. exploreId: ExploreId,
  178. refreshInterval: string
  179. ): ActionOf<ChangeRefreshIntervalPayload> {
  180. return changeRefreshIntervalAction({ exploreId, refreshInterval });
  181. }
  182. /**
  183. * Clear all queries and results.
  184. */
  185. export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
  186. return dispatch => {
  187. dispatch(scanStopAction({ exploreId }));
  188. dispatch(clearQueriesAction({ exploreId }));
  189. dispatch(stateSave());
  190. };
  191. }
  192. /**
  193. * Loads all explore data sources and sets the chosen datasource.
  194. * If there are no datasources a missing datasource action is dispatched.
  195. */
  196. export function loadExploreDatasourcesAndSetDatasource(
  197. exploreId: ExploreId,
  198. datasourceName: string
  199. ): ThunkResult<void> {
  200. return dispatch => {
  201. const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv()
  202. .getExternal()
  203. .map(
  204. (ds: any) =>
  205. ({
  206. value: ds.name,
  207. name: ds.name,
  208. meta: ds.meta,
  209. } as DataSourceSelectItem)
  210. );
  211. dispatch(loadExploreDatasources({ exploreId, exploreDatasources }));
  212. if (exploreDatasources.length >= 1) {
  213. dispatch(changeDatasource(exploreId, datasourceName));
  214. } else {
  215. dispatch(loadDatasourceMissingAction({ exploreId }));
  216. }
  217. };
  218. }
  219. /**
  220. * Initialize Explore state with state from the URL and the React component.
  221. * Call this only on components for with the Explore state has not been initialized.
  222. */
  223. export function initializeExplore(
  224. exploreId: ExploreId,
  225. datasourceName: string,
  226. queries: DataQuery[],
  227. rawRange: RawTimeRange,
  228. mode: ExploreMode,
  229. containerWidth: number,
  230. eventBridge: Emitter,
  231. ui: ExploreUIState
  232. ): ThunkResult<void> {
  233. return async (dispatch, getState) => {
  234. const timeZone = getTimeZone(getState().user);
  235. const range = getTimeRange(timeZone, rawRange);
  236. dispatch(loadExploreDatasourcesAndSetDatasource(exploreId, datasourceName));
  237. dispatch(
  238. initializeExploreAction({
  239. exploreId,
  240. containerWidth,
  241. eventBridge,
  242. queries,
  243. range,
  244. mode,
  245. ui,
  246. })
  247. );
  248. dispatch(updateTime({ exploreId }));
  249. };
  250. }
  251. /**
  252. * Datasource loading was successfully completed.
  253. */
  254. export const loadDatasourceReady = (
  255. exploreId: ExploreId,
  256. instance: DataSourceApi,
  257. orgId: number
  258. ): ActionOf<LoadDatasourceReadyPayload> => {
  259. const historyKey = `grafana.explore.history.${instance.meta.id}`;
  260. const history = store.getObject(historyKey, []);
  261. // Save last-used datasource
  262. store.set(lastUsedDatasourceKeyForOrgId(orgId), instance.name);
  263. return loadDatasourceReadyAction({
  264. exploreId,
  265. history,
  266. });
  267. };
  268. export function importQueries(
  269. exploreId: ExploreId,
  270. queries: DataQuery[],
  271. sourceDataSource: DataSourceApi,
  272. targetDataSource: DataSourceApi
  273. ): ThunkResult<void> {
  274. return async dispatch => {
  275. if (!sourceDataSource) {
  276. // explore not initialized
  277. dispatch(queriesImportedAction({ exploreId, queries }));
  278. return;
  279. }
  280. let importedQueries = queries;
  281. // Check if queries can be imported from previously selected datasource
  282. if (sourceDataSource.meta.id === targetDataSource.meta.id) {
  283. // Keep same queries if same type of datasource
  284. importedQueries = [...queries];
  285. } else if (targetDataSource.importQueries) {
  286. // Datasource-specific importers
  287. importedQueries = await targetDataSource.importQueries(queries, sourceDataSource.meta);
  288. } else {
  289. // Default is blank queries
  290. importedQueries = ensureQueries();
  291. }
  292. const nextQueries = ensureQueries(importedQueries);
  293. dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
  294. };
  295. }
  296. /**
  297. * Tests datasource.
  298. */
  299. export const testDatasource = (exploreId: ExploreId, instance: DataSourceApi): ThunkResult<void> => {
  300. return async dispatch => {
  301. let datasourceError = null;
  302. dispatch(testDataSourcePendingAction({ exploreId }));
  303. try {
  304. const testResult = await instance.testDatasource();
  305. datasourceError = testResult.status === 'success' ? null : testResult.message;
  306. } catch (error) {
  307. datasourceError = (error && error.statusText) || 'Network error';
  308. }
  309. if (datasourceError) {
  310. dispatch(testDataSourceFailureAction({ exploreId, error: datasourceError }));
  311. return;
  312. }
  313. dispatch(testDataSourceSuccessAction({ exploreId }));
  314. };
  315. };
  316. /**
  317. * Reconnects datasource when there is a connection failure.
  318. */
  319. export const reconnectDatasource = (exploreId: ExploreId): ThunkResult<void> => {
  320. return async (dispatch, getState) => {
  321. const instance = getState().explore[exploreId].datasourceInstance;
  322. dispatch(changeDatasource(exploreId, instance.name));
  323. };
  324. };
  325. /**
  326. * Main action to asynchronously load a datasource. Dispatches lots of smaller actions for feedback.
  327. */
  328. export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi, orgId: number): ThunkResult<void> {
  329. return async (dispatch, getState) => {
  330. const datasourceName = instance.name;
  331. // Keep ID to track selection
  332. dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
  333. await dispatch(testDatasource(exploreId, instance));
  334. if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
  335. // User already changed datasource again, discard results
  336. return;
  337. }
  338. if (instance.init) {
  339. try {
  340. instance.init();
  341. } catch (err) {
  342. console.log(err);
  343. }
  344. }
  345. if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
  346. // User already changed datasource again, discard results
  347. return;
  348. }
  349. dispatch(loadDatasourceReady(exploreId, instance, orgId));
  350. };
  351. }
  352. /**
  353. * Action to modify a query given a datasource-specific modifier action.
  354. * @param exploreId Explore area
  355. * @param modification Action object with a type, e.g., ADD_FILTER
  356. * @param index Optional query row index. If omitted, the modification is applied to all query rows.
  357. * @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
  358. */
  359. export function modifyQueries(
  360. exploreId: ExploreId,
  361. modification: QueryFixAction,
  362. index: number,
  363. modifier: any
  364. ): ThunkResult<void> {
  365. return dispatch => {
  366. dispatch(modifyQueriesAction({ exploreId, modification, index, modifier }));
  367. if (!modification.preventSubmit) {
  368. dispatch(runQueries(exploreId));
  369. }
  370. };
  371. }
  372. /**
  373. * Main action to run queries and dispatches sub-actions based on which result viewers are active
  374. */
  375. export function runQueries(exploreId: ExploreId): ThunkResult<void> {
  376. return (dispatch, getState) => {
  377. dispatch(updateTime({ exploreId }));
  378. const exploreItemState = getState().explore[exploreId];
  379. const {
  380. datasourceInstance,
  381. queries,
  382. datasourceError,
  383. containerWidth,
  384. isLive: live,
  385. queryState,
  386. queryIntervals,
  387. range,
  388. scanning,
  389. history,
  390. } = exploreItemState;
  391. if (datasourceError) {
  392. // let's not run any queries if data source is in a faulty state
  393. return;
  394. }
  395. if (!hasNonEmptyQuery(queries)) {
  396. dispatch(clearQueriesAction({ exploreId }));
  397. dispatch(stateSave()); // Remember to save to state and update location
  398. return;
  399. }
  400. // Some datasource's query builders allow per-query interval limits,
  401. // but we're using the datasource interval limit for now
  402. const interval = datasourceInstance.interval;
  403. stopQueryState(queryState, 'New request issued');
  404. queryState.sendFrames = true;
  405. queryState.sendLegacy = true;
  406. const queryOptions = { interval, maxDataPoints: containerWidth, live };
  407. const datasourceId = datasourceInstance.meta.id;
  408. const transaction = buildQueryTransaction(queries, queryOptions, range, queryIntervals, scanning);
  409. queryState.onStreamingDataUpdated = () => {
  410. const response = queryState.validateStreamsAndGetPanelData();
  411. dispatch(queryStreamUpdatedAction({ exploreId, response }));
  412. };
  413. dispatch(queryStartAction({ exploreId }));
  414. queryState
  415. .execute(datasourceInstance, transaction.request)
  416. .then((response: PanelData) => {
  417. if (!response.error) {
  418. // Side-effect: Saving history in localstorage
  419. const nextHistory = updateHistory(history, datasourceId, queries);
  420. dispatch(historyUpdatedAction({ exploreId, history: nextHistory }));
  421. }
  422. dispatch(queryEndedAction({ exploreId, response }));
  423. dispatch(stateSave());
  424. // Keep scanning for results if this was the last scanning transaction
  425. if (getState().explore[exploreId].scanning) {
  426. if (_.size(response.series) === 0) {
  427. const range = getShiftedTimeRange(-1, getState().explore[exploreId].range);
  428. dispatch(updateTime({ exploreId, absoluteRange: range }));
  429. dispatch(runQueries(exploreId));
  430. } else {
  431. // We can stop scanning if we have a result
  432. dispatch(scanStopAction({ exploreId }));
  433. }
  434. }
  435. })
  436. .catch(error => {
  437. dispatch(
  438. queryEndedAction({
  439. exploreId,
  440. response: { error, legacy: [], series: [], request: transaction.request, state: LoadingState.Error },
  441. })
  442. );
  443. });
  444. };
  445. }
  446. const toRawTimeRange = (range: TimeRange): RawTimeRange => {
  447. let from = range.raw.from;
  448. if (isDateTime(from)) {
  449. from = from.valueOf().toString(10);
  450. }
  451. let to = range.raw.to;
  452. if (isDateTime(to)) {
  453. to = to.valueOf().toString(10);
  454. }
  455. return {
  456. from,
  457. to,
  458. };
  459. };
  460. export const stateSave = (): ThunkResult<void> => {
  461. return (dispatch, getState) => {
  462. const { left, right, split } = getState().explore;
  463. const orgId = getState().user.orgId.toString();
  464. const replace = left && left.urlReplaced === false;
  465. const urlStates: { [index: string]: string } = { orgId };
  466. const leftUrlState: ExploreUrlState = {
  467. datasource: left.datasourceInstance.name,
  468. queries: left.queries.map(clearQueryKeys),
  469. range: toRawTimeRange(left.range),
  470. mode: left.mode,
  471. ui: {
  472. showingGraph: left.showingGraph,
  473. showingLogs: true,
  474. showingTable: left.showingTable,
  475. dedupStrategy: left.dedupStrategy,
  476. },
  477. };
  478. urlStates.left = serializeStateToUrlParam(leftUrlState, true);
  479. if (split) {
  480. const rightUrlState: ExploreUrlState = {
  481. datasource: right.datasourceInstance.name,
  482. queries: right.queries.map(clearQueryKeys),
  483. range: toRawTimeRange(right.range),
  484. mode: right.mode,
  485. ui: {
  486. showingGraph: right.showingGraph,
  487. showingLogs: true,
  488. showingTable: right.showingTable,
  489. dedupStrategy: right.dedupStrategy,
  490. },
  491. };
  492. urlStates.right = serializeStateToUrlParam(rightUrlState, true);
  493. }
  494. dispatch(updateLocation({ query: urlStates, replace }));
  495. if (replace) {
  496. dispatch(setUrlReplacedAction({ exploreId: ExploreId.left }));
  497. }
  498. };
  499. };
  500. export const updateTime = (config: {
  501. exploreId: ExploreId;
  502. rawRange?: RawTimeRange;
  503. absoluteRange?: AbsoluteTimeRange;
  504. }): ThunkResult<void> => {
  505. return (dispatch, getState) => {
  506. const { exploreId, absoluteRange: absRange, rawRange: actionRange } = config;
  507. const itemState = getState().explore[exploreId];
  508. const timeZone = getTimeZone(getState().user);
  509. const { range: rangeInState } = itemState;
  510. let rawRange: RawTimeRange = rangeInState.raw;
  511. if (absRange) {
  512. rawRange = {
  513. from: dateTimeForTimeZone(timeZone, absRange.from),
  514. to: dateTimeForTimeZone(timeZone, absRange.to),
  515. };
  516. }
  517. if (actionRange) {
  518. rawRange = actionRange;
  519. }
  520. const range = getTimeRange(timeZone, rawRange);
  521. const absoluteRange: AbsoluteTimeRange = { from: range.from.valueOf(), to: range.to.valueOf() };
  522. getTimeSrv().init({
  523. time: range.raw,
  524. refresh: false,
  525. getTimezone: () => timeZone,
  526. timeRangeUpdated: (): any => undefined,
  527. });
  528. dispatch(changeRangeAction({ exploreId, range, absoluteRange }));
  529. };
  530. };
  531. /**
  532. * Start a scan for more results using the given scanner.
  533. * @param exploreId Explore area
  534. * @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
  535. */
  536. export function scanStart(exploreId: ExploreId): ThunkResult<void> {
  537. return (dispatch, getState) => {
  538. // Register the scanner
  539. dispatch(scanStartAction({ exploreId }));
  540. // Scanning must trigger query run, and return the new range
  541. const range = getShiftedTimeRange(-1, getState().explore[exploreId].range);
  542. // Set the new range to be displayed
  543. dispatch(updateTime({ exploreId, absoluteRange: range }));
  544. dispatch(runQueries(exploreId));
  545. };
  546. }
  547. /**
  548. * Reset queries to the given queries. Any modifications will be discarded.
  549. * Use this action for clicks on query examples. Triggers a query run.
  550. */
  551. export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult<void> {
  552. return (dispatch, getState) => {
  553. // Inject react keys into query objects
  554. const queries = getState().explore[exploreId].queries;
  555. const nextQueries = rawQueries.map((query, index) => generateNewKeyAndAddRefIdIfMissing(query, queries, index));
  556. dispatch(setQueriesAction({ exploreId, queries: nextQueries }));
  557. dispatch(runQueries(exploreId));
  558. };
  559. }
  560. /**
  561. * Close the split view and save URL state.
  562. */
  563. export function splitClose(itemId: ExploreId): ThunkResult<void> {
  564. return dispatch => {
  565. dispatch(splitCloseAction({ itemId }));
  566. dispatch(stateSave());
  567. };
  568. }
  569. /**
  570. * Open the split view and copy the left state to be the right state.
  571. * The right state is automatically initialized.
  572. * The copy keeps all query modifications but wipes the query results.
  573. */
  574. export function splitOpen(): ThunkResult<void> {
  575. return (dispatch, getState) => {
  576. // Clone left state to become the right state
  577. const leftState = getState().explore[ExploreId.left];
  578. const queryState = getState().location.query[ExploreId.left] as string;
  579. const urlState = parseUrlState(queryState);
  580. const itemState: ExploreItemState = {
  581. ...leftState,
  582. queries: leftState.queries.slice(),
  583. urlState,
  584. };
  585. dispatch(splitOpenAction({ itemState }));
  586. dispatch(stateSave());
  587. };
  588. }
  589. /**
  590. * Creates action to collapse graph/logs/table panel. When panel is collapsed,
  591. * queries won't be run
  592. */
  593. const togglePanelActionCreator = (
  594. actionCreator: ActionCreator<ToggleGraphPayload> | ActionCreator<ToggleTablePayload>
  595. ) => (exploreId: ExploreId, isPanelVisible: boolean): ThunkResult<void> => {
  596. return dispatch => {
  597. let uiFragmentStateUpdate: Partial<ExploreUIState>;
  598. const shouldRunQueries = !isPanelVisible;
  599. switch (actionCreator.type) {
  600. case toggleGraphAction.type:
  601. uiFragmentStateUpdate = { showingGraph: !isPanelVisible };
  602. break;
  603. case toggleTableAction.type:
  604. uiFragmentStateUpdate = { showingTable: !isPanelVisible };
  605. break;
  606. }
  607. dispatch(actionCreator({ exploreId }));
  608. dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate));
  609. if (shouldRunQueries) {
  610. dispatch(runQueries(exploreId));
  611. }
  612. };
  613. };
  614. /**
  615. * Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
  616. */
  617. export const toggleGraph = togglePanelActionCreator(toggleGraphAction);
  618. /**
  619. * Expand/collapse the table result viewer. When collapsed, table queries won't be run.
  620. */
  621. export const toggleTable = togglePanelActionCreator(toggleTableAction);
  622. /**
  623. * Change logs deduplication strategy and update URL.
  624. */
  625. export const changeDedupStrategy = (exploreId: ExploreId, dedupStrategy: LogsDedupStrategy): ThunkResult<void> => {
  626. return dispatch => {
  627. dispatch(updateExploreUIState(exploreId, { dedupStrategy }));
  628. };
  629. };
  630. export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
  631. return (dispatch, getState) => {
  632. const itemState = getState().explore[exploreId];
  633. if (!itemState.initialized) {
  634. return;
  635. }
  636. const { urlState, update, containerWidth, eventBridge } = itemState;
  637. const { datasource, queries, range: urlRange, mode, ui } = urlState;
  638. const refreshQueries: DataQuery[] = [];
  639. for (let index = 0; index < queries.length; index++) {
  640. const query = queries[index];
  641. refreshQueries.push(generateNewKeyAndAddRefIdIfMissing(query, refreshQueries, index));
  642. }
  643. const timeZone = getTimeZone(getState().user);
  644. const range = getTimeRangeFromUrl(urlRange, timeZone);
  645. // need to refresh datasource
  646. if (update.datasource) {
  647. const initialQueries = ensureQueries(queries);
  648. dispatch(initializeExplore(exploreId, datasource, initialQueries, range, mode, containerWidth, eventBridge, ui));
  649. return;
  650. }
  651. if (update.range) {
  652. dispatch(updateTime({ exploreId, rawRange: range.raw }));
  653. }
  654. // need to refresh ui state
  655. if (update.ui) {
  656. dispatch(updateUIStateAction({ ...ui, exploreId }));
  657. }
  658. // need to refresh queries
  659. if (update.queries) {
  660. dispatch(setQueriesAction({ exploreId, queries: refreshQueries }));
  661. }
  662. // need to refresh mode
  663. if (update.mode) {
  664. dispatch(changeModeAction({ exploreId, mode }));
  665. }
  666. // always run queries when refresh is needed
  667. if (update.queries || update.ui || update.range) {
  668. dispatch(runQueries(exploreId));
  669. }
  670. };
  671. }