grafana/public/app/features/explore/state/actions.ts

569 lines
17 KiB
TypeScript
Raw Normal View History

// Libraries
2019-01-10 07:24:31 -06:00
import _ from 'lodash';
// Services & Utils
import store from 'app/core/store';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { Emitter } from 'app/core/core';
2019-01-10 07:24:31 -06:00
import {
LAST_USED_DATASOURCE_KEY,
ensureQueries,
generateEmptyQuery,
parseUrlState,
getTimeRange,
getTimeRangeFromUrl,
generateNewKeyAndAddRefIdIfMissing,
2019-01-10 07:24:31 -06:00
} from 'app/core/utils/explore';
// Types
import { ThunkResult } from 'app/types';
import {
RawTimeRange,
DataSourceApi,
DataQuery,
DataSourceSelectItem,
2019-02-01 05:54:16 -06:00
QueryFixAction,
LogsDedupStrategy,
} from '@grafana/ui';
import { ExploreId, RangeScanner, ExploreUIState, QueryTransaction, ExploreMode } from 'app/types/explore';
2019-01-10 07:24:31 -06:00
import {
updateDatasourceInstanceAction,
changeQueryAction,
Explore & Dashboard: New Refresh picker (#16505) * Added RefreshButton * Added RefreshSelect * Added RefreshSelectButton * Added RefreshPicker * Removed the magic string Paused * Minor style changes and using Off instead of Pause * Added HeadlessSelect * Added HeadlessSelect story * Added SelectButton * Removed RefreshSelectButton * Added TimePicker and moved ClickOutsideWrapper to ui/components * Added TimePickerPopOver * Added react-calendar * Missed yarn lock file * Added inputs to popover * Added TimePicker and RefreshPicker to DashNav * Moved TimePicker and RefreshPicker to app/core * Added react-calendar to app and removed from ui/components * Fixed PopOver onClick * Moved everything back to ui components because of typings problems * Exporing RefreshPicker and TimePicker * Added Apply and inputs * Added typings * Added TimePickerInput and logic * Fixed parsing of string to Moments * Fixed range string * Styling and connecting the calendars and inputs * Changed Calendar styling * Added backward forward and zoom * Fixed responsive styles * Moved TimePicker and RefreshPicker into app core * Renamed menuIsOpen to isOpen * Changed from className={} to className="" * Moved Popover to TimePickerOptionGroup * Renamed all PopOver to Popover * Renamed popOver to popover and some minor refactorings * Renamed files with git mv * Added ButtonSelect and refactored RefreshPicker * Refactored TimePicker to use new ButtonSelect * Removed HeadlessSelect as suggested * fix: Fix typings and misc errors after rebase * wip: Enable time picker on dashboard and add tooltip * Merge branch 'master' into hugoh/new-timepicker-and-unified-component # Conflicts: # packages/grafana-ui/package.json # packages/grafana-ui/src/components/Input/Input.test.tsx # packages/grafana-ui/src/components/Input/Input.tsx # packages/grafana-ui/src/utils/validate.ts # public/app/features/dashboard/panel_editor/QueryOptions.tsx # yarn.lock * fix: Snapshot update * Move TimePicker default options into the TimePicker as statics, pass the tooltipContent down the line when wanted and wrap the button in a tooltip element * fix: Override internal state prop if we provide one in a prop * Updated snapshots * Let dashnav control refreshPicker state * feat: Add a stringToMs function * wip: RefreshPicker * wip: Move RefreshPicker to @grafana/ui * wip: Move TimePicker to @grafana/ui * wip: Remove comments * wip: Add refreshPicker to explore * wip: Use default intervals if the prop is missing * wip: Nicer way of setting defaults * fix: Control the select component * wip: Add onMoveForward/onMoveBack * Remove code related to the new time picker and refresh picker from dashnav * Fix: Typings after merge * chore: Minor fix after merge * chore: Remove _.map usage * chore: Moved refresh-picker logic out of the refresh picker since it will work a little differently in explore and dashboards until we have replaced the TimeSrv * feat: Add an Interval component to @grafana/ui * chore: Remove intervalId from redux state and move setInterval logic from ExploreToolbar to its own Interval component * feat: Add refreshInterval to Explore's URL state * feat: Pick up refreshInterval from url on page load * fix: Set default refreshInterval when no value can be retained from URL * fix: Update test initial state with refreshInterval * fix: Handle URLs before RefreshPicker * fix: Move RefreshInterval to url position 3 since the segments can take multiple positions * fix: A better way of detecting urls without RefreshInterval in Explore * chore: Some Explore typings * fix: Attach refresh picker to interval picker * chore: Sass fix for refresh button border radius * fix: Remove refreshInterval from URL * fix: Intervals now start when previous interval is finished * fix: Use clearTimeout instead of clearInterval * fix: Make sure there's a delay set before adding a timeout when we have slow explore queries * wip: Add refresh picker to dashboard * feat: Add util for removing keys with empty values * feat: RefreshPicker in dashboards and tmp rem out old RefreshPicker * fix: Remove the jumpy:ness in the refreshpicker * Changed placement and made it hide when your in dashboard settings * chore: Move logic related to refresh picker out of DashNav to its own component * feat: Add tooltip to refreshpicker * fix: Fix bug with refreshpicker not updating when setting to 'off' * fix: Make it possible to override refresh intervals using the dashboard intervals * chore: Change name of Interval to SetInterval to align with ecmascripts naming since its basically the same but declarative and async * fix: Use default intervals when auto refresh is empty in dashboard settings * fix: Hide time/interval picker when hidden is true on the model, such as on the home dashboard * fix: Interval picker will have to handle location changes since timeSrv wont * RefreshPicker: Refactoring refresh picker * RefreshPicker: minor refactoring
2019-04-16 02:15:23 -05:00
changeRefreshIntervalAction,
ChangeRefreshIntervalPayload,
changeSizeAction,
ChangeSizePayload,
changeTimeAction,
clearQueriesAction,
initializeExploreAction,
loadDatasourceMissingAction,
loadDatasourcePendingAction,
queriesImportedAction,
LoadDatasourceReadyPayload,
loadDatasourceReadyAction,
modifyQueriesAction,
scanStartAction,
setQueriesAction,
splitCloseAction,
splitOpenAction,
2019-02-04 08:06:01 -06:00
addQueryRowAction,
toggleGraphAction,
toggleTableAction,
2019-02-04 08:06:01 -06:00
ToggleGraphPayload,
ToggleTablePayload,
2019-02-07 10:46:33 -06:00
updateUIStateAction,
testDataSourcePendingAction,
testDataSourceSuccessAction,
testDataSourceFailureAction,
loadExploreDatasources,
changeModeAction,
scanStopAction,
scanRangeAction,
runQueriesAction,
stateSaveAction,
} from './actionTypes';
2019-02-04 08:06:01 -06:00
import { ActionOf, ActionCreator } from 'app/core/redux/actionCreatorFactory';
import { getTimeZone } from 'app/features/profile/state/selectors';
import { offOption } from '@grafana/ui/src/components/RefreshPicker/RefreshPicker';
2019-02-07 10:46:33 -06:00
/**
* Updates UI state and save it to the URL
*/
const updateExploreUIState = (exploreId: ExploreId, uiStateFragment: Partial<ExploreUIState>): ThunkResult<void> => {
2019-02-07 10:46:33 -06:00
return dispatch => {
dispatch(updateUIStateAction({ exploreId, ...uiStateFragment }));
dispatch(stateSaveAction());
2019-02-07 10:46:33 -06:00
};
};
/**
* Adds a query row after the row with the given index.
*/
2019-03-14 11:20:33 -05:00
export function addQueryRow(exploreId: ExploreId, index: number): ThunkResult<void> {
return (dispatch, getState) => {
const queries = getState().explore[exploreId].queries;
const query = generateEmptyQuery(queries, index);
2019-03-14 11:20:33 -05:00
dispatch(addQueryRowAction({ exploreId, index, query }));
};
2019-01-10 07:24:31 -06:00
}
/**
* Loads a new datasource identified by the given name.
*/
export function changeDatasource(exploreId: ExploreId, datasource: string): ThunkResult<void> {
return async (dispatch, getState) => {
let newDataSourceInstance: DataSourceApi = null;
if (!datasource) {
newDataSourceInstance = await getDatasourceSrv().get();
} else {
newDataSourceInstance = await getDatasourceSrv().get(datasource);
}
const currentDataSourceInstance = getState().explore[exploreId].datasourceInstance;
2019-02-04 23:19:40 -06:00
const queries = getState().explore[exploreId].queries;
2019-02-04 04:07:32 -06:00
await dispatch(importQueries(exploreId, queries, currentDataSourceInstance, newDataSourceInstance));
dispatch(updateDatasourceInstanceAction({ exploreId, datasourceInstance: newDataSourceInstance }));
if (getState().explore[exploreId].isLive) {
dispatch(changeRefreshInterval(exploreId, offOption.value));
}
await dispatch(loadDatasource(exploreId, newDataSourceInstance));
dispatch(runQueries(exploreId));
2019-01-10 07:24:31 -06:00
};
}
/**
* Change the display mode in Explore.
*/
export function changeMode(exploreId: ExploreId, mode: ExploreMode): ThunkResult<void> {
return dispatch => {
dispatch(changeModeAction({ exploreId, mode }));
dispatch(runQueries(exploreId));
};
}
/**
* Query change handler for the query row with the given index.
* If `override` is reset the query modifications and run the queries. Use this to set queries via a link.
*/
2019-01-11 11:26:56 -06:00
export function changeQuery(
exploreId: ExploreId,
query: DataQuery,
index: number,
override: boolean
): ThunkResult<void> {
2019-03-14 11:20:33 -05:00
return (dispatch, getState) => {
2019-01-10 07:24:31 -06:00
// Null query means reset
if (query === null) {
const queries = getState().explore[exploreId].queries;
const { refId, key } = queries[index];
query = generateNewKeyAndAddRefIdIfMissing({ refId, key }, queries, index);
2019-01-10 07:24:31 -06:00
}
dispatch(changeQueryAction({ exploreId, query, index, override }));
2019-01-10 07:24:31 -06:00
if (override) {
2019-01-11 11:26:56 -06:00
dispatch(runQueries(exploreId));
2019-01-10 07:24:31 -06:00
}
};
}
/**
* Keep track of the Explore container size, in particular the width.
* The width will be used to calculate graph intervals (number of datapoints).
*/
2019-01-11 11:26:56 -06:00
export function changeSize(
exploreId: ExploreId,
{ height, width }: { height: number; width: number }
): ActionOf<ChangeSizePayload> {
return changeSizeAction({ exploreId, height, width });
2019-01-11 11:26:56 -06:00
}
/**
Explore & Dashboard: New Refresh picker (#16505) * Added RefreshButton * Added RefreshSelect * Added RefreshSelectButton * Added RefreshPicker * Removed the magic string Paused * Minor style changes and using Off instead of Pause * Added HeadlessSelect * Added HeadlessSelect story * Added SelectButton * Removed RefreshSelectButton * Added TimePicker and moved ClickOutsideWrapper to ui/components * Added TimePickerPopOver * Added react-calendar * Missed yarn lock file * Added inputs to popover * Added TimePicker and RefreshPicker to DashNav * Moved TimePicker and RefreshPicker to app/core * Added react-calendar to app and removed from ui/components * Fixed PopOver onClick * Moved everything back to ui components because of typings problems * Exporing RefreshPicker and TimePicker * Added Apply and inputs * Added typings * Added TimePickerInput and logic * Fixed parsing of string to Moments * Fixed range string * Styling and connecting the calendars and inputs * Changed Calendar styling * Added backward forward and zoom * Fixed responsive styles * Moved TimePicker and RefreshPicker into app core * Renamed menuIsOpen to isOpen * Changed from className={} to className="" * Moved Popover to TimePickerOptionGroup * Renamed all PopOver to Popover * Renamed popOver to popover and some minor refactorings * Renamed files with git mv * Added ButtonSelect and refactored RefreshPicker * Refactored TimePicker to use new ButtonSelect * Removed HeadlessSelect as suggested * fix: Fix typings and misc errors after rebase * wip: Enable time picker on dashboard and add tooltip * Merge branch 'master' into hugoh/new-timepicker-and-unified-component # Conflicts: # packages/grafana-ui/package.json # packages/grafana-ui/src/components/Input/Input.test.tsx # packages/grafana-ui/src/components/Input/Input.tsx # packages/grafana-ui/src/utils/validate.ts # public/app/features/dashboard/panel_editor/QueryOptions.tsx # yarn.lock * fix: Snapshot update * Move TimePicker default options into the TimePicker as statics, pass the tooltipContent down the line when wanted and wrap the button in a tooltip element * fix: Override internal state prop if we provide one in a prop * Updated snapshots * Let dashnav control refreshPicker state * feat: Add a stringToMs function * wip: RefreshPicker * wip: Move RefreshPicker to @grafana/ui * wip: Move TimePicker to @grafana/ui * wip: Remove comments * wip: Add refreshPicker to explore * wip: Use default intervals if the prop is missing * wip: Nicer way of setting defaults * fix: Control the select component * wip: Add onMoveForward/onMoveBack * Remove code related to the new time picker and refresh picker from dashnav * Fix: Typings after merge * chore: Minor fix after merge * chore: Remove _.map usage * chore: Moved refresh-picker logic out of the refresh picker since it will work a little differently in explore and dashboards until we have replaced the TimeSrv * feat: Add an Interval component to @grafana/ui * chore: Remove intervalId from redux state and move setInterval logic from ExploreToolbar to its own Interval component * feat: Add refreshInterval to Explore's URL state * feat: Pick up refreshInterval from url on page load * fix: Set default refreshInterval when no value can be retained from URL * fix: Update test initial state with refreshInterval * fix: Handle URLs before RefreshPicker * fix: Move RefreshInterval to url position 3 since the segments can take multiple positions * fix: A better way of detecting urls without RefreshInterval in Explore * chore: Some Explore typings * fix: Attach refresh picker to interval picker * chore: Sass fix for refresh button border radius * fix: Remove refreshInterval from URL * fix: Intervals now start when previous interval is finished * fix: Use clearTimeout instead of clearInterval * fix: Make sure there's a delay set before adding a timeout when we have slow explore queries * wip: Add refresh picker to dashboard * feat: Add util for removing keys with empty values * feat: RefreshPicker in dashboards and tmp rem out old RefreshPicker * fix: Remove the jumpy:ness in the refreshpicker * Changed placement and made it hide when your in dashboard settings * chore: Move logic related to refresh picker out of DashNav to its own component * feat: Add tooltip to refreshpicker * fix: Fix bug with refreshpicker not updating when setting to 'off' * fix: Make it possible to override refresh intervals using the dashboard intervals * chore: Change name of Interval to SetInterval to align with ecmascripts naming since its basically the same but declarative and async * fix: Use default intervals when auto refresh is empty in dashboard settings * fix: Hide time/interval picker when hidden is true on the model, such as on the home dashboard * fix: Interval picker will have to handle location changes since timeSrv wont * RefreshPicker: Refactoring refresh picker * RefreshPicker: minor refactoring
2019-04-16 02:15:23 -05:00
* Change the time range of Explore. Usually called from the Time picker or a graph interaction.
*/
export function changeTime(exploreId: ExploreId, rawRange: RawTimeRange): ThunkResult<void> {
return (dispatch, getState) => {
const timeZone = getTimeZone(getState().user);
const range = getTimeRange(timeZone, rawRange);
dispatch(changeTimeAction({ exploreId, range }));
2019-01-11 11:26:56 -06:00
dispatch(runQueries(exploreId));
};
2019-01-10 07:24:31 -06:00
}
Explore & Dashboard: New Refresh picker (#16505) * Added RefreshButton * Added RefreshSelect * Added RefreshSelectButton * Added RefreshPicker * Removed the magic string Paused * Minor style changes and using Off instead of Pause * Added HeadlessSelect * Added HeadlessSelect story * Added SelectButton * Removed RefreshSelectButton * Added TimePicker and moved ClickOutsideWrapper to ui/components * Added TimePickerPopOver * Added react-calendar * Missed yarn lock file * Added inputs to popover * Added TimePicker and RefreshPicker to DashNav * Moved TimePicker and RefreshPicker to app/core * Added react-calendar to app and removed from ui/components * Fixed PopOver onClick * Moved everything back to ui components because of typings problems * Exporing RefreshPicker and TimePicker * Added Apply and inputs * Added typings * Added TimePickerInput and logic * Fixed parsing of string to Moments * Fixed range string * Styling and connecting the calendars and inputs * Changed Calendar styling * Added backward forward and zoom * Fixed responsive styles * Moved TimePicker and RefreshPicker into app core * Renamed menuIsOpen to isOpen * Changed from className={} to className="" * Moved Popover to TimePickerOptionGroup * Renamed all PopOver to Popover * Renamed popOver to popover and some minor refactorings * Renamed files with git mv * Added ButtonSelect and refactored RefreshPicker * Refactored TimePicker to use new ButtonSelect * Removed HeadlessSelect as suggested * fix: Fix typings and misc errors after rebase * wip: Enable time picker on dashboard and add tooltip * Merge branch 'master' into hugoh/new-timepicker-and-unified-component # Conflicts: # packages/grafana-ui/package.json # packages/grafana-ui/src/components/Input/Input.test.tsx # packages/grafana-ui/src/components/Input/Input.tsx # packages/grafana-ui/src/utils/validate.ts # public/app/features/dashboard/panel_editor/QueryOptions.tsx # yarn.lock * fix: Snapshot update * Move TimePicker default options into the TimePicker as statics, pass the tooltipContent down the line when wanted and wrap the button in a tooltip element * fix: Override internal state prop if we provide one in a prop * Updated snapshots * Let dashnav control refreshPicker state * feat: Add a stringToMs function * wip: RefreshPicker * wip: Move RefreshPicker to @grafana/ui * wip: Move TimePicker to @grafana/ui * wip: Remove comments * wip: Add refreshPicker to explore * wip: Use default intervals if the prop is missing * wip: Nicer way of setting defaults * fix: Control the select component * wip: Add onMoveForward/onMoveBack * Remove code related to the new time picker and refresh picker from dashnav * Fix: Typings after merge * chore: Minor fix after merge * chore: Remove _.map usage * chore: Moved refresh-picker logic out of the refresh picker since it will work a little differently in explore and dashboards until we have replaced the TimeSrv * feat: Add an Interval component to @grafana/ui * chore: Remove intervalId from redux state and move setInterval logic from ExploreToolbar to its own Interval component * feat: Add refreshInterval to Explore's URL state * feat: Pick up refreshInterval from url on page load * fix: Set default refreshInterval when no value can be retained from URL * fix: Update test initial state with refreshInterval * fix: Handle URLs before RefreshPicker * fix: Move RefreshInterval to url position 3 since the segments can take multiple positions * fix: A better way of detecting urls without RefreshInterval in Explore * chore: Some Explore typings * fix: Attach refresh picker to interval picker * chore: Sass fix for refresh button border radius * fix: Remove refreshInterval from URL * fix: Intervals now start when previous interval is finished * fix: Use clearTimeout instead of clearInterval * fix: Make sure there's a delay set before adding a timeout when we have slow explore queries * wip: Add refresh picker to dashboard * feat: Add util for removing keys with empty values * feat: RefreshPicker in dashboards and tmp rem out old RefreshPicker * fix: Remove the jumpy:ness in the refreshpicker * Changed placement and made it hide when your in dashboard settings * chore: Move logic related to refresh picker out of DashNav to its own component * feat: Add tooltip to refreshpicker * fix: Fix bug with refreshpicker not updating when setting to 'off' * fix: Make it possible to override refresh intervals using the dashboard intervals * chore: Change name of Interval to SetInterval to align with ecmascripts naming since its basically the same but declarative and async * fix: Use default intervals when auto refresh is empty in dashboard settings * fix: Hide time/interval picker when hidden is true on the model, such as on the home dashboard * fix: Interval picker will have to handle location changes since timeSrv wont * RefreshPicker: Refactoring refresh picker * RefreshPicker: minor refactoring
2019-04-16 02:15:23 -05:00
/**
* Change the refresh interval of Explore. Called from the Refresh picker.
*/
export function changeRefreshInterval(
exploreId: ExploreId,
refreshInterval: string
): ActionOf<ChangeRefreshIntervalPayload> {
return changeRefreshIntervalAction({ exploreId, refreshInterval });
}
/**
* Clear all queries and results.
*/
export function clearQueries(exploreId: ExploreId): ThunkResult<void> {
2019-01-10 07:24:31 -06:00
return dispatch => {
dispatch(scanStopAction({ exploreId }));
dispatch(clearQueriesAction({ exploreId }));
dispatch(stateSaveAction());
2019-01-11 11:26:56 -06:00
};
}
/**
* Loads all explore data sources and sets the chosen datasource.
* If there are no datasources a missing datasource action is dispatched.
*/
export function loadExploreDatasourcesAndSetDatasource(
exploreId: ExploreId,
datasourceName: string
): ThunkResult<void> {
return dispatch => {
const exploreDatasources: DataSourceSelectItem[] = getDatasourceSrv()
.getExternal()
2019-04-15 05:11:52 -05:00
.map(
(ds: any) =>
({
value: ds.name,
name: ds.name,
meta: ds.meta,
} as DataSourceSelectItem)
);
dispatch(loadExploreDatasources({ exploreId, exploreDatasources }));
if (exploreDatasources.length >= 1) {
dispatch(changeDatasource(exploreId, datasourceName));
} else {
dispatch(loadDatasourceMissingAction({ exploreId }));
}
};
}
/**
* Initialize Explore state with state from the URL and the React component.
* Call this only on components for with the Explore state has not been initialized.
*/
2019-01-10 07:24:31 -06:00
export function initializeExplore(
2019-01-11 11:26:56 -06:00
exploreId: ExploreId,
datasourceName: string,
2019-01-10 07:24:31 -06:00
queries: DataQuery[],
rawRange: RawTimeRange,
2019-01-10 07:24:31 -06:00
containerWidth: number,
eventBridge: Emitter,
ui: ExploreUIState
2019-01-10 07:24:31 -06:00
): ThunkResult<void> {
return async (dispatch, getState) => {
const timeZone = getTimeZone(getState().user);
const range = getTimeRange(timeZone, rawRange);
dispatch(loadExploreDatasourcesAndSetDatasource(exploreId, datasourceName));
dispatch(
initializeExploreAction({
2019-01-16 03:21:11 -06:00
exploreId,
containerWidth,
eventBridge,
queries,
range,
ui,
})
);
2019-01-10 07:24:31 -06:00
};
}
/**
* Datasource loading was successfully completed.
*/
export const loadDatasourceReady = (
exploreId: ExploreId,
instance: DataSourceApi
): ActionOf<LoadDatasourceReadyPayload> => {
2019-01-10 07:24:31 -06:00
const historyKey = `grafana.explore.history.${instance.meta.id}`;
const history = store.getObject(historyKey, []);
// Save last-used datasource
store.set(LAST_USED_DATASOURCE_KEY, instance.name);
return loadDatasourceReadyAction({
exploreId,
history,
});
2019-01-10 07:24:31 -06:00
};
export function importQueries(
exploreId: ExploreId,
queries: DataQuery[],
sourceDataSource: DataSourceApi,
targetDataSource: DataSourceApi
): ThunkResult<void> {
return async dispatch => {
if (!sourceDataSource) {
// explore not initialized
dispatch(queriesImportedAction({ exploreId, queries }));
return;
}
let importedQueries = queries;
// Check if queries can be imported from previously selected datasource
if (sourceDataSource.meta.id === targetDataSource.meta.id) {
// Keep same queries if same type of datasource
importedQueries = [...queries];
} else if (targetDataSource.importQueries) {
// Datasource-specific importers
importedQueries = await targetDataSource.importQueries(queries, sourceDataSource.meta);
} else {
// Default is blank queries
importedQueries = ensureQueries();
}
2019-01-30 02:36:23 -06:00
const nextQueries = ensureQueries(importedQueries);
2019-01-30 02:36:23 -06:00
dispatch(queriesImportedAction({ exploreId, queries: nextQueries }));
};
}
/**
* Tests datasource.
*/
export const testDatasource = (exploreId: ExploreId, instance: DataSourceApi): ThunkResult<void> => {
return async dispatch => {
2019-01-10 07:24:31 -06:00
let datasourceError = null;
dispatch(testDataSourcePendingAction({ exploreId }));
2019-01-10 07:24:31 -06:00
try {
const testResult = await instance.testDatasource();
datasourceError = testResult.status === 'success' ? null : testResult.message;
} catch (error) {
datasourceError = (error && error.statusText) || 'Network error';
}
2019-01-21 14:36:30 -06:00
2019-01-10 07:24:31 -06:00
if (datasourceError) {
dispatch(testDataSourceFailureAction({ exploreId, error: datasourceError }));
return;
2019-01-10 07:24:31 -06:00
}
dispatch(testDataSourceSuccessAction({ exploreId }));
};
};
/**
* Reconnects datasource when there is a connection failure.
*/
export const reconnectDatasource = (exploreId: ExploreId): ThunkResult<void> => {
return async (dispatch, getState) => {
const instance = getState().explore[exploreId].datasourceInstance;
dispatch(changeDatasource(exploreId, instance.name));
};
};
/**
* Main action to asynchronously load a datasource. Dispatches lots of smaller actions for feedback.
*/
export function loadDatasource(exploreId: ExploreId, instance: DataSourceApi): ThunkResult<void> {
return async (dispatch, getState) => {
const datasourceName = instance.name;
// Keep ID to track selection
dispatch(loadDatasourcePendingAction({ exploreId, requestedDatasourceName: datasourceName }));
await dispatch(testDatasource(exploreId, instance));
2019-01-21 14:36:30 -06:00
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
2019-01-10 07:24:31 -06:00
// User already changed datasource again, discard results
return;
}
if (instance.init) {
try {
instance.init();
} catch (err) {
console.log(err);
}
2019-01-10 07:24:31 -06:00
}
2019-01-21 14:36:30 -06:00
if (datasourceName !== getState().explore[exploreId].requestedDatasourceName) {
2019-01-10 07:24:31 -06:00
// User already changed datasource again, discard results
return;
}
dispatch(loadDatasourceReady(exploreId, instance));
2019-01-10 07:24:31 -06:00
};
}
/**
* Action to modify a query given a datasource-specific modifier action.
* @param exploreId Explore area
* @param modification Action object with a type, e.g., ADD_FILTER
* @param index Optional query row index. If omitted, the modification is applied to all query rows.
* @param modifier Function that executes the modification, typically `datasourceInstance.modifyQueries`.
*/
2019-01-11 11:26:56 -06:00
export function modifyQueries(
exploreId: ExploreId,
2019-02-01 05:54:16 -06:00
modification: QueryFixAction,
2019-01-11 11:26:56 -06:00
index: number,
modifier: any
): ThunkResult<void> {
2019-01-10 07:24:31 -06:00
return dispatch => {
dispatch(modifyQueriesAction({ exploreId, modification, index, modifier }));
2019-01-10 07:24:31 -06:00
if (!modification.preventSubmit) {
2019-01-11 11:26:56 -06:00
dispatch(runQueries(exploreId));
2019-01-10 07:24:31 -06:00
}
};
}
/**
* Main action to run queries and dispatches sub-actions based on which result viewers are active
*/
export function runQueries(exploreId: ExploreId): ThunkResult<void> {
2019-01-10 07:24:31 -06:00
return (dispatch, getState) => {
const { range } = getState().explore[exploreId];
2019-01-10 07:24:31 -06:00
const timeZone = getTimeZone(getState().user);
const updatedRange = getTimeRange(timeZone, range.raw);
dispatch(runQueriesAction({ exploreId, range: updatedRange }));
2019-01-10 07:24:31 -06:00
};
}
/**
* Start a scan for more results using the given scanner.
* @param exploreId Explore area
* @param scanner Function that a) returns a new time range and b) triggers a query run for the new range
*/
2019-01-11 11:26:56 -06:00
export function scanStart(exploreId: ExploreId, scanner: RangeScanner): ThunkResult<void> {
2019-01-10 07:24:31 -06:00
return dispatch => {
// Register the scanner
dispatch(scanStartAction({ exploreId, scanner }));
// Scanning must trigger query run, and return the new range
2019-01-10 07:24:31 -06:00
const range = scanner();
// Set the new range to be displayed
dispatch(scanRangeAction({ exploreId, range }));
2019-01-10 07:24:31 -06:00
};
}
/**
* Reset queries to the given queries. Any modifications will be discarded.
* Use this action for clicks on query examples. Triggers a query run.
*/
export function setQueries(exploreId: ExploreId, rawQueries: DataQuery[]): ThunkResult<void> {
2019-03-14 11:20:33 -05:00
return (dispatch, getState) => {
// Inject react keys into query objects
const queries = getState().explore[exploreId].queries;
const nextQueries = rawQueries.map((query, index) => generateNewKeyAndAddRefIdIfMissing(query, queries, index));
dispatch(setQueriesAction({ exploreId, queries: nextQueries }));
dispatch(runQueries(exploreId));
};
}
/**
* Close the split view and save URL state.
*/
export function splitClose(itemId: ExploreId): ThunkResult<void> {
return dispatch => {
dispatch(splitCloseAction({ itemId }));
dispatch(stateSaveAction());
};
}
/**
* Open the split view and copy the left state to be the right state.
* The right state is automatically initialized.
* The copy keeps all query modifications but wipes the query results.
*/
export function splitOpen(): ThunkResult<void> {
return (dispatch, getState) => {
// Clone left state to become the right state
const leftState = getState().explore[ExploreId.left];
const queryState = getState().location.query[ExploreId.left] as string;
const urlState = parseUrlState(queryState);
const queryTransactions: QueryTransaction[] = [];
const itemState = {
...leftState,
queryTransactions,
2019-02-04 23:19:40 -06:00
queries: leftState.queries.slice(),
exploreId: ExploreId.right,
urlState,
};
dispatch(splitOpenAction({ itemState }));
dispatch(stateSaveAction());
2019-01-12 16:22:28 -06:00
};
}
/**
* Creates action to collapse graph/logs/table panel. When panel is collapsed,
* queries won't be run
*/
2019-02-04 08:06:01 -06:00
const togglePanelActionCreator = (
actionCreator: ActionCreator<ToggleGraphPayload> | ActionCreator<ToggleTablePayload>
) => (exploreId: ExploreId, isPanelVisible: boolean): ThunkResult<void> => {
2019-02-11 06:18:16 -06:00
return dispatch => {
let uiFragmentStateUpdate: Partial<ExploreUIState>;
const shouldRunQueries = !isPanelVisible;
2019-02-04 08:06:01 -06:00
switch (actionCreator.type) {
case toggleGraphAction.type:
uiFragmentStateUpdate = { showingGraph: !isPanelVisible };
break;
2019-02-04 08:06:01 -06:00
case toggleTableAction.type:
uiFragmentStateUpdate = { showingTable: !isPanelVisible };
break;
}
2019-02-07 10:46:33 -06:00
dispatch(actionCreator({ exploreId }));
dispatch(updateExploreUIState(exploreId, uiFragmentStateUpdate));
if (shouldRunQueries) {
dispatch(runQueries(exploreId));
}
};
};
/**
* Expand/collapse the graph result viewer. When collapsed, graph queries won't be run.
*/
2019-02-04 08:06:01 -06:00
export const toggleGraph = togglePanelActionCreator(toggleGraphAction);
/**
* Expand/collapse the table result viewer. When collapsed, table queries won't be run.
*/
2019-02-04 08:06:01 -06:00
export const toggleTable = togglePanelActionCreator(toggleTableAction);
2019-02-07 10:46:33 -06:00
/**
* Change logs deduplication strategy and update URL.
*/
export const changeDedupStrategy = (exploreId: ExploreId, dedupStrategy: LogsDedupStrategy): ThunkResult<void> => {
2019-02-07 10:46:33 -06:00
return dispatch => {
dispatch(updateExploreUIState(exploreId, { dedupStrategy }));
};
};
export function refreshExplore(exploreId: ExploreId): ThunkResult<void> {
return (dispatch, getState) => {
const itemState = getState().explore[exploreId];
if (!itemState.initialized) {
return;
}
const { urlState, update, containerWidth, eventBridge } = itemState;
const { datasource, queries, range: urlRange, ui } = urlState;
const refreshQueries: DataQuery[] = [];
for (let index = 0; index < queries.length; index++) {
const query = queries[index];
refreshQueries.push(generateNewKeyAndAddRefIdIfMissing(query, refreshQueries, index));
}
const timeZone = getTimeZone(getState().user);
const range = getTimeRangeFromUrl(urlRange, timeZone);
// need to refresh datasource
if (update.datasource) {
const initialQueries = ensureQueries(queries);
dispatch(initializeExplore(exploreId, datasource, initialQueries, range, containerWidth, eventBridge, ui));
return;
}
if (update.range) {
dispatch(changeTimeAction({ exploreId, range }));
}
// need to refresh ui state
if (update.ui) {
dispatch(updateUIStateAction({ ...ui, exploreId }));
}
// need to refresh queries
if (update.queries) {
dispatch(setQueriesAction({ exploreId, queries: refreshQueries }));
}
// always run queries when refresh is needed
if (update.queries || update.ui || update.range) {
dispatch(runQueries(exploreId));
}
};
}